#include <Block/SpriteBlock/Pot/Pot.hpp>
#include <Controller/InventoryController/PotController/PotController.hpp>
#include <Utilities/Utilities.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <BlockEffect/BlockEffect.hpp>
#include <FrameDrawer/FrameDrawer.hpp>
#include <ChunkWindow/ChunkWindow.hpp>
#include <Milestone/BronzeAgeMilestone/BronzeAgeMilestone.hpp>
#include <Milestone/MetalWorkingMilestone/MetalWorkingMilestone.hpp>
#include <random>

class PotEffect : public BlockEffect
{
private:
    glm::vec3 offset;
    uint32_t lastParticleSpawn;

    static constexpr uint32_t ticksBetweenSpawns = 5;

public:
    PotEffect(glm::vec3 offset) : offset(offset)
    {
        lastParticleSpawn = SDL_GetTicks();
    }

    virtual void update(ChunkWindow &window) override
    {
        if (SDL_GetTicks() - lastParticleSpawn >= ticksBetweenSpawns)
        {
            std::random_device rd;
            std::mt19937 gen(rd());
            std::uniform_real_distribution<float> posDis(0, 1);
            std::uniform_real_distribution<float> unitDis(-1, 1);

            uint32_t count = (SDL_GetTicks() - lastParticleSpawn) / ticksBetweenSpawns;

            lastParticleSpawn += count * ticksBetweenSpawns;

            count = std::min(16u, count);

            for (int i = 0; i < count; i++)
            {
                ParticlesStage::Particle particle{};
                float dx = unitDis(gen);
                float dy = unitDis(gen);
                float dl = posDis(gen);
                float dist = std::sqrt(dx * dx + dy * dy);
                particle.startPos = offset + glm::vec3(dl, 0.0f + 0.02f * dy, 0.5f/8.0f);
                particle.startVel = glm::vec3(0.0f, 0.0f, 0.1f + 0.1f * posDis(gen));
                particle.startSize = glm::vec3(1.0f/16.0f);
                particle.endSize = glm::vec3(0.0f);
                particle.startColor = glm::vec4(1.0f, 0.2f, 0.01f, 1.0f);
                particle.endColor = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
                particle.startTime = lastParticleSpawn;
                particle.endTime = particle.startTime + (200 + 100 * posDis(gen)) * (1.0f - dist * 0.3f);
                window.emitParticle(particle);

                particle.startPos = offset + glm::vec3(0.0f + 0.02f * dx, dl, 0.5f/8.0f);
                window.emitParticle(particle);

                particle.startPos = offset + glm::vec3(1.0f + 0.02f * dx, dl, 0.5f/8.0f);
                window.emitParticle(particle);

                particle.startPos = offset + glm::vec3(dl, 1.0f + 0.02f * dy, 0.5f/8.0f);
                window.emitParticle(particle);
            }
        }
    }
};

std::vector<Pot::Sprite> Pot::makeSprites(bool isIron)
{
    std::vector<Sprite> sprites;
    std::string typeName = isIron ? "iron" : "stone";

    int i;
    for (i=0; i<8; i++)
    {
        std::string sideTexName = formatString("pot/$+/side$+", typeName, i);
        std::string topTexName = formatString("pot/$+/top", typeName);

        sprites.push_back(
            Sprite{
                {sideTexName, sideTexName, sideTexName, sideTexName, "stone", topTexName},
                {
                    BoundingBox<int>{0, 0, 0, 8, 8, 1},
                    BoundingBox<int>{1, 1, 1, 7, 7, 2},
                    BoundingBox<int>{0, 0, 2, 8, 8, 3},
                    BoundingBox<int>{0, 0, 3, 1, 8, 7},
                    BoundingBox<int>{7, 0, 3, 8, 8, 7},
                    BoundingBox<int>{1, 0, 3, 7, 1, 7},
                    BoundingBox<int>{1, 7, 3, 7, 8, 7},
                },
                formatString("$+-temp$+", (i & 4) ? "lit" : "unlit", (i & 3)),
            }
        );
    }

    return sprites;
}

Pot::Pot(std::string name, bool isIron) : SpriteBlock(
    name,
    isIron ? "Iron Pot" : "Stone Pot",
    60.0f,
    formatString("pot/$+/side0", isIron ? "iron" : "stone"),
    makeSprites(isIron)
), isIron(isIron)
{
    if (isIron)
    {
        withMetalSounds();
    }
    else
    {
        withStoneSounds();
    }
}

glm::vec3 Pot::getLightColor(uint8_t meta)
{
    return glm::vec3(0.2f * (meta & 3), 0.12f * (meta & 3), 0.0f);
}

float Pot::getLightVariance(uint8_t meta)
{
    if (meta & (1 << 2))
    {
        return 0.1f;
    }

    return 0.0f;
}

glm::ivec3 Pot::getLightOffset(uint8_t meta)
{
    return glm::ivec3(4, 4, 1);
}

void Pot::onInteract(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    if (isIron)
    {
        player.achieve(MetalWorkingMilestone::ID, MetalWorkingMilestone::IronPot);
    }
    else
    {
        player.achieve(BronzeAgeMilestone::ID, BronzeAgeMilestone::StonePot);
    }

    player.connectController<PotController>(
        isIron ? "main:IronPotController" : "main:StonePotController",
        world.getMachine(pos)
    );
}

void Pot::getHarvestedStacks(LocalWorld &world, glm::ivec3 pos, std::vector<ItemStack> &stacks)
{
    SpriteBlock::getHarvestedStacks(world, pos, stacks);
    
    std::shared_ptr<IInventory> inv = std::dynamic_pointer_cast<IInventory>(world.getMachine(pos));
    if (inv != nullptr)
    {
        int i;
        for (i=0; i<inv->getNumSlots(); i++)
        {
            ItemStack stack = inv->getStackInSlot(i);
            if (!stack.isEmpty())
            {
                stacks.push_back(stack);
            }
        }
    }
}

bool Pot::canManuallyDig()
{
    return true;
}

Sound& Pot::getBackgroundSound(uint8_t meta)
{
    if (meta & 4)
    {
        return Sound::fire;
    }

    return Sound::NONE;
}

void Pot::getCollisionBoxes(World &world, glm::ivec3 pos, std::vector<BoundingBox<wfixed>> &boxes)
{
    boxes.push_back(BoundingBox<wfixed>{
        (wfixed) pos.x,
        (wfixed) pos.y,
        (wfixed) pos.z,
        (wfixed) (pos.x+1),
        (wfixed) (pos.y+1),
        (wfixed) pos.z + wfixed(1.0 - 1.0/8.0),
    });
}

void Pot::getEffects(uint8_t meta, glm::vec3 offset, std::vector<std::shared_ptr<BlockEffect>> &effects)
{
    if (meta & (1 << 2))
    {
        effects.push_back(std::make_shared<PotEffect>(offset));
    }
}