#include "TileMap.hpp"

TSE::TileMapChunk::TileMapChunk(int _chunksize, const Vector2 &_pos, SortingOrder _order)
{
    chunksize = _chunksize;
    pos = _pos;
    order = _order;
}

void TSE::TileMapChunk::SetTile(const Vector2& p, const Vector2& Spriteindex, const Vector2& Normalindex, TileSet* set)
{
    int normalid = -1;
    if(Normalindex != Vector2(-1,-1))
        normalid = set->GetSpriteIdAt(Normalindex.x, Normalindex.y);
    sprites[p] = {set->GetSpriteIdAt(Spriteindex.x, Spriteindex.y), normalid};
    dirtyPositions = true;
    dirtySpriteIds = true;
}

void TSE::TileMapChunk::RemoveTile(Vector2 p)
{
    sprites.erase(p);
}

void TSE::TileMapChunk::SetOrdering(SortingOrder _order)
{
    order = _order;
    dirtyPositions = true;
    dirtySpriteIds = true;
}

const std::vector<TSE::Vector2>* TSE::TileMapChunk::GetOrderedPositions()
{
    if(dirtyPositions)
    {
        orderedPositions.clear();
        switch (order)
        {
        case TopLeft:
            for (int y = 0; y < chunksize; y++)
            {
                Vector2 offset = nextLine * y;
                for (int x = 0; x < chunksize; x++)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                    orderedPositions.push_back(v->first * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * y, nextLine.y * 0.5f * x));
                }
            }
            break;
        case TopRight:
            for (int y = 0; y < chunksize; y++)
            {
                Vector2 offset = nextLine * y;
                for (int x = chunksize - 1; x >= 0; x--)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                    orderedPositions.push_back(v->first * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * y, nextLine.y * 0.5f * x));
                }
            }
            break;
        case BottomLeft:
            for (int y = chunksize - 1; y >= 0; y--)
            {
                Vector2 offset = nextLine * y;
                for (int x = 0; x < chunksize; x++)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                    orderedPositions.push_back(v->first * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * y, nextLine.y * 0.5f * x));
                }
            }
            break;
        case BottomRight:
            for (int y = chunksize - 1; y >= 0; y--)
            {
                Vector2 offset = nextLine * y;
                for (int x = chunksize - 1; x >= 0; x--)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                    orderedPositions.push_back(v->first * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * y, nextLine.y * 0.5f * x));
                }
            }
            break;
        }
        dirtyPositions = false;
    }
    return &orderedPositions;
}

const std::vector<TSE::Vector2i>* TSE::TileMapChunk::GetOrderedSpriteIds()
{
    if(dirtySpriteIds)
    {
        orderedSpriteIDs.clear();
        switch (order)
        {
        case TopLeft:
            for (int y = 0; y < chunksize; y++)
            {
                for (int x = 0; x < chunksize; x++)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                        orderedSpriteIDs.push_back(v->second);
                }
            }
            break;
        case TopRight:
            for (int y = 0; y < chunksize; y++)
            {
                for (int x = chunksize - 1; x >= 0; x--)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                        orderedSpriteIDs.push_back(v->second);
                }
            }
            break;
        case BottomLeft:
            for (int y = chunksize - 1; y >= 0; y--)
            {
                for (int x = 0; x < chunksize; x++)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                        orderedSpriteIDs.push_back(v->second);
                }
            }
            break;
        case BottomRight:
            for (int y = chunksize - 1; y >= 0; y--)
            {
                for (int x = chunksize - 1; x >= 0; x--)
                {
                    Vector2 p(x,y);
                    auto v = sprites.find(p);
                    if(v != sprites.end())
                        orderedSpriteIDs.push_back(v->second);
                }
            }
            break;
        }
        dirtySpriteIds = false;
    }
    return &orderedSpriteIDs;
}

int TSE::TileMapChunk::GetChunksize()
{
    return chunksize;
}

int TSE::TileMapChunk::GetSpriteCount()
{
    return sprites.size();
}

void TSE::TileMap::RemoveTile(Vector2 p)
{
    Vector2 chunkInnerPos = LocalToChunkPos(p);
    Vector2 chunkIndex = p - chunkInnerPos;
    if(chunks.contains(chunkIndex))
        chunks[chunkIndex].RemoveTile(chunkInnerPos);
}

