#ifndef GAME_H
#define GAME_H

#include <stdint.h>
#include <gba.h>
#include <maxmod.h>
#include "mm_types.h"
#include <stdbool.h> 
#include <string.h>  

// Screen and sprite dimensions
#define SCREEN_WIDTH  240
#define SCREEN_HEIGHT 160
#define SPRITE_SIZE   16
#define SPRITE_SPEED  2

// Spatial grid configuration for collision detection
#define GRID_CELL_SIZE 32
#define GRID_WIDTH ((SCREEN_WIDTH + GRID_CELL_SIZE - 1) / GRID_CELL_SIZE)
#define GRID_HEIGHT ((SCREEN_HEIGHT + GRID_CELL_SIZE - 1) / GRID_CELL_SIZE)
#define MAX_INDICES_PER_CELL 32 

// Type aliases for unsigned integers
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint8_t u8;

// Key input definitions
#define KEY_DOWN   0x0080
#define KEY_UP     0x0040
#define KEY_LEFT   0x0020
#define KEY_RIGHT  0x0010
#define KEY_START  0x0008
#define KEY_SELECT 0x0004
#define KEY_B      0x0002
#define KEY_A      0x0001

// Tile identifiers for game objects and sprites
enum Tiles {
    PLAYER_LEFT = 0, PLAYER_RIGHT = 16, PLAYER_UP = 8, PLAYER_DOWN = 24,
    PLAYER_SPAWN_1 = 32, PLAYER_SPAWN_2 = 40, PLAYER_SPAWN_3 = 48,
    PLAYER_SPAWN_4 = 56, PLAYER_SPAWN_5 = 64, PLAYER_SPAWN_6 = 72,
    RAMP_LEFT = 80, RAMP_RIGHT = 88, 
    PLAYER_LEFT_ELECTRO = 96, PLAYER_UP_ELECTRO = 104, PLAYER_RIGHT_ELECTRO = 112, PLAYER_DOWN_ELECTRO = 120,
    POKEBALL_EXPLODE_1 = 128, POKEBALL_EXPLODE_2 = 152, POKEBALL_EXPLODE_3 = 144, POKEBALL_EXPLODE_4 = 152,
    JIGGLY_1 = 160, JIGGLY_2 = 168, JIGGLY_KISS = 176, POKEBALL = 184,
    BERRY_1 = 192, BERRY_2 = 200, BERRY_3 = 208, BERRY_4 = 216,    
    COUNTER_0 = 224, COUNTER_1 = 232,
    COUNTER_2 = 240, COUNTER_3 = 248, COUNTER_4 = 256, COUNTER_5 = 264,
    COUNTER_6 = 272, COUNTER_7 = 280, COUNTER_8 = 288, COUNTER_9 = 296,
    SYMBOL_EX = 304, SYMBOL_QUESTION = 312,
    LETTER_A = 320, LETTER_B = 328, LETTER_C = 336, LETTER_D = 344,
    LETTER_E = 352, LETTER_F = 360, LETTER_G = 368, LETTER_H = 376,
    LETTER_I = 384, LETTER_J = 392, LETTER_K = 400, LETTER_L = 408,
    LETTER_M = 416, LETTER_N = 424, LETTER_O = 432, LETTER_P = 440,
    LETTER_Q = 448, LETTER_R = 456, LETTER_S = 464, LETTER_T = 472,
    LETTER_U = 480, LETTER_V = 488, LETTER_W = 496, LETTER_X = 504,
    LETTER_Y = 512, LETTER_Z = 520, SYMBOL_COLON = 528, SYMBOL_PERIOD = 536,
    WALL_GUI_1 = 544, WALL_GUI_2 = 552, WALL_1 = 560, WALL_2 = 568,
    QBLOCK = 576, BOX = 584, EXIT = 592, WARP = 600,
    STOP = 608, SWITCH_ON = 616, SWITCH_OFF = 624, COIN = 632,
    WALL_SPECIAL_1 = 640, WALL_SPECIAL_2 = 648, WALL_SPECIAL_3 = 656, WALL_SPECIAL_4 = 664,
    WALL_SPECIAL_5 = 672, WALL_SPECIAL_6 = 680, WALL_SPECIAL_7 = 688, WALL_SPECIAL_8 = 696,
    WALL_SPECIAL_9 = 704, WALL_SPECIAL_A = 712, WALL_SPECIAL_B = 720, WALL_SPECIAL_C = 728,
    WALL_SPECIAL_D = 736, WALL_SPECIAL_E = 744, WALL_SPECIAL_F = 752, WALL_SPECIAL_G = 760,
};

