#include <genesis.h>
#include "megatiler_res.h"

// ====================================================================
// DEFINES
// ====================================================================
#define SOLID_TILE 1
#define SPAWN_TILE 4
#define EXIT_TILE 5
#define COIN_TILE 6

#define MAX_COINS 3
#define LEVEL_NUM 3

#define TILESIZE 8
#define MAP_WIDTH 8
#define MAP_HEIGHT 8

#define ANIM_DOWN 0
#define ANIM_UP   1
#define ANIM_SIDE 2

#define SFX_COIN   64
#define SFX_UNLOCK 65

// ====================================================================
// STRUCTS
// ====================================================================
typedef u8 map[MAP_HEIGHT][MAP_WIDTH];

typedef struct {
    u8 x;
    u8 y;
} Point;

typedef enum { up, down, left, right, none } moveDirection;

typedef struct {
    Point pos;
    Point tilePos;
    int w;
    int h;
    int health;
    bool moving;
    moveDirection dir;
    Sprite *sprite;
    char name[6];
} Entity;

typedef struct {
    Point pos;
    u8 w;
    u8 h;
    Sprite *sprite;
    u8 health;
} Coin;

// ====================================================================
// LEVEL DATA
// ====================================================================
map level1 = {
    {6,0,0,0,0,0,0,6},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,1,0,0,0},
    {0,0,0,1,1,0,0,0},
    {4,0,0,0,1,0,0,0},
    {0,0,0,0,1,0,0,0},
    {0,0,0,0,1,0,0,0},
    {5,0,0,0,0,0,0,6}
};

map level2 = {
    {0,0,0,6,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,4,0,1,1,1,0,0},
    {0,0,0,6,0,1,0,0},
    {0,0,0,1,1,1,6,0},
    {0,0,0,1,0,0,0,0},
    {0,0,0,1,1,1,5,0},
    {0,0,0,0,0,0,0,0}
};

map level3 = {
    {0,0,0,0,0,0,0,0},
    {0,0,1,1,1,1,0,0},
    {0,0,0,0,6,1,0,0},
    {0,0,0,1,1,1,6,0},
    {0,0,0,0,0,1,0,0},
    {0,6,1,1,1,1,0,0},
    {0,4,0,0,0,0,5,0},
    {0,0,0,0,0,0,0,0}
};

// ====================================================================
// GLOBALS
// ====================================================================
u8 x = 0, y = 0, t = 0, i = 0;
u8 coinNum = 0;
u8 coinsCollected = 0;

u8* currentLevel;
static u8 currentLevelIndex = 0;

// FLAT POINTER ARRAY (WARNING FREE!)
u8* levelList[LEVEL_NUM];

char hud_string[10] = "";

Point exitLocation = {0,0};
bool exitUnlocked = FALSE;

Entity player = {{0,0},{0,0},8,8,0,FALSE,none,NULL,"PLAYER"};
Coin coins[MAX_COINS];
Coin *coinToCheck;
Coin *c = coins;

// ====================================================================
// FORWARD DECLARATIONS
// ====================================================================
int getTileAt(u8 X, u8 Y);
void loadLevel();
void movePlayer(moveDirection Direction);
void updateScoreDisplay();
void unlockExit();
void clearLevel();
void tiler_inputHandler(u16 joy, u16 changed, u16 state);

// ====================================================================
// ENTRY POINT FOR MENU
// ====================================================================
void runMegatiler() {

    VDP_clearPlane(BG_A, TRUE);
    VDP_clearPlane(BG_B, TRUE);
    VDP_clearTextArea(0, 0, 40, 28);
    VDP_resetSprites();

    JOY_reset();
    JOY_setEventHandler(&tiler_inputHandler);

    // PCM
    XGM_setPCM(SFX_COIN,   TILER_sfx_coin, sizeof(TILER_sfx_coin));
    XGM_setPCM(SFX_UNLOCK, TILER_sfx_unlock, sizeof(TILER_sfx_unlock));

    // Level pointers (WARNING FIX)
    levelList[0] = &level1[0][0];
    levelList[1] = &level2[0][0];
    levelList[2] = &level3[0][0];

    loadLevel();
    updateScoreDisplay();

    // -------------------------
    // MAIN LOOP
    // -------------------------
    while (1)
    {
        if (JOY_readJoypad(JOY_1) & BUTTON_B)
            return;

        // movement
        if (player.moving)
        {
            switch(player.dir){
                case up:    player.pos.y -= 1; break;
                case down:  player.pos.y += 1; break;
                case left:  player.pos.x -= 1; break;
                case right: player.pos.x += 1; break;
                default: break;
            }
        }

        SPR_update();

        // tile alignment → movement finished
        if ((player.pos.x % TILESIZE == 0) &&
            (player.pos.y % TILESIZE == 0))
        {
            player.moving = FALSE;

            if (exitUnlocked &&
                player.tilePos.x == exitLocation.x &&
                player.tilePos.y == exitLocation.y)
            {
                currentLevelIndex++;
                if (currentLevelIndex >= LEVEL_NUM) currentLevelIndex = 0;
                loadLevel();
            }
        }

        SPR_setPosition(player.sprite, player.pos.x, player.pos.y);

        // coin collision
        for (i = 0; i < MAX_COINS; i++)
        {
            coinToCheck = &coins[i];
            if (coinToCheck->health > 0 &&
                player.pos.x < coinToCheck->pos.x + coinToCheck->w &&
                player.pos.x + player.w > coinToCheck->pos.x &&
                player.pos.y < coinToCheck->pos.y + coinToCheck->h &&
                player.pos.y + player.h > coinToCheck->pos.y)
            {
                coinToCheck->health = 0;
                SPR_setVisibility(coinToCheck->sprite, HIDDEN);
                coinsCollected++;
                XGM_startPlayPCM(SFX_COIN, 1, SOUND_PCM_CH2);

                updateScoreDisplay();

                if (coinsCollected == MAX_COINS)
                    unlockExit();
            }
        }

        SYS_doVBlankProcess();
    }
}