void TSE::TileMap::SetTile(Vector2 p, Vector2 Spriteindex, Vector2 Normalindex)
{
    Vector2 chunkInnerPos = LocalToChunkPos(p);
    Vector2 chunkIndex = p - chunkInnerPos;
    if(!chunks.contains(chunkIndex))
    {
        dirty = true;
        chunks[chunkIndex] = TileMapChunk(chunkSize, chunkIndex, order);
        chunks[chunkIndex].nextLine = nextLine;
        CheckBounds(chunkIndex);
    }
    chunks[chunkIndex].SetTile(chunkInnerPos, Spriteindex, Normalindex, set);
}

TSE::TileMapChunk* TSE::TileMap::GetChunk(const Vector2 &pos)
{
    auto chunk = chunks.find(pos);
    if(chunk == chunks.end())
        return nullptr;
    return &chunks[pos];
}

const std::vector<TSE::Vector2>* TSE::TileMap::GetChunkPositionsInOrder()
{
    if(dirty)
    {
        orderedChunks.clear();
        switch (order)
        {
        case TopLeft:
            for (int y = bounds.p1.y; y < bounds.p2.y + 1; y++)
            {
                for (int x = bounds.p1.x; x < bounds.p2.x + 1; x++)
                {
                    Vector2 p(x,y);
                    auto v = chunks.find(p);
                    if(v != chunks.end())
                        orderedChunks.push_back(v->first);
                }
            }
            break;
        case TopRight:
            for (int y = bounds.p1.y; y < bounds.p2.y + 1; y++)
            {
                for (int x = bounds.p2.x; x > bounds.p1.x - 1; x--)
                {
                    Vector2 p(x,y);
                    auto v = chunks.find(p);
                    if(v != chunks.end())
                        orderedChunks.push_back(v->first);
                }
            }
            break;
        case BottomLeft:
            for (int y = bounds.p2.y; y > bounds.p1.y - 1; y--)
            {
                for (int x = bounds.p1.x; x < bounds.p2.x + 1; x++)
                {
                    Vector2 p(x,y);
                    auto v = chunks.find(p);
                    if(v != chunks.end())
                        orderedChunks.push_back(v->first);
                }
            }
            break;
        case BottomRight:
            for (int y = bounds.p2.y; y > bounds.p1.y - 1; y--)
            {
                for (int x = bounds.p2.x; x > bounds.p1.x - 1; x--)
                {
                    Vector2 p(x,y);
                    auto v = chunks.find(p);
                    if(v != chunks.end())
                        orderedChunks.push_back(v->first);
                }
            }
            break;
        }
        dirty = false;
    }
    return &orderedChunks;
}

int TSE::TileMap::GetChunkCount()
{
    return chunks.size();
}

TSE::TileSet *TSE::TileMap::GetTileSet()
{
    return set;
}

void TSE::TileMap::SetNextLineOffset(const Vector2 &offset)
{
    nextLine = offset;
    for(auto& [_, chunk] : chunks)
    {
        chunk.nextLine = offset;
    }
    DirtyAll();
}

TSE::Vector2 TSE::TileMap::GetNextLineOffset()
{
    return nextLine;
}

void TSE::TileMap::DirtyAll()
{
    dirty = true;
    for(auto& chunk : chunks)
    {
        chunk.second.dirtyPositions = true;
        chunk.second.dirtySpriteIds = true;
    }
}

void TSE::TileMap::CheckBounds(Vector2 pos)
{
    if(pos.x > bounds.p2.x)
        bounds.p2.x = pos.x;
    if(pos.y > bounds.p2.y)
        bounds.p2.y = pos.y;
    if(pos.x < bounds.p1.x)
        bounds.p1.x = pos.x;
    if(pos.y < bounds.p1.y)
        bounds.p1.y = pos.y;
}

TSE::Vector2 TSE::TileMap::LocalToChunkPos(const Vector2 &v)
{
    Vector2 p = Vector2((int)v.x % chunkSize, (int)v.y % chunkSize);
    if(p.x < 0) p.x += chunkSize;
    if(p.y < 0) p.y += chunkSize;
    return p;
}

TSE::Vector2 TSE::TileMap::ChunkToLocalPos(const Vector2 &v, const TileMapChunk &chunk)
{
    return v + chunk.pos * chunkSize;
}

TSE::Vector2 TSE::TileMap::RealPosToTileMapPos(const Vector2 &v)
{
    return v * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * v.y, nextLine.y * 0.5f * v.x);
}

TSE::Vector2 TSE::TileMap::TileMapToRealPos(const Vector2 &v)
{
    return v * Vector2(nextLine.x, nextLine.y * 0.5f) + Vector2(-nextLine.x * v.y, nextLine.y * 0.5f * v.x);
}
