#include "Map.h"

// Determine when a map redraw should happen.
u8 mapRedraw = TRUE;

// The map grid.
u8 mapCells[MAP_CELL_COUNT];

// 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;

// The cell type used by default in the map grid and when clearing cells.
u8 defaultEnvironmentCell = 0;

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

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

    // Decompress the map into the map grid.
    cpct_zx7b_decrunch_s(destinationEnd, sourceEnd);
#else
    memcpy(mapCells, level_map_uncompressed[levelNumber], MAP_CELL_COUNT);
#endif

    // Set the default environment cell that will be used for cells in the grid (used when setting cells to empty).
    defaultEnvironmentCell = level_default_environment_cell[currentMap];

    // 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 += SCREEN_TILES_Y;
        }

        mapRedraw = FALSE;
    }
}

void drawCell(u8 x, u8 y, u8 cell)
{
#ifdef CPC

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

    cpct_drawSprite(graphic, mapMemory, TILE_WIDTH_BYTES, TILE_HEIGHT);

#else

    u16 targetX = x * 16 * scaleFactor;
    u16 targetY = y * 16 * scaleFactor;

    ALLEGRO_BITMAP* bitmap = allegroBitmapFromCell(cell);

    if (bitmap)
    {
#ifdef ALLEGRO4
        // No scaling.
        //draw_sprite(screen, bitmap, targetX, targetY);

        // With scaling
        stretch_sprite(screen, bitmap, targetX, targetY, 16 * scaleFactor, 16 * scaleFactor);
#else
        al_draw_scaled_bitmap(bitmap, 0, 0, TILE_WIDTH_BYTES * 2, TILE_HEIGHT, targetX, targetY, 16 * scaleFactor, 16 * scaleFactor, 0);
#endif
    }

#endif
}

const u8* graphicFromCell(u8 cell)
{
    u8 cellType = cell & CELL_GFX_RANGE;

    if (cell && cell < CELL_ITEM_FLAG)
    {
        // Draw tile (-1 because the spriteset will start at 0 index but the type will start at 1).
        return tileSprites[cellType - 1];
    }
    else if (cell & CELL_ITEM_FLAG)
    {
        // Draw item (-1 because the spriteset will start at 0 index but the type will start at 1).
        return itemSprites[cellType - 1];
    }
    else if (cell & CELL_INTERACTABLE_FLAG)
    {
        // Draw interactable (-1 because the spriteset will start at 0 index but the type will start at 1).
        return interactableSprites[cellType - 1];
    }
    else if (cell & CELL_ENVIRONMENT_FLAG)
    {
        // Draw environment cell (-1 because the spriteset will start at 0 index but the type will start at 1).
        return environmentSprites[cellType - 1];
    }
    else
    {
        // This is the default sprite for the ground.
        return environmentSprites[ENVIRONMENT_SPRITE_COUNT - 1];
    }
}

#ifndef CPC
ALLEGRO_BITMAP* allegroBitmapFromCell(u8 cell)
{
    // The cell type without the flag.
    u8 cellType = cell & CELL_GFX_RANGE;

    if (cell && cell < CELL_ITEM_FLAG)
    {
        // Draw tile (-1 because the spriteset will start at 0 index but the type will start at 1).
        return allegroTileSprites[cellType - 1];
    }
    else if (cell & CELL_ITEM_FLAG)
    {
        return allegroItemSprites[cellType - 1];
    }
    else if (cell & CELL_INTERACTABLE_FLAG)
    {
        // Return interactable (-1 because the graphicset will start at 0 index but the type will start at 1).
        return allegroInteractableSprites[cellType - 1];
    }
    else if (cell & CELL_ENVIRONMENT_FLAG)
    {
        // Draw environment cell (-1 because the spriteset will start at 0 index but the type will start at 1).
        return allegroEnvironmentSprites[cellType - 1];
    }
    else
    {
        // This is the default sprite for the ground.
        return allegroEnvironmentSprites[ENVIRONMENT_SPRITE_COUNT - 1];
    }
}
#endif

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)];
}

#ifdef CPC
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;

    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 < SCREEN_BOTTOM_TILE_Y ? 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));
    }
}
#endif

void drawCellAtPosition(u8 x, u8 y)
{
    u8 cell = mapCell(x, y, currentRoomX, currentRoomY);

    drawCell(x, y, cell);
}

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

    // We will assume it is in the current room and if not it will draw something else.
    drawCellAtPosition(x, y);
}

u8 cellContainsType(u8 x, u8 y, u8 type)
{
    // Retrieve the cell in the current room.
    u8 cell = mapCell(x, y, currentRoomX, currentRoomY);

    // Check if the cell is a particular type of cell.
    if (cell == type)
    {
        return TRUE;
    }

    return FALSE;
}