// Structure for Object Attribute Memory entries
typedef struct {
    u16 attr0;
    u16 attr1;
    u16 attr2;
    u16 pad;
} OAMEntry;

// Structure for blocking tile information
typedef struct {
    const char* name;
    int tile;
} BlockingTile;

// Structure for wall objects
typedef struct {
    int x;
    int y;
    int tile;
    int specialTargetLevel; 
} Wall;

// Structure for level objects with interaction properties
typedef struct {
    int x;
    int y;
    int tile;
    int collected;
    int soundPlayed;
    int switchTargetRow;    
    int switchTargetCol;    
    int switchTargetTile;   
    int originalTile;       
    int isTriggered;
} LevelObject;

// Function to handle switch toggle logic
void handleSwitchToggle(int switchIndex, LevelObject* objects, int* objectCount, Wall* walls, int* wallCount);

// Structure for tile information with blocking property
typedef struct {
    const char* name;
    int tile;
    int isBlocking; 
} TileInfo;

// Enum for different world types
typedef enum {
    WORLD_LAB,
    WORLD_ICE,
    WORLD_POWERP,
    WORLD_CAVE,
    WORLD_MANSION,
    WORLD_WOODS,
    WORLD_CITY,
    WORLD_SPECIAL1,
    WORLD_SPECIAL2
} WorldType;

// Maximum limits for walls and objects
#define MAX_WALLS 100
#define MAX_OBJECTS 50

// Structure to store level state
typedef struct {
    Wall walls[MAX_WALLS];
    int wallCount;
    LevelObject objects[MAX_OBJECTS];
    int objectCount;
    int playerX;
    int playerY;
    int coinCount;
    WorldType worldType;
} LevelState;

// Structure for spatial grid cells
typedef struct {
    int wall_indices[MAX_INDICES_PER_CELL];
    int wall_count;
    int object_indices[MAX_INDICES_PER_CELL];
    int object_count;
} GridCell;

// External declarations for global variables
extern GridCell spatialGrid[GRID_WIDTH][GRID_HEIGHT];
void updateSpatialGrid(const Wall* walls, int wallCount, const LevelObject* objects, int objectCount);

extern const TileInfo tileInfo[];
extern const int tileInfoCount;

extern const char* level_01_data[];
extern const char* level_70_data[];

extern const char* const* levels[];

extern const int maxLevels;
extern int currentLevel;

extern const BlockingTile blockingTiles[];
extern const int blockingTileCount;

extern int specialWallCount;

// SRAM memory access definitions
#define SRAM_START ((volatile u8*)0x0E000000)
#define SRAM_LEVEL_OFFSET 0

// Function declarations for game logic
void waitForKey(void);
void seedRand(u16 seed);
u16 rand(void);
void parseLevel(const char* const* levelData, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, int* playerX, int* playerY);

// Inline function for collision detection
static inline int checkCollision(int x1, int y1, int x2, int y2, int isPokeball) {
    int w1 = SPRITE_SIZE, h1 = SPRITE_SIZE;
    int left1 = x1, right1 = x1 + w1 - 1, top1 = y1, bottom1 = y1 + h1 - 1;

    if (isPokeball) {
        int w2 = 12, h2 = 12, offset = 4;
        int left2 = x2 + offset, right2 = x2 + w2 - 1 - offset;
        int top2 = y2 + offset, bottom2 = y2 + h2 - 1 - offset;
        return !(right1 < left2 || left1 > right2 || bottom1 < top2 || top1 > bottom2);
    } else {
        int w2 = SPRITE_SIZE, h2 = SPRITE_SIZE;
        int left2 = x2, right2 = x2 + w2 - 1, top2 = y2, bottom2 = y2 + h1 - 1;
        return !(right1 < left2 || left1 > right2 || bottom1 < top2 || top1 > bottom2);
    }
}

