#include "game.h"
#include <string.h>
#include <stdio.h>

#include "levels/level_01_data.h"
#include "levels/level_02_data.h"
#include "levels/level_03_data.h"
#include "levels/level_04_data.h"
#include "levels/level_05_data.h"
#include "levels/level_06_data.h"
#include "levels/level_07_data.h"
#include "levels/level_08_data.h"
#include "levels/level_09_data.h"
#include "levels/level_special1_data.h"
#include "levels/level_10_data.h"
#include "levels/level_11_data.h"
#include "levels/level_12_data.h"
#include "levels/level_13_data.h"
#include "levels/level_14_data.h"
#include "levels/level_15_data.h"
#include "levels/level_16_data.h"
#include "levels/level_17_data.h"
#include "levels/level_18_data.h"
#include "levels/level_19_data.h"
#include "levels/level_20_data.h"
#include "levels/level_21_data.h"
#include "levels/level_22_data.h"
#include "levels/level_23_data.h"
#include "levels/level_24_data.h"
#include "levels/level_25_data.h"
#include "levels/level_26_data.h"
#include "levels/level_27_data.h"
#include "levels/level_28_data.h"
#include "levels/level_29_data.h"
#include "levels/level_30_data.h"
#include "levels/level_31_data.h"
#include "levels/level_32_data.h"
#include "levels/level_33_data.h"
#include "levels/level_34_data.h"
#include "levels/level_35_data.h"
#include "levels/level_36_data.h"
#include "levels/level_37_data.h"
#include "levels/level_38_data.h"
#include "levels/level_39_data.h"
#include "levels/level_40_data.h"
#include "levels/level_41_data.h"
#include "levels/level_42_data.h"
#include "levels/level_43_data.h"
#include "levels/level_44_data.h"
#include "levels/level_45_data.h"
#include "levels/level_46_data.h"
#include "levels/level_47_data.h"
#include "levels/level_48_data.h"
#include "levels/level_49_data.h"
#include "levels/level_50_data.h"
#include "levels/level_51_data.h"
#include "levels/level_52_data.h"
#include "levels/level_53_data.h"
#include "levels/level_special2_data.h"
#include "levels/level_54_data.h"
#include "levels/level_55_data.h"
#include "levels/level_56_data.h"
#include "levels/level_57_data.h"
#include "levels/level_58_data.h"
#include "levels/level_59_data.h"
#include "levels/level_60_data.h"
#include "levels/level_61_data.h"
#include "levels/level_62_data.h"
#include "levels/level_63_data.h"
#include "levels/level_64_data.h"
#include "levels/level_65_data.h"
#include "levels/level_66_data.h"
#include "levels/level_67_data.h"
#include "levels/level_68_data.h"
#include "levels/level_69_data.h"
#include "levels/level_70_data.h"

// Defining an array of level data pointers stored in EWRAM
const char* const* levels[] __attribute__((section(".ewram"))) = {
    level_01_data, level_02_data, level_03_data, level_04_data, level_05_data,
    level_06_data, level_07_data, level_08_data, level_09_data, level_special1_data,
    level_10_data, level_11_data, level_12_data, level_13_data, level_14_data,
    level_15_data, level_16_data, level_17_data, level_18_data, level_19_data,
    level_20_data, level_21_data, level_22_data, level_23_data, level_24_data,
    level_25_data, level_26_data, level_27_data, level_28_data, level_29_data,
    level_30_data, level_31_data, level_32_data, level_33_data, level_34_data,
    level_35_data, level_36_data, level_37_data, level_38_data, level_39_data,
    level_40_data, level_41_data, level_42_data, level_43_data, level_44_data,
    level_45_data, level_46_data, level_47_data, level_48_data, level_49_data,
    level_50_data, level_51_data, level_52_data, level_53_data, level_special2_data, level_54_data,
    level_55_data, level_56_data, level_57_data, level_58_data, level_59_data,
    level_60_data, level_61_data, level_62_data, level_63_data, level_64_data,
    level_65_data, level_66_data, level_67_data, level_68_data, level_69_data,
    level_70_data
};

