#include <stdint.h>
#include <stdio.h>
#include <dos.h>

#include "keyb.h"
#include "control.h"
#include "vga.h"
#include "sound.h"
#include "text.h"
#include "map.h"
#include "data.h"
#include "timer.h"
#include "entities.h"

#include "tmonster.h"
#include "player.h"

#include "game.h"

#define GAME_LIVES_START    3
#define GAME_LIVES_MAX      9
#define GAME_TIME_MAX       60
#define GAME_MAX_PICKAXE    3

#define GAME_EXTRA_LIFE     ((uint16_t)10000)

#define HUD_CLEAN   0
#define HUD_LIVES   1
#define HUD_SCORE   2
#define HUD_STAGE   4
#define HUD_TIME    8
#define HUD_PICKAXE 16
#define HUD_ALL     255

#define GAMEOVER_DELAY 96
#define STAGECLEAR_DELAY 64

static uint32_t hiscore = 15000;

static uint8_t hud;
static volatile uint8_t clock_updated;

/* game variables */
static uint8_t lives;
static uint32_t score;
static uint16_t extra_life;
static uint8_t stage;
static uint8_t time;
static uint8_t pickaxe;
static uint8_t stageclear;
static uint8_t gameover;
static uint8_t pause;
static Entity *tmonster;
static uint8_t continuegame;

/* 0-index */
#define START_STAGE 0
#define STAGES_LEN 30

static const uint8_t *stages[STAGES_LEN] = {
    binary_stage01_start, binary_stage02_start, binary_stage03_start, binary_stage04_start,
    binary_stage05_start, binary_stage06_start, binary_stage07_start, binary_stage08_start,
    binary_stage09_start, binary_stage10_start, binary_stage11_start, binary_stage12_start,
    binary_stage13_start, binary_stage14_start, binary_stage15_start, binary_stage16_start,
    binary_stage17_start, binary_stage18_start, binary_stage19_start, binary_stage20_start,
    binary_stage21_start, binary_stage22_start, binary_stage23_start, binary_stage24_start,
    binary_stage25_start, binary_stage26_start, binary_stage27_start, binary_stage28_start,
    binary_stage29_start, binary_stage30_start,
};

static void hud_render()
{
    char b[32];

    if (hud & HUD_ALL)
    {
        Rect src = { 128, 32, 144, 112 };
        Rect dst = { 4, 0, 16, 16 };

        /* lives */
        blitrc(binary_sprites_start, &src, &dst);

        /* pickaxe */
        src.x = 112;
        dst.x = 90;
        blitrc(binary_sprites_start, &src, &dst);

        put_text(132, 4, "TIME", 1);
        put_text(249, 4, "STAGE", 1);
    }

    if (hud & HUD_LIVES)
    {
        sprintf(b, "%d", lives);
        put_text(18, 4, b, 1);
    }

    if (hud & HUD_SCORE)
    {
        sprintf(b, "%06li", score);
        put_text(34, 4, b, 5);
    }

    if (hud & HUD_PICKAXE)
    {
        sprintf(b, "%d", pickaxe);
        put_text(106, 4, b, 1);
    }

    if (hud & HUD_TIME)
    {
        sprintf(b, "%02d", time);
        put_text(172, 4, b, time > 10 || stageclear ? 1 : 15);
        if (!gameover && !stageclear && time <= 10)
            sound_play_efx(EFX_TIME);
    }

    if (hud & HUD_STAGE)
    {
        sprintf(b, "%02d", stage < STAGES_LEN ? stage + 1 : stage);
        put_text(297, 4, b, 1);
    }

    hud = HUD_CLEAN;
}

#define WALK_DELAY 8
#define WALK_CYCLE_FRAMES 4

static const Rect intro_frames[WALK_CYCLE_FRAMES] =
{
    /* walk cycle */
    { 0, 0, 144, 112 },
    { 16, 0, 144, 112 },
    { 0, 0, 144, 112 },
    { 32, 0, 144, 112 },
};

static void run_intro()
{
    /* for the HUD */
    pickaxe = 0;
    time = GAME_TIME_MAX;
    hud = HUD_ALL;

    blit_target(TARGET_BUFFER);
    blit_erase(0);

    map_init(binary_intro_start);

    map_render();
    hud_render();
    put_text(88, 65, "GET ALL THAT GOLD!", 1);

    wait_vsync();
    blit_copy_all();

    Rect dst = { 80, 88 + MAP_OFFS_Y, 16, 16 };
    uint8_t frame = 0, delay = 0;

    wait_frames(32);

    sound_music_pattern(PAT_INTRO);
    /* so the music starts */
    wait_vsync();

    blit_target(TARGET_SCREEN);
    while (dst.x < 224)
    {
        wait_vsync();
        blit_copy16(&dst);
        dst.x++;
        if (map_update_gold(dst.x + 8, dst.y + 15 - MAP_OFFS_Y))
            sound_play_efx(EFX_GOLD);
        blitrc(binary_sprites_start, &intro_frames[frame], &dst);

        if (delay++ == WALK_DELAY)
        {
            delay = 0;
            frame++;
            if (frame == WALK_CYCLE_FRAMES)
                frame = 0;
        }
    }

    wait_vsync();
    blit_copy16(&dst);

    wait_frames(32);
    sound_music_pattern(PAT_SILENCE);
    wait_frames(64);
}