// Inline function for collision detection with screen wrapping
static inline int checkCollisionWithWrap(int newX, int newY, const Wall* walls, int wallCount, const LevelObject* objects, int objectCount, int coinCount, int currentLevel) {
    bool checked_walls[MAX_WALLS];
    bool checked_objects[MAX_OBJECTS];
    memset(checked_walls, 0, sizeof(checked_walls));
    memset(checked_objects, 0, sizeof(checked_objects));

    int w1 = SPRITE_SIZE, h1 = SPRITE_SIZE;

    int positions[5][2];
    int pos_count = 0;

    positions[pos_count][0] = newX;
    positions[pos_count][1] = newY;
    pos_count++;

    if (newX + SPRITE_SIZE > SCREEN_WIDTH) {
        positions[pos_count][0] = newX - SCREEN_WIDTH;
        positions[pos_count][1] = newY;
        pos_count++;
    }
    if (newX < 0) {
        positions[pos_count][0] = newX + SCREEN_WIDTH;
        positions[pos_count][1] = newY;
        pos_count++;
    }
    if (newY + SPRITE_SIZE > SCREEN_HEIGHT) {
        positions[pos_count][0] = newX;
        positions[pos_count][1] = newY - SCREEN_HEIGHT;
        pos_count++;
    }
    if (newY < 0) {
        positions[pos_count][0] = newX;
        positions[pos_count][1] = newY + SCREEN_HEIGHT;
        pos_count++;
    }
    
    for (int p_idx = 0; p_idx < pos_count; p_idx++) {
        int currentX = positions[p_idx][0];
        int currentY = positions[p_idx][1];

        int left = currentX, right = currentX + w1 - 1, top = currentY, bottom = currentY + h1 - 1;
        int startCellX = left / GRID_CELL_SIZE;
        int endCellX = right / GRID_CELL_SIZE;
        int startCellY = top / GRID_CELL_SIZE;
        int endCellY = bottom / GRID_CELL_SIZE;

        for (int cy = startCellY; cy <= endCellY; cy++) {
            if (cy < 0 || cy >= GRID_HEIGHT) continue;
            for (int cx = startCellX; cx <= endCellX; cx++) {
                if (cx < 0 || cx >= GRID_WIDTH) continue;

                GridCell* cell = &spatialGrid[cx][cy];

                for (int i = 0; i < cell->wall_count; i++) {
                    int idx = cell->wall_indices[i];
                    if (!checked_walls[idx]) {
                        if (checkCollision(currentX, currentY, walls[idx].x, walls[idx].y, 0)) return 1;
                        checked_walls[idx] = true;
                    }
                }
                
                for (int i = 0; i < cell->object_count; i++) {
                    int idx = cell->object_indices[i];
                    if (!checked_objects[idx] && !objects[idx].collected) {
                        if (checkCollision(currentX, currentY, objects[idx].x, objects[idx].y, 0)) return 1;
                        checked_objects[idx] = true;
                    }
                }
            }
        }
    }

    return 0; 
}

// Inline function for detecting collisions with special tiles
static inline int checkSpecialTileCollision(int x, int y, const Wall* walls, int wallCount, int coinCount, int* levelChanged) {
    for (int i = 0; i < wallCount; i++) {
        if (walls[i].specialTargetLevel != -1 && coinCount == 0) {
            if (checkCollision(x, y, walls[i].x, walls[i].y, 0)) {
                *levelChanged = walls[i].specialTargetLevel;
                return 1;
            }
        }
    }
    return 0;
}

// Function declarations for level management
void saveLevel(int levelNum);
int loadSavedLevel(void);

WorldType getWorldType(const char* const* levelData);

void loadLevel(int levelNum, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, 
               int* playerX, int* playerY, int* coinCount, WorldType* worldType);

void restoreLevel(int levelNum, Wall* walls, int* wallCount, LevelObject* objects, int* objectCount, 
                 int* playerX, int* playerY, int* coinCount, WorldType* worldType);

// Function to update Jigglypuff animation
void updateJigglyAnimation(int* jigglyFrame, int* jigglyDelayCounter, Wall* walls, int wallCount, LevelObject* objects, int objectCount, int* spriteIndex);

#endif