// Global variables for tracking level state
const int maxLevels = sizeof(levels) / sizeof(levels[0]);
int currentLevel = 0;

static LevelState currentLevelState;
static int currentLevelNum = -1;

// Spatial grid for collision detection, stored in EWRAM
GridCell spatialGrid[GRID_WIDTH][GRID_HEIGHT] __attribute__((section(".ewram")));

// Tile information for game objects
const TileInfo tileInfo[] = {
    {"WALL_1", WALL_1, 1},
    {"WALL_2", WALL_2, 1},
    {"WALL_GUI_1", WALL_GUI_1, 1},
    {"WALL_GUI_2", WALL_GUI_2, 1},
    {"WALL_SPECIAL_1", WALL_SPECIAL_1, 1},
    {"WALL_SPECIAL_2", WALL_SPECIAL_2, 1},
    {"WALL_SPECIAL_3", WALL_SPECIAL_3, 1},
    {"WALL_SPECIAL_4", WALL_SPECIAL_4, 1},
    {"WALL_SPECIAL_5", WALL_SPECIAL_5, 1},
    {"WALL_SPECIAL_6", WALL_SPECIAL_6, 1},
    {"WALL_SPECIAL_7", WALL_SPECIAL_7, 1},
    {"WALL_SPECIAL_8", WALL_SPECIAL_8, 1},
    {"WALL_SPECIAL_9", WALL_SPECIAL_9, 1},
    {"WALL_SPECIAL_A", WALL_SPECIAL_A, 1},
    {"WALL_SPECIAL_B", WALL_SPECIAL_B, 1},
    {"WALL_SPECIAL_C", WALL_SPECIAL_C, 1},
    {"WALL_SPECIAL_D", WALL_SPECIAL_D, 1},
    {"WALL_SPECIAL_E", WALL_SPECIAL_E, 1},
    {"WALL_SPECIAL_F", WALL_SPECIAL_F, 1},
    {"WALL_SPECIAL_G", WALL_SPECIAL_G, 1},
    {"BOX", BOX, 1},
    {"JIGGLY_1", JIGGLY_1, 1},
    {"QBLOCK", QBLOCK, 1},
    {"SWITCH_OFF", SWITCH_OFF, 0},
    {"COIN", COIN, 0},
    {"EXIT", EXIT, 0},
    {"WARP", WARP, 0},
    {"STOP", STOP, 0},
    {"POKEBALL", POKEBALL, 0},
    {"RAMP_RIGHT", RAMP_RIGHT, 0},
    {"RAMP_LEFT", RAMP_LEFT, 0},
    {"BERRY_1", BERRY_1, 0},
    {"BERRY_2", BERRY_2, 0},
    {"BERRY_3", BERRY_3, 0},
    {"BERRY_4", BERRY_4, 0},
    {"BG", 0, 0},
    {"PLAYER_START", 0, 0},
    {"PLAYER_LEFT", 0, 0}
};

// Count of tile information entries
const int tileInfoCount = sizeof(tileInfo) / sizeof(tileInfo[0]);

// Blocking tiles for collision detection
const BlockingTile blockingTiles[] = {
    {"WALL_1", WALL_1}, {"WALL_2", WALL_2}, {"WALL_GUI_1", WALL_GUI_1},
    {"WALL_GUI_2", WALL_GUI_2}, {"WALL_SPECIAL_1", WALL_SPECIAL_1},
    {"WALL_SPECIAL_2", WALL_SPECIAL_2}, {"WALL_SPECIAL_3", WALL_SPECIAL_3},
    {"WALL_SPECIAL_4", WALL_SPECIAL_4}, {"WALL_SPECIAL_5", WALL_SPECIAL_5},
    {"WALL_SPECIAL_6", WALL_SPECIAL_6}, {"WALL_SPECIAL_7", WALL_SPECIAL_7},
    {"WALL_SPECIAL_8", WALL_SPECIAL_8}, {"WALL_SPECIAL_9", WALL_SPECIAL_9},
    {"WALL_SPECIAL_A", WALL_SPECIAL_A}, {"WALL_SPECIAL_B", WALL_SPECIAL_B},
    {"WALL_SPECIAL_C", WALL_SPECIAL_C}, {"WALL_SPECIAL_D", WALL_SPECIAL_D},
    {"WALL_SPECIAL_E", WALL_SPECIAL_E}, {"WALL_SPECIAL_F", WALL_SPECIAL_F},
    {"WALL_SPECIAL_G", WALL_SPECIAL_G}, {"BOX", BOX}, {"JIGGLY", JIGGLY_1},
    {"QBLOCK", QBLOCK}
};

