#include <Block/SpriteBlock/PipelineBlock/PipelineBlock.hpp>
#include <bitset>

std::vector<PipelineBlock::Sprite> PipelineBlock::makeSprites(const char *textureNameFormat, int pipeMargin)
{
    std::vector<Sprite> result;

    for (uint8_t meta = 0; meta < (1 << 6); meta++)
    {
        const BoundingBox<int> centerBB{
            pipeMargin, pipeMargin, pipeMargin,
            8-pipeMargin, 8-pipeMargin, 8-pipeMargin,
        };

        const BoundingBox<int> connectionBoxes[6] = {
            // side 0 (-X)
            BoundingBox<int>{
                0, pipeMargin, pipeMargin,
                pipeMargin, 8-pipeMargin, 8-pipeMargin,
            },

            // side 1 (+X)
            BoundingBox<int>{
                8-pipeMargin, pipeMargin, pipeMargin,
                8, 8-pipeMargin, 8-pipeMargin,
            },

            // side 2 (-Y)
            BoundingBox<int>{
                pipeMargin, 0, pipeMargin,
                8-pipeMargin, pipeMargin, 8-pipeMargin,
            },

            // side 3 (+Y)
            BoundingBox<int>{
                pipeMargin, 8-pipeMargin, pipeMargin,
                8-pipeMargin, 8, 8-pipeMargin,
            },

            // side 4 (-Z)
            BoundingBox<int>{
                pipeMargin, pipeMargin, 0,
                8-pipeMargin, 8-pipeMargin, pipeMargin,
            },

            // side 5 (+Z)
            BoundingBox<int>{
                pipeMargin, pipeMargin, 8-pipeMargin,
                8-pipeMargin, 8-pipeMargin, 8,
            },
        };

        Sprite sprite{
            {},
            {centerBB},
            formatString("$+", std::bitset<6>(meta))
        };

        for (const Side &side : Side::all)
        {
            const char *texSubTypeName;
            if (meta == 0)
            {
                // If there is no branching, show the tape ("connected") on all sides.
                texSubTypeName = "connected";
            }
            else if (meta & side.mask())
            {
                texSubTypeName = "tip";
            }
            else if (meta == side.opposite().mask())
            {
                // We are the center piece for a cable which only branches in the opposite direction, so we are a tip.
                texSubTypeName = "tip";
            }
            else if ((meta & ~3) == 0)
            {
                // We are only branching in the X dimension.
                texSubTypeName = "xonly";
            }
            else if ((meta & ~0xc) == 0)
            {
                // We are only branching in the Y dimension.
                texSubTypeName = "yonly";
            }
            else if ((meta & ~0x30) == 0)
            {
                // We are only branching in the Z dimension.
                texSubTypeName = "zonly";
            }
            else
            {
                texSubTypeName = "connected";
            }

            std::string subTexName = formatString("$+/$+", side, texSubTypeName);
            sprite.texturesOnSides[side.asNumber()] = formatString(textureNameFormat, subTexName);

            if (meta & side.mask())
            {
                sprite.segments.push_back(connectionBoxes[side.asNumber()]);
            }
        }

        result.push_back(sprite);
    }

    return result;
}

PipelineBlock::PipelineBlock(std::string name, std::string displayName, const char *textureNameFormat, int pipeMargin) :
    SpriteBlock(
        name,
        displayName,
        1.0f,
        formatString(textureNameFormat, "icon"),
        makeSprites(textureNameFormat, pipeMargin)
    ),
    pipeMargin(pipeMargin)
{}

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

bool PipelineBlock::isFullyOpaque()
{
    return false;
}

void PipelineBlock::onPlaced(LocalWorld &world, Chunk &chunk, glm::ivec3 pos)
{
    uint8_t meta = 0;

    for (const Side &side : Side::all)
    {
        if (shouldConnectOnSide(world, pos, side))
        {
            meta |= (1 << side.asNumber());
        }
    }

    world.setBlockMeta(pos, meta);

    Wiring &wiring = getWiringFor(chunk);
    wiring.onWireBlockPlaced(world, chunk.getNode(), pos);
}

void PipelineBlock::onRemoved(LocalWorld &world, Chunk &chunk, glm::ivec3 pos, uint8_t meta)
{
    Wiring &wiring = getWiringFor(chunk);
    wiring.onWireBlockRemoved(world, chunk.getNode(), pos);
}

void PipelineBlock::onNeighborUpdated(LocalWorld &world, glm::ivec3 pos, Side side)
{
    uint8_t meta = *world.getBlockMeta(pos);
    meta &= ~(1 << side.asNumber());

    if (shouldConnectOnSide(world, pos, side))
    {
        meta |= (1 << side.asNumber());
    }

    world.setBlockMeta(pos, meta);
}

void PipelineBlock::getCollisionBoxes(World &world, glm::ivec3 pos, std::vector<BoundingBox<wfixed>> &boxes)
{
    uint8_t meta = *world.getBlockMeta(pos);

    BoundingBox<wfixed> center;
    center.x0 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
    center.x1 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
    center.y0 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
    center.y1 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
    center.z0 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
    center.z1 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
    boxes.push_back(center);

    if (meta & (1 << 0))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x;
        side.x1 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
        side.y0 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
        side.y1 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.z0 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
        side.z1 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        boxes.push_back(side);
    }

    if (meta & (1 << 1))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.x1 = (wfixed) pos.x + wfixed(1.0);
        side.y0 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
        side.y1 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.z0 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
        side.z1 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        boxes.push_back(side);
    }

    if (meta & (1 << 2))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
        side.x1 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.y0 = (wfixed) pos.y;
        side.y1 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
        side.z0 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
        side.z1 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        boxes.push_back(side);
    }

    if (meta & (1 << 3))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
        side.x1 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.y0 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.y1 = (wfixed) pos.y + wfixed(1.0);
        side.z0 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
        side.z1 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        boxes.push_back(side);
    }

    if (meta & (1 << 4))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
        side.x1 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.y0 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
        side.y1 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.z0 = (wfixed) pos.z;
        side.z1 = (wfixed) pos.z + (wfixed) pipeMargin / wfixed(8.0);
        boxes.push_back(side);
    }

    if (meta & (1 << 5))
    {
        BoundingBox<wfixed> side;
        side.x0 = (wfixed) pos.x + (wfixed) pipeMargin / wfixed(8.0);
        side.x1 = (wfixed) pos.x + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.y0 = (wfixed) pos.y + (wfixed) pipeMargin / wfixed(8.0);
        side.y1 = (wfixed) pos.y + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.z0 = (wfixed) pos.z + wfixed(1.0) - (wfixed) pipeMargin / wfixed(8.0);
        side.z1 = (wfixed) pos.z + wfixed(1.0);
        boxes.push_back(side);
    }
}