#include "Map.h"

u8 mapCells[MAP_CELL_COUNT];
Enemy mapEnemies[MAX_ENEMY_COUNT];
u8 mapEnemyCount;

// Redraw variables.
u8 mapRedraw = TRUE;
u8 enemyRedrawAll = FALSE;

u8 currentMap = 0;

// The objective applied to the current map.
u8 mapObjective = 0;

// The number of objective actions remaining to complete the level.
u8 objectiveActionCount = 0;

// The number of pipes the player is carrying when objective type is collect and place pipes.
u8 pipeCount = 0;

// The back buffer where tiles and actors are combined before drawing.
u8 backbuffer[BACKBUFFER_SIZE];

// The width of the back buffer where tiles and actors are combined before drawing.
u8 backbufferWidth = BACKBUFFER_WIDTH;

void initMap()
{
    // The end of the source level array (arraypointer + size - 1) as a u16 pointer.
    void* sourceEnd = level_map[currentMap] + level_map_size[currentMap] - 1;

    // The end of the destination array (arraypointer + size - 1) as a u16 pointer.
    void* destinationEnd = mapCells + MAP_CELL_COUNT - 1;

    cpct_zx7b_decrunch_s(destinationEnd, sourceEnd);

    // Loading enemies from the map.
    // TODO get rid of the need for /3.
    mapEnemyCount = (level_map_enemies_size[currentMap] / 3);

    // Only load them into the array if there are enemies in the map.
    if (mapEnemyCount > 0)
    {
        // Ensure the enemies are cleared before they are initialised.
        cpct_memset(mapEnemies, 0, MAX_ENEMY_COUNT * sizeof(Enemy));

        initEnemies(level_map_enemies[currentMap], level_map_enemies_size[currentMap]);
    }

    // Load objective information from the level.
    mapObjective = level_objective_type[currentMap];
    objectiveActionCount = level_objective_action_count[currentMap];
    pipeCount = 0;

    // Enable map redraw.
    mapRedraw = TRUE;
}

void drawMap()
{
    if (mapRedraw)
    {
        // Draw the content of the map grid.
        u8 x;
        u8 y;
        u8 cell;

        // Position is determined by how much the screen has scrolled to show the current room.
        u16 position = mapCellPosition(0, 0, currentRoomX, currentRoomY);
        u8* cellPointer = mapCells + position;

        // Position is the element in the map tile array.
        for (x = 0; x < SCREEN_TILES_X; ++x)
        {
            for (y = 0; y < SCREEN_TILES_Y; ++y)
            {
                cell = *cellPointer;

                drawCell(x, y, cell);

                ++cellPointer;
            }

            // Move to the start of the next column (half the column height in a 2x2 screen map), 2/3 of the column height in a 3x3 map.
            cellPointer += 24;
        }

        mapRedraw = FALSE;

        // Set all enemies to redraw after map has been redrawn.
        markEnemiesForRedraw();
    }
}

u8* graphicFromCellPosition(u8 cellX, u8 cellY)
{
    return graphicFromCell(MAPA(cellX, cellY));
}

u8* graphicFromCell(u8 cell)
{
    if (cell & CELL_ITEM_FLAG)
    {
        // Draw item (-1 because the graphicset will start at 0 index but the type will start at 1).
        return itemGraphics[(cell & CELL_GFX_RANGE) - 1];
    }
    else if (cell & CELL_INTERACTABLE_FLAG)
    {
        // Draw interactable (-1 because the graphicset will start at 0 index but the type will start at 1).
        return interactableGraphics[(cell & CELL_GFX_RANGE) - 1];
    }
    else if (cell & CELL_WATER_FLAG)
    {
        // Draw water.
        return waterGraphics[(cell & CELL_GFX_RANGE)];
    }
    else if (cell != CELL_EMPTY)
    {
        // Draw tile (-1 because the graphicset will start at 0 index but the type will start at 1).
        return tileGraphics[(cell & CELL_GFX_RANGE) - 1];
    }
    else
    {
        // Draw G_tile_cave_wall for empty cells.
        return G_tile_cave_wall;
    }
}