// Count of blocking tile entries
const int blockingTileCount = sizeof(blockingTiles) / sizeof(blockingTiles[0]);

// Linear Feedback Shift Register for random number generation
static u16 lfsr = 0xACE1;

// Wait for A key press and release
void waitForKey(void) {
    while (~REG_KEYINPUT & KEY_A);
    while (! (~REG_KEYINPUT & KEY_A));
}

// Generate random number using LFSR
u16 rand(void) {
    u16 bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1;
    lfsr = (lfsr >> 1) | (bit << 15);
    return lfsr;
}

// Seed the random number generator
void seedRand(u16 seed) {
    lfsr = seed | 0x0001;
}

// Structure for tile map entries
typedef struct {
    int row;
    int col;
    int tile;
} TileMapEntry;

// Update spatial grid with walls and objects for collision detection
void updateSpatialGrid(const Wall* walls, int wallCount, const LevelObject* objects, int objectCount) {
    memset(spatialGrid, 0, sizeof(spatialGrid));

    for (int i = 0; i < wallCount; i++) {
        int startX = walls[i].x / GRID_CELL_SIZE;
        int endX = (walls[i].x + SPRITE_SIZE - 1) / GRID_CELL_SIZE;
        int startY = walls[i].y / GRID_CELL_SIZE;
        int endY = (walls[i].y + SPRITE_SIZE - 1) / GRID_CELL_SIZE;

        for (int r = startY; r <= endY; r++) {
            if (r < 0 || r >= GRID_HEIGHT) continue;
            for (int c = startX; c <= endX; c++) {
                if (c < 0 || c >= GRID_WIDTH) continue;
                GridCell* cell = &spatialGrid[c][r];
                if (cell->wall_count < MAX_INDICES_PER_CELL) {
                    cell->wall_indices[cell->wall_count++] = i;
                }
            }
        }
    }

    for (int i = 0; i < objectCount; i++) {
        bool isBlocking = false;
        for (int j = 0; j < blockingTileCount; j++) {
            if (objects[i].tile == blockingTiles[j].tile) {
                isBlocking = true;
                break;
            }
        }
        if (!isBlocking) continue;

        int startX = objects[i].x / GRID_CELL_SIZE;
        int endX = (objects[i].x + SPRITE_SIZE - 1) / GRID_CELL_SIZE;
        int startY = objects[i].y / GRID_CELL_SIZE;
        int endY = (objects[i].y + SPRITE_SIZE - 1) / GRID_CELL_SIZE;

        for (int r = startY; r <= endY; r++) {
            if (r < 0 || r >= GRID_HEIGHT) continue;
            for (int c = startX; c <= endX; c++) {
                if (c < 0 || c >= GRID_WIDTH) continue;
                GridCell* cell = &spatialGrid[c][r];
                if (cell->object_count < MAX_INDICES_PER_CELL) {
                    cell->object_indices[cell->object_count++] = i;
                }
            }
        }
    }
}

// Define map dimensions
#define MAP_ROWS 10
#define MAP_COLS 15

