#include <Block/SpriteBlock/Torch/Torch.hpp>
#include <Utilities/Extern.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <random>
#include <FrameDrawer/FrameDrawer.hpp>
#include <ChunkWindow/ChunkWindow.hpp>

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

    static constexpr uint32_t ticksBetweenSpawns = 2;

public:
    TorchEffect(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 dist = std::sqrt(dx * dx + dy * dy);
                particle.startPos = offset + glm::vec3(0.5f + 0.170f * dx, 0.5f + 0.170f * dy, 6.8f/8.0f + 0.170f * unitDis(gen));
                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 + (600 + 400 * posDis(gen)) * (1.0f - dist * 0.3f);

                window.emitParticle(particle);
            }
        }
    }
};

std::vector<Torch::Sprite> Torch::makeSprites()
{
    std::vector<Sprite> sprites;

    int i;
    for (i=0; i<4; i++)
    {
        glm::ivec2 base(0, 0);
        base[i >> 1] = (i & 1) ? 6 : 0;
        base[(i >> 1) ^ 1] = 3;

        glm::ivec2 shift(0, 0);
        shift[i >> 1] = (i & 1) ? -1 : 1;

        std::vector<BoundingBox<int>> boxes;

        int z;
        for (z=0; z<4; z++)
        {
            glm::ivec2 thisBase = base + shift * z;
            boxes.push_back(BoundingBox<int>{thisBase.x, thisBase.y, z+1, thisBase.x+2, thisBase.y+2, z+2});
        }

        boxes.push_back(BoundingBox<int>{3, 3, 5, 5, 5, 7});

        sprites.push_back(
            Sprite{
                {"torchSide", "torchSide", "torchSide", "torchSide", "torchBottom", "torchTop"},
                boxes,
                formatString("attached$+", Side(i).name())
            }
        );
    }

    sprites.push_back(
        Sprite{
            {"torchSide", "torchSide", "torchSide", "torchSide", "torchBottom", "torchTop"},
            {
                BoundingBox<int>{3, 3, 0, 5, 5, 7},
            },
            "upright"
        }
    );

    return sprites;
}

Torch::Torch(std::string name) : SpriteBlock(
    name,
    "Torch",
    30.0f,
    "torchIcon",
    makeSprites()
)
{}

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

glm::vec3 Torch::getLightColor(uint8_t meta)
{
    return glm::vec3(0.24f, 0.18f, 0.0f);
}

float Torch::getLightVariance(uint8_t meta)
{
    return 0.039f;
}

bool Torch::canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta)
{
    if (side == Side::Top)
    {
        // Torches cannot connect to the top.
        return false;
    }

    // Otherwise, connect us to the specified side.
    meta = side.asNumber();
    return true;
}

void Torch::onNeighborUpdated(LocalWorld &world, glm::ivec3 pos, Side side)
{
    Side connectingSide(*world.getBlockMeta(pos));
    if (side != connectingSide)
    {
        // We don't care about changes to sides we aren't connected to.
        return;
    }

    glm::ivec3 targetPos = pos + side.out();

    Block *connectedBlock = world.getBlockAt(targetPos);
    if (connectedBlock->name == "main:air")
    {
        // If we are now connected to air, break.
        world.breakBlock(pos);
    }
}

bool Torch::isIntersectedBy(World &world, glm::ivec3 pos, const BoundingBox<wfixed> &box)
{
    // Allow entities to walk through toches.
    return false;
}

void Torch::getEffects(uint8_t meta, glm::vec3 offset, std::vector<std::shared_ptr<BlockEffect>> &effects)
{
    effects.push_back(std::make_shared<TorchEffect>(offset));
}