#include "ChunkMap.h"

#include "Chunk.h"
#include "Constants.h"
#include "Game.h"

ChunkMap::ChunkMap(Framework::Punkt chunkCenter)
    : ReferenceCounter(),
      chunkCenter(chunkCenter)
{
    pixels = new MapPixel[CHUNK_SIZE * CHUNK_SIZE];
    memset(pixels, 0, sizeof(MapPixel) * CHUNK_SIZE * CHUNK_SIZE);
}

ChunkMap::ChunkMap(Chunk* zChunk)
    : ReferenceCounter(),
      chunkCenter(zChunk->location)
{
    pixels = new MapPixel[CHUNK_SIZE * CHUNK_SIZE];
    memset(pixels, 0, sizeof(MapPixel) * CHUNK_SIZE * CHUNK_SIZE);
    MapBlock blocksBuffer[256];
    for (int x = 0; x < CHUNK_SIZE; x++)
    {
        for (int y = 0; y < CHUNK_SIZE; y++)
        {
            int count = 0;
            bool visible = 1;
            for (int height = WORLD_HEIGHT / 2 - 1; height >= 0; height--)
            {
                int index = (x * CHUNK_SIZE + y) * WORLD_HEIGHT;
                const Block* block1
                    = CONST_BLOCK(zChunk->blocks[index + height * 2],
                        zChunk->blockIds[index + height * 2]);
                const Block* block2
                    = CONST_BLOCK(zChunk->blocks[index + height * 2 + 1],
                        zChunk->blockIds[index + height * 2 + 1]);
                int color1 = 0;
                int color2 = 0;
                if (visible) color2 = block2->getMapColor();
                visible = block2->isPassable() || block2->isTransparent();
                if (visible) color1 = block1->getMapColor();
                visible = block1->isPassable() || block1->isTransparent();
                if (color1 || color2)
                {
                    MapBlock tmp = {(unsigned char)height,
                        ((color1 >> 24) & 0xFF) > ((color2 >> 24) & 0xFF)
                            ? color1
                            : color2};
                    blocksBuffer[256 - ++count] = tmp;
                }
            }
            int i = x * CHUNK_SIZE + y;
            pixels[i].blocks = new MapBlock[count];
            memcpy(pixels[i].blocks,
                blocksBuffer + 256 - count,
                sizeof(MapBlock) * count);
            pixels[i].len = (unsigned char)count;
        }
    }
}

ChunkMap::ChunkMap(Framework::StreamReader* zReader)
    : ReferenceCounter()
{
    zReader->lese((char*)&chunkCenter.x, 4);
    zReader->lese((char*)&chunkCenter.y, 4);
    pixels = new MapPixel[CHUNK_SIZE * CHUNK_SIZE];
    memset(pixels, 0, sizeof(MapPixel) * CHUNK_SIZE * CHUNK_SIZE);
    for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE; i++)
    {
        zReader->lese((char*)&pixels[i].len, 1);
        if (pixels[i].len > 0)
        {
            pixels[i].blocks = new MapBlock[pixels[i].len];
            zReader->lese(
                (char*)pixels[i].blocks, (int)sizeof(MapBlock) * pixels[i].len);
        }
    }
}

ChunkMap::~ChunkMap()
{
    for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE; i++)
    {
        delete[] pixels[i].blocks;
    }
    delete[] pixels;
}

bool ChunkMap::update(
    char x, char y, unsigned char height, int color1, int color2)
{
    cs.lock();
    int index = x * CHUNK_SIZE + y;
    bool found = 0;
    int resultColor
        = ((color1 >> 24) & 0xFF) > ((color2 >> 24) & 0xFF) ? color1 : color2;
    bool removed = !((resultColor >> 24) & 0xFF);
    bool changed = 0;
    for (int i = 0; i < pixels[index].len; i++)
    {
        if (pixels[index].blocks[i].height == height)
        {
            changed = pixels[index].blocks[i].color != resultColor;
            pixels[index].blocks[i].color = resultColor;
            found = 1;
        }
        else if (!found && pixels[index].blocks[i].height > height)
        {
            break;
        }
        if (found && removed && i < pixels[index].len - 1)
        {
            pixels[index].blocks[i] = pixels[index].blocks[i + 1];
        }
    }
    if (found && removed)
    {
        changed = 1;
        pixels[index].len--;
    }
    else if (!found && !removed)
    {
        MapBlock* blocks = new MapBlock[pixels[index].len + 1];
        bool added = 0;
        for (int i = 0; i < pixels[index].len; i++)
        {
            if (pixels[index].blocks[i].height < height)
            {
                blocks[i] = pixels[index].blocks[i];
            }
            else
            {
                if (!added)
                {
                    blocks[i] = {height, resultColor};
                    added = 1;
                }
                blocks[i + 1] = pixels[index].blocks[i];
            }
        }
        if (!added)
        {
            blocks[pixels[index].len] = {height, resultColor};
        }
        changed = 1;
        pixels[index].len++;
        delete[] pixels[index].blocks;
        pixels[index].blocks = blocks;
    }
    cs.unlock();
    return changed;
}

void ChunkMap::writeTo(Framework::StreamWriter* zWriter) const
{
    zWriter->schreibe((char*)&chunkCenter.x, 4);
    zWriter->schreibe((char*)&chunkCenter.y, 4);
    for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE; i++)
    {
        zWriter->schreibe((char*)&pixels[i].len, 1);
        if (pixels[i].len > 0)
        {
            zWriter->schreibe(
                (char*)pixels[i].blocks, (int)sizeof(MapBlock) * pixels[i].len);
        }
    }
}

Framework::Punkt ChunkMap::getChunkCenter() const
{
    return chunkCenter;
}