#include <Block/Block.hpp>
#include <ChunkHandle/ChunkHandle.hpp>
#include <Machine/MSolarPanel/MSolarPanel.hpp>
#include <MachineComponent/AttributionComponent/AttributionComponent.hpp>
#include <MachineComponent/ChunkMonitorComponent/ChunkMonitorComponent.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <Milestone/ElectronicsMilestone/ElectronicsMilestone.hpp>

MSolarPanel::MSolarPanel(LocalWorld &world, glm::ivec3 pos) : Machine(world, pos)
{
    attach<ElectricComponent>();
    attach<AttributionComponent>();
    auto &monitor = attach<ChunkMonitorComponent>();

    monitor.addBlockChangedCallback(
        [this, pos](glm::ivec3 blockPos, Block &oldBlock, uint8_t oldMeta, Block &newBlock, uint8_t newMeta)
        {
            if (blockPos.x != pos.x || blockPos.y != pos.y)
            {
                return;
            }

            int offset = blockPos.z - pos.z - 1;
            if (offset < 0 || offset >= sunlightCheckHeight)
            {
                return;
            }

            blockCovering[offset] = newBlock.name != "main:air";
        }
    );
}

void MSolarPanel::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, bufferedEnergy, chunkMonitoringDone, blockCovering);
}

void MSolarPanel::deserializeState(const uint8_t *data, size_t size)
{
    deserialize(data, size, bufferedEnergy, chunkMonitoringDone, blockCovering);
}

void MSolarPanel::update()
{
    if (pos.z < - 16)
    {
        return;
    }

    if (!chunkMonitoringDone)
    {
        std::vector<ChunkHandle> chunks;
        bool anyNotReady = false;
        
        for (glm::ivec3 chunkPos = pos + glm::ivec3(0, 0, 1); (chunkPos.z & ~0xF) <= pos.z + sunlightCheckHeight; chunkPos.z += 16)
        {
            ChunkHandle chunk = m_world.requestChunk(chunkPos);
            if (!chunk->isReady())
            {
                anyNotReady = true;
                continue;
            }

            chunks.push_back(chunk);
        }

        if (anyNotReady)
        {
            return;
        }

        ChunkMonitorComponent &monitor = getComponent<ChunkMonitorComponent>();
        for (const ChunkHandle &chunk : chunks)
        {
            monitor.monitor(chunk);
        }

        chunkMonitoringDone = true;

        for (int offset = 0; offset < sunlightCheckHeight; offset++)
        {
            blockCovering[offset] = m_world.getBlockAt(pos + glm::ivec3(0, 0, offset + 1))->name != "main:air";
        }
    }

    if (blockCovering.any())
    {
        return;
    }

    bufferedEnergy += m_world.getSunIntensity() * m_world.weather.getCloudCover();

    ElectricComponent &electricComp = getComponent<ElectricComponent>();
    if (bufferedEnergy >= 1.0f)
    {
        electricComp.rechargeBy(1, 1);
    }

    bufferedEnergy -= std::floor(bufferedEnergy);
}

bool MSolarPanel::canConnectElectricWire(Side side)
{
    return side == Side::Bottom;
}

bool MSolarPanel::canDrainEnergyFromSide(Side side)
{
    return side == Side::Bottom;
}

void MSolarPanel::onEnergyDrained(int amount)
{
    getComponent<AttributionComponent>().grantAchievementFor(
        std::string(Machine::Action::Placed),
        ElectronicsMilestone::ID, ElectronicsMilestone::GenerateSolarEnergy
    );
}