#include "DimensionMap.h"

#include "Constants.h"
#include "World.h"

DimensionMap::DimensionMap()
    : ZeichnungHintergrund(),
      originChunkCenter(0, 0),
      scrollOffset(0, 0),
      chunkCount(0),
      pixelsPerBlock(16),
      maxHeight(255),
      waitingForChunk(0),
      drag(0)
{
    setStyle(Style::Sichtbar | Style::Erlaubt);
    chunks = new Framework::Trie<ChunkMap>();
    setMausEreignis(_ret1ME);
    requestNextChunk();
}

DimensionMap::~DimensionMap()
{
    chunks->release();
}

void DimensionMap::getAddrOf(Punkt cPos, char* addr) const
{
    *(int*)addr = cPos.x;
    *((int*)addr + 1) = cPos.y;
}

void DimensionMap::getAddrOfWorld(Punkt wPos, char* addr) const
{
    // needed because otherwise would (-8, -8) have the same
    // adress as (8, 8)
    if (wPos.x < 0) wPos.x -= CHUNK_SIZE;
    if (wPos.y < 0) wPos.y -= CHUNK_SIZE;

    wPos /= CHUNK_SIZE;
    getAddrOf(wPos, addr);
}

Framework::Punkt DimensionMap::getMinVisibleChunkCenter(
    Framework::Punkt& screenPos) const
{
    screenPos = getSize() / 2 - scrollOffset;
    Punkt currentChunkCenter = originChunkCenter;
    while (screenPos.x + pixelsPerBlock * (CHUNK_SIZE / 2) >= 0)
    {
        screenPos.x -= pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.x -= CHUNK_SIZE;
    }
    while (screenPos.y + pixelsPerBlock * (CHUNK_SIZE / 2) >= 0)
    {
        screenPos.y -= pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.y -= CHUNK_SIZE;
    }
    while (screenPos.x + pixelsPerBlock * (CHUNK_SIZE / 2) < 0)
    {
        screenPos.x += pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.x += CHUNK_SIZE;
    }
    while (screenPos.y + pixelsPerBlock * (CHUNK_SIZE / 2) < 0)
    {
        screenPos.y += pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.y += CHUNK_SIZE;
    }
    return currentChunkCenter;
}

Framework::Punkt DimensionMap::getMaxVisibleChunkCenter(
    Framework::Punkt& screenPos) const
{
    screenPos = getSize() / 2 - scrollOffset;
    Punkt currentChunkCenter = originChunkCenter;
    while (screenPos.x - pixelsPerBlock * (CHUNK_SIZE / 2) < getBreite())
    {
        screenPos.x += pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.x += CHUNK_SIZE;
    }
    while (screenPos.y - pixelsPerBlock * (CHUNK_SIZE / 2) < getHeight())
    {
        screenPos.y += pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.y += CHUNK_SIZE;
    }
    while (screenPos.x - pixelsPerBlock * (CHUNK_SIZE / 2) >= getBreite())
    {
        screenPos.x -= pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.x -= CHUNK_SIZE;
    }
    while (screenPos.y - pixelsPerBlock * (CHUNK_SIZE / 2) >= getHeight())
    {
        screenPos.y -= pixelsPerBlock * CHUNK_SIZE;
        currentChunkCenter.y -= CHUNK_SIZE;
    }
    return currentChunkCenter;
}

bool DimensionMap::tick(double time)
{
    if (lastSize != getSize())
    {
        lastSize = getSize();
        requestNextChunk();
    }
    return ZeichnungHintergrund::tick(time);
}

void DimensionMap::requestNextChunk()
{
    cs.lock();
    if (waitingForChunk)
    {
        cs.unlock();
        return;
    }
    if (chunkCount == 0)
    {
        waitingForChunk = 1;
        Vec3<float> playerPos
            = World::INSTANCE->getCurrentPlayerEntity()->getPos();
        char msg[9];
        msg[0] = 2;
        *(int*)(msg + 1) = (int)playerPos.x;
        *(int*)(msg + 5) = (int)playerPos.y;
        World::INSTANCE->zClient()->dimensionAPIRequest(msg, 9);
    }
    else
    {
        Punkt minScreenPos;
        Punkt minVisibleChunk = getMinVisibleChunkCenter(minScreenPos);
        Punkt maxScreenPos;
        Punkt maxVisibleChunk = getMaxVisibleChunkCenter(maxScreenPos);
        Punkt screenPos = minScreenPos;
        Punkt screenCenter = getSize() / 2;
        double minDist = -1;
        Punkt resultChunk(0, 0);
        char addr[8];
        for (int x = minVisibleChunk.x; x <= maxVisibleChunk.x; x += CHUNK_SIZE)
        {
            for (int y = minVisibleChunk.y; y <= maxVisibleChunk.y;
                 y += CHUNK_SIZE)
            {
                getAddrOfWorld({x, y}, addr);
                if (!chunks->z(addr, 8))
                {
                    if (minDist < 0
                        || (screenCenter - screenPos).getLengthSq() < minDist)
                    {
                        minDist = (screenCenter - screenPos).getLengthSq();
                        resultChunk = {x, y};
                    }
                }
                screenPos.y += pixelsPerBlock * CHUNK_SIZE;
            }
            screenPos.x += pixelsPerBlock * CHUNK_SIZE;
            screenPos.y = minScreenPos.y;
        }
        if (minDist >= 0)
        {
            waitingForChunk = 1;
            char msg[9];
            msg[0] = 2;
            *(int*)(msg + 1) = (int)resultChunk.x;
            *(int*)(msg + 5) = (int)resultChunk.y;
            World::INSTANCE->zClient()->dimensionAPIRequest(msg, 9);
        }
    }
    cs.unlock();
}