// Parse level data into walls, objects, and player position
void parseLevel(const char* const* levelData, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, int* playerX, int* playerY) {
    *wallCount = 0;
    *objectCount = 0;

    int tileMap[MAP_ROWS][MAP_COLS];
    for (int row = 0; row < MAP_ROWS; row++) {
        for (int col = 0; col < MAP_COLS; col++) {
            tileMap[row][col] = 0;
        }
    }

    for (int i = 0; levelData[i] != NULL; i++) {
        int row, col;
        const char* line = levelData[i];
        if (sscanf(line, "[%d,%d] = %*s", &row, &col) != 2) continue;

        if (row < 1 || row > MAP_ROWS || col < 1 || col > MAP_COLS) continue;

        int x = (col - 1) * SPRITE_SIZE;
        int y = (row - 1) * SPRITE_SIZE;

        const char* tileName = strstr(line, "=");
        if (!tileName) continue;
        tileName += 2;
        char tileNameBuffer[32];
        int len = strcspn(tileName, " :");
        if (len >= sizeof(tileNameBuffer)) len = sizeof(tileNameBuffer) - 1;
        strncpy(tileNameBuffer, tileName, len);
        tileNameBuffer[len] = '\0';

        int tile = -1;
        int isBlocking = 0;
        for (int j = 0; j < tileInfoCount; j++) {
            if (strcmp(tileNameBuffer, tileInfo[j].name) == 0) {
                tile = tileInfo[j].tile;
                isBlocking = tileInfo[j].isBlocking;
                break;
            }
        }

        if (strcmp(tileNameBuffer, "PLAYER_START") == 0) {
            *playerX = x;
            *playerY = y;
            continue;
        }

        if (tile != -1) {
            tileMap[row - 1][col - 1] = tile;

            if (isBlocking) {
                walls[*wallCount].x = x;
                walls[*wallCount].y = y;
                walls[*wallCount].tile = tile;
                walls[*wallCount].specialTargetLevel = -1;

                const char* specialMarker = strstr(line, " - ");
                if (specialMarker) {
                    char targetName[16];
                    sscanf(specialMarker + 3, "%15s", targetName);
                    if (strcmp(targetName, "SPECIAL1") == 0) {
                        walls[*wallCount].specialTargetLevel = 9;
                    }
                    else if (strcmp(targetName, "SPECIAL2") == 0) {
                        walls[*wallCount].specialTargetLevel = 54;
                    }
                }
                (*wallCount)++;
                continue;
            }

            objects[*objectCount].x = x;
            objects[*objectCount].y = y;
            objects[*objectCount].tile = tile;
            objects[*objectCount].collected = 0;
            objects[*objectCount].soundPlayed = 0;
            objects[*objectCount].switchTargetRow = 0;
            objects[*objectCount].switchTargetCol = 0;
            objects[*objectCount].switchTargetTile = 0;
            objects[*objectCount].originalTile = 0;
            objects[*objectCount].isTriggered = 0;

            if (tile == SWITCH_OFF && strstr(line, ":")) {
                int targetRow, targetCol;
                char targetTileName[32];
                if (sscanf(line, "[%*d,%*d] = SWITCH_OFF : %d,%d,%31s", &targetRow, &targetCol, targetTileName) == 3) {
                    objects[*objectCount].switchTargetRow = targetRow;
                    objects[*objectCount].switchTargetCol = targetCol;

                    for (int j = 0; j < tileInfoCount; j++) {
                        if (strcmp(targetTileName, tileInfo[j].name) == 0) {
                            objects[*objectCount].switchTargetTile = tileInfo[j].tile;
                            break;
                        }
                    }
                }
            }
            (*objectCount)++;
        }
    }

    for (int i = 0; i < *objectCount; i++) {
        if (objects[i].tile == SWITCH_OFF && objects[i].switchTargetRow != 0 && objects[i].switchTargetCol != 0) {
            int targetRow = objects[i].switchTargetRow - 1;
            int targetCol = objects[i].switchTargetCol - 1;
            if (targetRow >= 0 && targetRow < MAP_ROWS && targetCol >= 0 && targetCol < MAP_COLS) {
                objects[i].originalTile = tileMap[targetRow][targetCol];
            }
        }
    }
}