void drawCell(u8 x, u8 y, u8 cell)
{
    u8* mapMemory = cpct_getScreenPtr(CPCT_VMEM_START, xPositions[x], yPositions[y]);
    u8* graphic = graphicFromCell(cell);

    cpct_drawSprite(graphic, mapMemory, TILE_WIDTH_BYTES, TILE_HEIGHT);
}

void drawCellAtPosition(u8 x, u8 y)
{
    u8 cell = MAPA(x, y);

    drawCell(x, y, cell);
}

void drawCellAtPositionSafe(u8 x, u8 y)
{
    if (x >= 0 && x < SCREEN_TILES_X && y >= 0 && y < SCREEN_TILES_Y)
    {
        u8 cell = MAPA(x, y);

        drawCell(x, y, cell);
    }
}

u16 mapCellPosition(u8 x, u8 y, u8 roomX, u8 roomY)
{
    return (x + (SCREEN_TILES_X * roomX)) * MAP_TILES_Y + (y + (SCREEN_TILES_Y * roomY));
}

u8 mapCell(u8 x, u8 y, u8 roomX, u8 roomY)
{
    return mapCells[mapCellPosition(x, y, roomX, roomY)];
}

void updateMapCell(u8 x, u8 y, u8 roomX, u8 roomY, u8 cell)
{
    mapCells[mapCellPosition(x, y, roomX, roomY)] = cell;
}

void addSpriteToBackbuffer(u8 x, u8 y, u8* sprite)
{
    // Determine the position within the backbuffer that the new sprite should be added to.
    u8 position = y * backbufferWidth + x;
    u8* mapMemory = backbuffer + position;

    // Draw the specified sprite on the back buffer as pointed to by mapMemory.
    cpct_drawToSpriteBuffer(backbufferWidth, mapMemory, TILE_WIDTH_BYTES, TILE_HEIGHT, sprite);
}

void drawBackbuffer(u8 x, u8 y)
{
    u8* memory = cpct_getScreenPtr(CPCT_VMEM_START, xPositions[x], yPositions[y]);

    // When at the bottom of the screen we only draw half the backbuffer height.
    u8 height = y < 11 ? BACKBUFFER_HEIGHT : TILE_HEIGHT;

    cpct_drawSprite(backbuffer, memory, backbufferWidth, height);
}

void populateBackbufferFromMap(u8 cellX, u8 cellY)
{
    // Get the array position from mapCells for (x,y).
    u16 cellPosition = mapCellPosition(cellX, cellY, currentRoomX, currentRoomY);
    u8* cell = mapCells + cellPosition;

    // When cellX is 19 the backbuffer is 1x2.
    addSpriteToBackbuffer(0, 0, graphicFromCell(*cell));

    // Move cell to (x,y+1).
    ++cell;
    addSpriteToBackbuffer(0, TILE_HEIGHT, graphicFromCell(*cell));

    if (cellX != 19)
    {
        // Normally backbuffer is 2x2.
        // Move cell to (x+1,y+1).
        cell += MAP_TILES_Y;
        addSpriteToBackbuffer(TILE_WIDTH_BYTES, TILE_HEIGHT, graphicFromCell(*cell));

        // Move cell to (x+1,y).
        --cell;
        addSpriteToBackbuffer(TILE_WIDTH_BYTES, 0, graphicFromCell(*cell));
    }
}

u8 cellContainsSpike(u8 x, u8 y)
{
    // Retrieve the interactable and graphics bits.
    // 95 = CELL_INTERACTABLE_FLAG + CELL_GFX_RANGE
    u8 cell = MAPA(x, y) & 95;

    // Check if the cell is a spike.
    // INTERACTALBE = 64 + (14->18) TYPE = 78->82
    if (cell >= 78 && cell <= 82)
    {
        return TRUE;
    }

    return FALSE;
}

void markEnemiesForRedraw()
{
    Enemy* enemy = mapEnemies;
    u8 i = 0;

    for (i = 0; i < mapEnemyCount; ++i)
    {
        // Only mark for redraw enemies in the current room.
        if (enemy->object.position.room == currentRoom)
        {
            enemy->redraw = TRUE;
        }

        ++enemy;
    }
}