static uint8_t run_gameover()
{
    wait_vsync();

    blit_erase(0);

    /* restore the HUD after erasing the screen */
    hud = HUD_ALL;
    hud_render();

    put_text(124, 90, "GAME OVER", 1);

    sound_music_pattern(PAT_GAMEOVER);

    /* wait some time, and check if the player wants to continue */
    for (uint16_t i = 0; i < 524; i++)
    {
        if (stage < STAGES_LEN)
        {
            if (i == 200 || i == 400)
                put_text(68, 105, "PRESS SPACE TO CONTINUE", i == 200 ? 5 : 0);

            if (i > 200 && i < 400 && keys[KEY_SPACE])
            {
                sound_music_pattern(PAT_SILENCE);
                return 1;
            }
        }

        wait_vsync();
    }

    sound_music_pattern(PAT_SILENCE);
    return 0;
}

static const Rect pop_in_frames[] =
{
    { 128, 16, 144, 112 },
    { 112, 16, 144, 112 }
};

static const Rect pop_in_letters[] =
{
    { 32, 32, 160, 48 },
    { 48, 32, 160, 48 },
    { 64, 32, 160, 48 },
    { 64, 32, 160, 48 },
    { 80, 32, 160, 48 },
    { 96, 32, 160, 48 },
    { 112, 32, 160, 48 },
    { 48, 32, 160, 48 },
};

static void pop_in_letter(uint16_t x, uint16_t y, uint8_t letter)
{
    Rect dst = { x, y, 16, 16 };
    const Rect *src;

    sound_play_efx(EFX_WARP);
    for (uint8_t i = 0; i < 2; i++)
    {
        src = &pop_in_frames[i];
        wait_vsync();
        blitrc(binary_sprites_start, src, &dst);
        wait_frames(7);
    }

    src = &pop_in_letters[letter];
    wait_vsync();
    blitrc(binary_tiles_start, src, &dst);
}

static void run_endgame()
{
    blit_target(TARGET_BUFFER);
    blit_erase(0);

    /* for the HUD */
    pickaxe = 0;
    time = GAME_TIME_MAX;
    hud = HUD_ALL;

    hud_render();
    wait_vsync();
    blit_copy_all();

    blit_target(TARGET_SCREEN);

    for (uint8_t i = 0; i < 4; i++)
        pop_in_letter(88 + i * 16, 60, i);

    for (uint8_t i = 0; i < 4; i++)
        pop_in_letter(168 + i * 16, 60, 4 + i);

    wait_vsync();
    put_text(48, 100, "YOU HAVE COMPLETED THE GAME!", 1);
    put_text(108, 115, "THANK YOU FOR", 6);
    put_text(72, 125, "PLAYING GOLD MINE RUN!", 6);

    sound_music_pattern(PAT_STAGE);

    wait_frames(512);

    run_gameover();
}

static void run_stageclear()
{
    blit_target(TARGET_BUFFER);
    blit_erase(0);

    hud = HUD_ALL;
    hud_render();
    put_text(116, 90, "STAGE", 1);
    put_text(164, 90, "CLEAR", 1);

    wait_vsync();
    blit_copy_all();
    blit_target(TARGET_SCREEN);

    sound_music_pattern(PAT_STAGE);

    /* wait some time */
    wait_frames(164);

    while (time)
    {
        add_score(20);
        time--;
        hud |= HUD_TIME;

        wait_frames(4);

        wait_vsync();
        hud_render();
        sound_play_efx(EFX_GOLD);
    }

    /* wait some time */
    wait_frames(64);
}

static uint8_t run_confirm_quit()
{
    timer_stop();

    blit_target(TARGET_BUFFER);
    blit_erase(0);

    hud = HUD_ALL;
    hud_render();
    put_text(108, 90, "ABANDON GAME?", 1);

    wait_vsync();
    blit_copy_all();

    blit_target(TARGET_SCREEN);

    uint8_t sel = 0;
    uint8_t ctl = CTL_NONE;
    uint8_t cooldown = 0;

update_menu:
    wait_vsync();
    if (sel)
    {
        put_text(144, 105, " YES", 4);
        put_text(144, 115, ">NO", 5);
    }
    else
    {
        put_text(144, 105, ">YES", 5);
        put_text(144, 115, " NO", 4);
    }

    while (1)
    {
        if (cooldown)
            cooldown--;
        else
        {
            ctl = control_read();

            if ((ctl & CTL_UP) || (ctl & CTL_DOWN))
            {
                cooldown = 10;
                sel ^= 1;
                goto update_menu;
            }

            if (keys[KEY_ENTER] || (ctl & CTL_FIRE1))
            {
                while (keys[KEY_ENTER] || (ctl & CTL_FIRE1))
                {
                    ctl = control_read();
                    wait_vsync();
                }
                break;
            }
        }

        wait_vsync();
    }

    /* restore the game screen only when not abandoned */
    if (sel)
    {
        blit_target(TARGET_BUFFER);
        map_render();
        wait_vsync();
        blit_copy_all();

        blit_target(TARGET_SCREEN);
        wait_vsync();
        entities_draw();
    }

    timer_resume();

    return sel == 0;
}