void DimensionMap::addChunk(ChunkMap* chunk)
{
    cs.lock();
    if (chunkCount == 0) originChunkCenter = chunk->getChunkCenter();
    char addr[8];
    getAddrOfWorld(chunk->getChunkCenter(), addr);
    chunks->set(addr, 8, chunk);
    chunkCount++;
    waitingForChunk = 0;
    cs.unlock();
    requestNextChunk();
}

void DimensionMap::render(Framework::Bild& rObj)
{
    ZeichnungHintergrund::render(rObj);
    if (!rObj.setDrawOptions(innenPosition, innenSize)) return;
    cs.lock();
    Punkt minScreenPos;
    Punkt minVisibleChunk = getMinVisibleChunkCenter(minScreenPos);
    Punkt maxScreenPos;
    Punkt maxVisibleChunk = getMaxVisibleChunkCenter(maxScreenPos);
    char addr[8];
    Punkt screenPos = minScreenPos;
    for (int x = minVisibleChunk.x; x <= maxVisibleChunk.x; x += CHUNK_SIZE)
    {
        for (int y = minVisibleChunk.y; y <= maxVisibleChunk.y; y += CHUNK_SIZE)
        {
            getAddrOfWorld({x, y}, addr);
            ChunkMap* map = chunks->z(addr, 8);
            if (map)
            {
                map->setMaxHeight((unsigned char)maxHeight);
                Punkt topLeft(screenPos.x - (pixelsPerBlock * CHUNK_SIZE) / 2,
                    screenPos.y - (pixelsPerBlock * CHUNK_SIZE) / 2);
                rObj.drawBildSkall(topLeft.x,
                    topLeft.y,
                    pixelsPerBlock * CHUNK_SIZE,
                    pixelsPerBlock * CHUNK_SIZE,
                    map->getRenderedImage());
                unsigned char* heightMap = map->getHeightMap();
                for (int xx = 0; xx < CHUNK_SIZE - 1; xx++)
                {
                    for (int yy = 0; yy < CHUNK_SIZE - 1; yy++)
                    {
                        if (heightMap[yy * CHUNK_SIZE + xx]
                    > heightMap[yy * CHUNK_SIZE + xx + 1]
                            )
                        {
                            rObj.drawLinieVAlpha((xx * pixelsPerBlock)
                                                     + topLeft.x
                                                     + pixelsPerBlock,
                                (yy * pixelsPerBlock) + topLeft.y,
                                pixelsPerBlock,
                                0xFF000000);
                        }
                        if (heightMap[yy * CHUNK_SIZE + xx]
                > heightMap[(yy + 1) * CHUNK_SIZE + xx])
                        {
                            rObj.drawLinieHAlpha(
                                (xx * pixelsPerBlock) + topLeft.x,
                                (yy * pixelsPerBlock) + topLeft.y
                                    + pixelsPerBlock,
                                pixelsPerBlock,
                                0x40000000);
                        }
                    }
                }
                rObj.drawLinieHAlpha(topLeft.x,
                    topLeft.y,
                    pixelsPerBlock * CHUNK_SIZE,
                    0x50FFFFFF);
                rObj.drawLinieVAlpha(topLeft.x,
                    topLeft.y,
                    pixelsPerBlock * CHUNK_SIZE,
                    0x50FFFFFF);
            }
            screenPos.y += pixelsPerBlock * CHUNK_SIZE;
        }
        screenPos.x += pixelsPerBlock * CHUNK_SIZE;
        screenPos.y = minScreenPos.y;
    }
    cs.unlock();
    rObj.releaseDrawOptions();
}

void DimensionMap::doMausEreignis(Framework::MausEreignis& me, bool userRet)
{
    if (me.id == ME_PLinks)
    {
        drag = 1;
        lastMouse = {me.mx, me.my};
    }
    if (me.id == ME_RLinks || me.id == ME_Leaves) drag = 0;
    if (me.id == ME_Bewegung && drag)
    {
        scrollOffset -= Punkt(me.mx, me.my) - lastMouse;
        lastMouse = Punkt(me.mx, me.my);
        rend = 1;
        requestNextChunk();
    }
    if (me.id == ME_DScroll && pixelsPerBlock > 1)
    {
        scrollOffset = (scrollOffset / pixelsPerBlock) * (pixelsPerBlock - 1);
        pixelsPerBlock--;
        rend = 1;
        requestNextChunk();
    }
    if (me.id == ME_UScroll)
    {
        scrollOffset = (scrollOffset / pixelsPerBlock) * (pixelsPerBlock + 1);
        pixelsPerBlock++;
        rend = 1;
        requestNextChunk();
    }
    if (me.id == ME_RRechts)
    {
        if (maxHeight != 255)
        {
            maxHeight = 255;
        }
        else
        {
            maxHeight
                = (int)(World::INSTANCE->getCurrentPlayerEntity()->getPos().z
                        / 2);
        }
        rend = 1;
    }
    ZeichnungHintergrund::doMausEreignis(me, userRet);
}