#include "OrdererSpriteSet.hpp"
#include <algorithm>
#include <tuple>

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

void TSE::OrdererSpriteSetChunk::SetSprite(const Vector2 &p, const Vector2 &Spriteindex, const Vector2 &Normalindex, TileSet *set, float height, Vector2& scale)
{
    int normalid = -1;
    if(Normalindex != Vector2(-1,-1))
        normalid = set->GetSpriteIdAt(Normalindex.x, Normalindex.y);
    sprites[Vector3(p.x, p.y, height)] = {set->GetSpriteIdAt(Spriteindex.x, Spriteindex.y), normalid};
    spriteScales[Vector3(p.x, p.y, height)] = scale;
    dirtyPositions = true;
    dirtySpriteIds = true;
    dirtyScales = true;
}

void TSE::OrdererSpriteSetChunk::RemoveSprite(Vector2 p, float height)
{
    sprites.erase(Vector3(p.x, p.y, height));
}

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

const std::vector<TSE::Vector3> *TSE::OrdererSpriteSetChunk::GetOrderedPositions()
{
    if(dirtyPositions)
    {
        orderedPositions.clear();
        for(auto pos : sprites)
        {
            orderedPositions.push_back(pos.first);
        }
        switch (order)
        {
        case TopLeft:
            std::sort(orderedPositions.begin(), orderedPositions.end(), [](const Vector3& a, const Vector3& b)
            {
                return std::tie(a.y, a.x) > std::tie(b.y, b.x);
            });
            break;
        case TopRight:
            std::sort(orderedPositions.begin(), orderedPositions.end(), [](const Vector3& a, const Vector3& b)
            {
                if (a.y != b.y)
                    return a.y > b.y;
                return a.x > b.x;
            });
            break;
        case BottomLeft:
            std::sort(orderedPositions.begin(), orderedPositions.end(), [](const Vector3& a, const Vector3& b)
            {
                return std::tie(a.y, a.x) < std::tie(b.y, b.x);
            });
            break;
        case BottomRight:
            std::sort(orderedPositions.begin(), orderedPositions.end(), [](const Vector3& a, const Vector3& b)
            {
                if (a.y != b.y)
                    return a.y < b.y;
                return a.x > b.x;
            });
            break;
        }
        dirtyPositions = false;
    }
    return &orderedPositions;
}

const std::vector<TSE::Vector2i> *TSE::OrdererSpriteSetChunk::GetOrderedSpriteIds()
{
    if(dirtySpriteIds)
    {
        orderedSpriteIDs.clear();
        
        auto tmp = GetOrderedPositions();

        for(auto& pos : *tmp)
        {
            auto v = sprites.find(pos);
            if(v != sprites.end())
                orderedSpriteIDs.push_back(v->second);
            else
                orderedSpriteIDs.push_back({0,0});
        }

        dirtySpriteIds = false;
    }
    return &orderedSpriteIDs;
}

const std::vector<TSE::Vector2> *TSE::OrdererSpriteSetChunk::GetOrderedScales()
{
    if(dirtyScales)
    {
        orderedScales.clear();
        
        auto tmp = GetOrderedPositions();

        for(auto& pos : *tmp)
        {
            auto v = spriteScales.find(pos);
            if(v != spriteScales.end())
                orderedScales.push_back(v->second);
            else
                orderedScales.push_back({1,1});
        }

        dirtyScales = false;
    }
    return &orderedScales;
}

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

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

void TSE::OrdererSpriteSet::RemoveSprite(Vector2 p, float height)
{
    Vector2 chunkInnerPos = LocalToChunkPos(p);
    Vector2 chunkIndex = p - chunkInnerPos;
    if(chunks.contains(chunkIndex))
        chunks[chunkIndex].RemoveSprite(chunkInnerPos, height);
}

void TSE::OrdererSpriteSet::SetSprite(Vector2 p, Vector2 Spriteindex, float height, Vector2 scale, Vector2 Normalindex)
{
    Vector2 chunkInnerPos = LocalToChunkPos(p);
    Vector2 chunkIndex = p - chunkInnerPos;
    if(!chunks.contains(chunkIndex))
    {
        dirty = true;
        chunks[chunkIndex] = OrdererSpriteSetChunk(chunkSize, chunkIndex, order);
    }
    chunks[chunkIndex].SetSprite(chunkInnerPos, Spriteindex, Normalindex, set, height, scale);
}

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

const std::vector<TSE::Vector2> *TSE::OrdererSpriteSet::GetChunkPositionsInOrder()
{
    if(dirty)
    {
        orderedChunks.clear();

        for(auto pos : chunks)
        {
            orderedChunks.push_back(pos.first);
        }
        switch (order)
        {
        case TopLeft:
            std::sort(orderedChunks.begin(), orderedChunks.end(), [](const Vector2& a, const Vector2& b)
            {
                return std::tie(a.y, a.x) > std::tie(b.y, b.x);
            });
            break;
        case TopRight:
            std::sort(orderedChunks.begin(), orderedChunks.end(), [](const Vector2& a, const Vector2& b)
            {
                if (a.y != b.y)
                    return a.y > b.y;
                return a.x > b.x;
            });
            break;
        case BottomLeft:
            std::sort(orderedChunks.begin(), orderedChunks.end(), [](const Vector2& a, const Vector2& b)
            {
                return std::tie(a.y, a.x) < std::tie(b.y, b.x);
            });
            break;
        case BottomRight:
            std::sort(orderedChunks.begin(), orderedChunks.end(), [](const Vector2& a, const Vector2& b)
            {
                if (a.y != b.y)
                    return a.y < b.y;
                return a.x > b.x;
            });
            break;
        }

        dirty = false;
    }
    return &orderedChunks;
}

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

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

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

TSE::Vector2 TSE::OrdererSpriteSet::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;
}
