#include "map.h"

#include "../core/memory.h"
#include "../math/vec2i.h"

#include <cute_tiled.h>

BzTileMap BZ_TILEMAP_INVALID = {.isValid = false};

BzTileMap bzTileMapCreate(const BzTileMapDesc *desc) {
    BzTileMap map = {};
    i32 tilesetCount = desc->tilesetCount;
    if (tilesetCount == 0) {
        // Auto detect tileset count.
        for (i32 i = 0; i < BZ_MAX_MAP_TILESETS; i++) {
            if (!desc->tilesets[i].isValid)
                break;
            tilesetCount++;
        }
    }
    map.tilesetCount = tilesetCount;
    for (i32 i = 0; i < map.tilesetCount; i++) {
        map.tilesets[i] = desc->tilesets[i];
    }

    cute_tiled_map_t *cuteMap = cute_tiled_load_map_from_file(desc->path, NULL);

    map.backgroundColor = GetColor(cuteMap->backgroundcolor);

    map.width = cuteMap->width;
    map.height = cuteMap->height;
    map.tileWidth = cuteMap->tilewidth;
    map.tileHeight = cuteMap->tileheight;

    cute_tiled_layer_t *cuteLayer = cuteMap->layers;
    while (cuteLayer) {
        BZ_ASSERT(map.layerCount < BZ_MAX_MAP_LAYERS);

        BzTileLayer *layer = map.layers + map.layerCount;
        layer->id = cuteLayer->id;

        layer->dataCount = cuteLayer->data_count;
        layer->data = NULL;
        if (layer->dataCount > 0) {
            layer->data = bzAlloc(layer->dataCount * sizeof(*layer->data));
            layer->minData = layer->maxData = (i16) cuteLayer->data[0];
            for (i32 i = 0; i < layer->dataCount; i++) {
                layer->data[i] = (i16) cuteLayer->data[i];
                if (layer->data[i] < layer->minData)
                    layer->minData = layer->data[i];
                else if (layer->data[i] > layer->maxData)
                    layer->maxData = layer->data[i];
            }
        }

        layer->width = cuteLayer->width;
        layer->height = cuteLayer->height;

        layer->offsetX = cuteLayer->offsetx;
        layer->offsetY = cuteLayer->offsety;
        layer->opacity = cuteLayer->opacity;

        map.layerCount++;
        cuteLayer = cuteLayer->next;
    }

    cute_tiled_tileset_t *cuteTileset = cuteMap->tilesets;
    tilesetCount = 0;
    while ((cuteTileset = cuteTileset->next)) tilesetCount++;
    BZ_ASSERT(tilesetCount == map.tilesetCount);

    cuteTileset = cuteMap->tilesets;
    for (i32 i = 0; i < map.tilesetCount; i++) {
        BzTileset *tileset = map.tilesets + i;
        tileset->startID = cuteTileset->firstgid;
        cuteTileset = cuteTileset->next;
    }

    // Assign tilesets to layers
    for (i32 i = 0; i < map.layerCount; i++) {
        BzTileLayer *layer = map.layers + i;
        layer->tilesetIdx = -1;
        for (i32 j = map.tilesetCount - 1; j  >= 0; j--) {
            BzTileset *tileset = map.tilesets + j;

            if (tileset->startID >= layer->minData &&
                tileset->startID <= layer->maxData) {
                layer->tilesetIdx = j;
                break;
            }
        }
    }

    cute_tiled_free_map(cuteMap);

    map.isValid = true;
    return map;
}

static void drawLayer(BzTileLayer *layer, BzTileset *tileset) {
    if (!tileset) return;
    if (layer->minData == layer->maxData) return;
    Vector2 drawPos = {layer->offsetX, layer->offsetY};

    for (i32 y = 0; y < layer->height; y++) {
        for (i32 x = 0; x < layer->width; x++) {
            i16 tile = bzTileLayerGetTile(layer, x, y);
            if (tile - tileset->startID != -1) {
                Rectangle rec = bzTilesetGetTileRegion(tileset, tile);
                DrawTextureRec(tileset->tiles, rec, drawPos, WHITE);
            }
            drawPos.x += (float) tileset->tileWidth;
        }
        drawPos.x = layer->offsetX;
        drawPos.y += (float) tileset->tileHeight;
    }

}

void bzTileMapDraw(BzTileMap *map) {
    for (i32 i = 0; i < map->layerCount; i++) {
        BzTileLayer *layer = map->layers + i;
        BzTileset *tileset = NULL;
        if (layer->tilesetIdx != -1) {
            tileset = map->tilesets + layer->tilesetIdx;
        }
        drawLayer(map->layers + i, tileset);
    }
}

void bzTileMapDestroy(BzTileMap *tilemap) {
    for (i32 i = 0; i < tilemap->layerCount; i++) {
        BzTileLayer *layer = tilemap->layers + i;
        bzFree(layer->data);
        layer->data = NULL;
        layer->dataCount = 0;
    }
    *tilemap = BZ_TILEMAP_INVALID;
}


int16_t bzTileLayerGetTile(BzTileLayer *layer, i32 x, i32 y) {
    return layer->data[layer->width * y + x];
}