// Determine world type from level data
WorldType getWorldType(const char* const* levelData) {
    for (int i = 0; levelData[i] != NULL; i++) {
        if (strstr(levelData[i], "[WORLD] = LAB")) return WORLD_LAB;
        if (strstr(levelData[i], "[WORLD] = ICE")) return WORLD_ICE;
        if (strstr(levelData[i], "[WORLD] = POWERP")) return WORLD_POWERP;
        if (strstr(levelData[i], "[WORLD] = CAVE")) return WORLD_CAVE;
        if (strstr(levelData[i], "[WORLD] = MANSION")) return WORLD_MANSION;
        if (strstr(levelData[i], "[WORLD] = WOODS")) return WORLD_WOODS;
        if (strstr(levelData[i], "[WORLD] = CITY")) return WORLD_CITY;
        if (strstr(levelData[i], "[WORLD] = SPECIAL1")) return WORLD_SPECIAL1;
        if (strstr(levelData[i], "[WORLD] = SPECIAL2")) return WORLD_SPECIAL2;
    }
    return WORLD_LAB;
}

// Load a level and initialize game state
void loadLevel(int levelNum, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, 
               int* playerX, int* playerY, int* coinCount, WorldType* worldType) {
    if (levelNum >= maxLevels) currentLevel = 0;
    else currentLevel = levelNum;
    
    *worldType = getWorldType(levels[currentLevel]);
    parseLevel(levels[currentLevel], walls, wallCount, objects, objectCount, playerX, playerY);

    updateSpatialGrid(walls, *wallCount, objects, *objectCount);

    *coinCount = 0;
    for (int i = 0; i < *objectCount; i++) {
        objects[i].soundPlayed = 0;
        if (objects[i].tile == COIN || objects[i].tile == BERRY_1 || 
            objects[i].tile == BERRY_2 || objects[i].tile == BERRY_3 || 
            objects[i].tile == BERRY_4) {
            (*coinCount)++;
        }
    }

    currentLevelState.wallCount = *wallCount;
    memcpy(currentLevelState.walls, walls, *wallCount * sizeof(Wall));
    currentLevelState.objectCount = *objectCount;
    memcpy(currentLevelState.objects, objects, *objectCount * sizeof(LevelObject));
    currentLevelState.playerX = *playerX;
    currentLevelState.playerY = *playerY;
    currentLevelState.coinCount = *coinCount;
    currentLevelState.worldType = *worldType;
    currentLevelNum = currentLevel;
}

// Restore a previously loaded level state
void restoreLevel(int levelNum, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, 
                 int* playerX, int* playerY, int* coinCount, WorldType* worldType) {
    if (levelNum == currentLevelNum) {
        *wallCount = currentLevelState.wallCount;
        memcpy(walls, currentLevelState.walls, *wallCount * sizeof(Wall));
        *objectCount = currentLevelState.objectCount;
        memcpy(objects, currentLevelState.objects, *objectCount * sizeof(LevelObject));
        *playerX = currentLevelState.playerX;
        *playerY = currentLevelState.playerY;
        *coinCount = currentLevelState.coinCount;
        *worldType = currentLevelState.worldType;

        updateSpatialGrid(walls, *wallCount, objects, *objectCount);

    } else {
        loadLevel(levelNum, walls, wallCount, objects, objectCount, playerX, playerY, coinCount, worldType);
    }
}

// Save current level to SRAM
void saveLevel(int levelNum) {
    if (levelNum >= 0 && levelNum < maxLevels) {
        SRAM_START[SRAM_LEVEL_OFFSET] = (u8)levelNum;
    }
}

// Load saved level from SRAM
int loadSavedLevel(void) {
    u8 level = SRAM_START[SRAM_LEVEL_OFFSET];
    if (level >= maxLevels) {
        return 0;
    }
    return (int)level;
}