void run_game()
{
    continuegame = 0;

continue_game:
    lives = GAME_LIVES_START;
    score = 0;
    extra_life = 0;

    if (!continuegame)
    {
        stage = START_STAGE;
        run_intro();
    }

next_stage:
    pause = 0;
    gameover = 0;
    stageclear = 0;

    pickaxe = 0;
    time = GAME_TIME_MAX;
    hud = HUD_ALL;

    tmonster = NULL;

    blit_target(TARGET_BUFFER);
    blit_erase(0);

    hud_render();
    put_text(136, 90, "READY?", 1);

    wait_vsync();
    blit_copy_all();

    sound_music_pattern(PAT_READY);

    /* wait some time */
    wait_frames(164);

    map_init(stages[stage]);
    map_render();

    entities_sort();

    wait_vsync();
    blit_copy_all();

    blit_target(TARGET_SCREEN);
    wait_vsync();
    entities_draw();

    timer_start(GAME_TIME_MAX, &clock_updated);

    sound_music_pattern(PAT_PLAY);

    while (1)
    {
        if (clock_updated)
        {
            time = timer_value();
            hud |= HUD_TIME;
        }

        if (hud)
            hud_render();

        if (keys[KEY_ESC])
        {
            if (run_confirm_quit())
                break;
        }

        if (keys[KEY_P])
        {
            /* pause / resume */
            pause ^= 1;

            /* wait for the key to be released */
            while (keys[KEY_P])
                wait_vsync();

            if (pause)
            {
                timer_stop();
                sound_mute();
            }
            else
            {
                timer_resume();
                sound_unmute();
            }
        }

        if (pause)
            continue;

        if (map_is_complete())
        {
            if (!stageclear)
            {
                timer_stop();
                tmonster = NULL;
                entities_warp_out_all();
                sound_play_efx(EFX_WARP);
                player_stageclear();

                stageclear = STAGECLEAR_DELAY;
            }
            else
            {
                if (stageclear == STAGECLEAR_DELAY)
                    sound_music_pattern(PAT_SILENCE);

                stageclear--;
                if (stageclear == 1)
                {
                    sound_music_pattern(PAT_SILENCE);

                    run_stageclear();
                    stage++;
                    if (stage == STAGES_LEN)
                    {
                        run_endgame();
                        break;
                    }
                    goto next_stage;
                }
            }
        }
        else
        {
            player_update();

            /* time monster */
            if (!time && !tmonster)
            {
                tmonster = entities_new();
                if (tmonster)
                    tmonster_init(tmonster);
            }
            if (time && tmonster)
            {
                tmonster->used = USED_FREE;
                tmonster = NULL;
            }
        }

        entities_update();
        entities_sort();

        wait_vsync();

        /* prevent interrupts so updating the audio doesn't result in sprite
         * flickering on the less powered machines */
        disable();
        entities_draw();
        enable();

        if (gameover)
        {
            if (gameover == GAMEOVER_DELAY)
                sound_music_pattern(PAT_SILENCE);

            gameover--;
            if (gameover == 1)
            {
                if (run_gameover())
                {
                    continuegame = 1;
                    goto continue_game;
                }
                break;
            }
        }
    }

    sound_music_pattern(PAT_SILENCE);
}

void add_score(uint8_t v)
{
    score += v;
    extra_life += v;

    if (score > hiscore)
        hiscore = score;

    if (extra_life >= GAME_EXTRA_LIFE)
    {
        extra_life -= GAME_EXTRA_LIFE;
        if (lives < GAME_LIVES_MAX)
        {
            sound_play_efx(EFX_ONEUP);
            lives++;
            hud |= HUD_LIVES;
        }
    }

    hud |= HUD_SCORE;
}

uint32_t get_hiscore()
{
    return hiscore;
}

uint8_t dec_lives()
{
    lives--;

    if (lives == 0)
    {
        gameover = GAMEOVER_DELAY;
        timer_stop();
    }

    hud |= HUD_LIVES;
    return lives;
}

void reset_time()
{
    time = GAME_TIME_MAX;
    hud |= HUD_TIME;
    timer_start(GAME_TIME_MAX, &clock_updated);
}

void add_pickaxe()
{
    if (pickaxe < GAME_MAX_PICKAXE)
    {
        pickaxe++;
        hud |= HUD_PICKAXE;
    }
}

uint8_t use_pickaxe()
{
    if (pickaxe)
    {
        pickaxe--;
        hud |= HUD_PICKAXE;
        return 1;
    }
    return 0;
}

uint8_t is_stageclear()
{
    return stageclear;
}