// ====================================================================
// SUPPORT FUNCTIONS
// ====================================================================

int getTileAt(u8 X, u8 Y) {
    return *(currentLevel + (Y * MAP_WIDTH + X));
}

void loadLevel() {
    clearLevel();

    x = 0; y = 0; coinNum = 0;

    currentLevel = levelList[currentLevelIndex];

    SPR_init();

    u8 total = MAP_WIDTH * MAP_HEIGHT;
    for (i = 0; i < total; i++)
    {
        t = *(currentLevel + i);

        if (t == SPAWN_TILE)
        {
            player.tilePos.x = x;
            player.tilePos.y = y;
            player.pos.x = x * TILESIZE;
            player.pos.y = y * TILESIZE;

            player.sprite = SPR_addSprite(&TILER_spr_player,
                player.pos.x, player.pos.y,
                TILE_ATTR(PAL2, 0, FALSE, FALSE));

            VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1,0,FALSE,FALSE,1), x, y);
        }
        else if (t == COIN_TILE)
        {
            if (coinNum < MAX_COINS)
            {
                c = &coins[coinNum];
                c->pos.x = x * TILESIZE;
                c->pos.y = y * TILESIZE;
                c->w = 8;
                c->h = 8;
                c->health = 1;
                c->sprite = SPR_addSprite(&TILER_coin,
                    c->pos.x, c->pos.y,
                    TILE_ATTR(PAL2, 0, FALSE, FALSE));

                coinNum++;

                VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1,0,FALSE,FALSE,1), x, y);
            }
        }
        else if (t == EXIT_TILE)
        {
            exitLocation.x = x;
            exitLocation.y = y;

            VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1,0,FALSE,FALSE,1), x, y);
        }
        else
        {
            VDP_setTileMapXY(BG_B,
                TILE_ATTR_FULL(PAL1,0,FALSE,FALSE,t + 1), x, y);
        }

        x++;
        if (x >= MAP_WIDTH) { x = 0; y++; }
    }

    VDP_loadTileSet(TILER_floortiles.tileset, 1, DMA);
    PAL_setPalette(PAL1, TILER_floortiles.palette->data, DMA);
    PAL_setPalette(PAL2, TILER_spr_player.palette->data, DMA);
}

void movePlayer(moveDirection Direction)
{
    if (!player.moving)
    {
        switch(Direction)
        {
            case up:
                if (player.tilePos.y > 0 &&
                    getTileAt(player.tilePos.x, player.tilePos.y - 1) != SOLID_TILE)
                {
                    player.tilePos.y--;
                    player.moving = TRUE;
                    player.dir = up;
                    SPR_setAnim(player.sprite, ANIM_UP);
                }
                break;

            case down:
                if (player.tilePos.y < MAP_HEIGHT - 1 &&
                    getTileAt(player.tilePos.x, player.tilePos.y + 1) != SOLID_TILE)
                {
                    player.tilePos.y++;
                    player.moving = TRUE;
                    player.dir = down;
                    SPR_setAnim(player.sprite, ANIM_DOWN);
                }
                break;

            case left:
                if (player.tilePos.x > 0 &&
                    getTileAt(player.tilePos.x - 1, player.tilePos.y) != SOLID_TILE)
                {
                    player.tilePos.x--;
                    player.moving = TRUE;
                    player.dir = left;
                    SPR_setAnim(player.sprite, ANIM_SIDE);
                    SPR_setHFlip(player.sprite, TRUE);
                }
                break;

            case right:
                if (player.tilePos.x < MAP_WIDTH - 1 &&
                    getTileAt(player.tilePos.x + 1, player.tilePos.y) != SOLID_TILE)
                {
                    player.tilePos.x++;
                    player.moving = TRUE;
                    player.dir = right;
                    SPR_setAnim(player.sprite, ANIM_SIDE);
                    SPR_setHFlip(player.sprite, FALSE);
                }
                break;

            default:
                break;
        }
    }
}

void tiler_inputHandler(u16 joy, u16 changed, u16 state)
{
    if (joy == JOY_1)
    {
        if (state & BUTTON_UP)    movePlayer(up);
        else if (state & BUTTON_DOWN)  movePlayer(down);
        else if (state & BUTTON_LEFT)  movePlayer(left);
        else if (state & BUTTON_RIGHT) movePlayer(right);
    }
}

void updateScoreDisplay() {
    sprintf(hud_string, "SCORE: %d", coinsCollected);
    VDP_clearText(8, 0, 10);
    VDP_drawText(hud_string, 8, 0);
}

void unlockExit()
{
    exitUnlocked = TRUE;
    VDP_setTileMapXY(BG_B,
        TILE_ATTR_FULL(PAL1, FALSE, FALSE, FALSE, 3),
        exitLocation.x, exitLocation.y);

    XGM_startPlayPCM(SFX_UNLOCK, 1, SOUND_PCM_CH2);
}

void clearLevel()
{
    VDP_clearPlane(BG_B, TRUE);
    VDP_clearSprites();

    coinsCollected = 0;
    exitUnlocked = FALSE;
}
