#include "game.h"

#include <SDL_scancode.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "../main.h"
#include "../window/audio.h"
#include "../window/colour8.h"
#include "./tetromino/shapes.h"
#include "SDL_audio.h"
#include "SDL_timer.h"
#include "tetromino/placing.h"

// shuffle the array using a Fisher–Yates shuffle
static inline void shuffle(uint8_t const size, shape_id* const elmnts) {
    for (uint8_t i = 0; i < (size - 1); i++) {
        uint8_t const j = i + rand() % (size - i);
        shape_id const cache = elmnts[i];
        elmnts[i] = elmnts[j];
        elmnts[j] = cache;
    }
}

void next_shape(game_data* const dat) {
    dat->curr_idx++;                            // increase the current shape index
    dat->sel_x = COLUMNS / 2 - SHAPE_WIDTH / 2; // move the shape position to the centre
    dat->sel_y = 0;

    // return if know which shape is next
    if (dat->curr_idx < (TETROMINO_COUNT - 1))
        return;

    dat->curr_idx = 0;

    shuffle(TETROMINO_COUNT - 1, dat->nxt);
    shape_id cache = dat->nxt[0];
    dat->nxt[0] = dat->nxt[TETROMINO_COUNT - 1];
    dat->nxt[TETROMINO_COUNT - 1] = cache;
}

void game_init(game_data* const dat) {
    // zero-initialize the game data
    *dat = (game_data){0};

    // allocate size for each row
    for (int8_t i = 0; i < ROWS; i++) {
        dat->rows[i] = calloc(COLUMNS, sizeof(colour8));
        // game_data->rows[i][0] = (colour8){(uint8_t)((((i + 1) ^ ((i + 1) >> 3)) * 0x27) & 0xFF)}; // for debugging rows
    }

    // set a random seed using the system clock
    srand(time(NULL));

    // set the shape data in each slot to it's corrsponding ID
    for (shape_id i = 0; i < TETROMINO_COUNT; i++)
        dat->nxt[i] = i;

    dat->curr_idx = -1;                 // set the current index to the max so it becomes zero after increasement
    next_shape(dat);                    // select the next shape (shuffle should not be triggered)
    shuffle(TETROMINO_COUNT, dat->nxt); // manually trigger a shuffle

    // initialize audio
    dat->audio_device = audio_device_init(32000, AUDIO_S16, 1, 4096);
    dat->music = audio_wav_load(dat->audio_device, "korobeiniki.wav");
}

// called every time the game's state is updated
void game_update(game_data* const dat, uint8_t const* const keys) {
    if (keys[SDL_SCANCODE_ESCAPE])
        stop();

    input_data move = MOVE_NONE; // contains the move data
    uint32_t ctime = SDL_GetTicks();

    if (ctime > dat->timer_update) {
        dat->timer_update = ctime + 500;
        move |= MOVE_DOWN;
    }

    if (ctime > dat->timer_music) {
        dat->timer_music = ctime + (dat->music.ms);
        audio_play(dat->audio_device, &dat->music);
    }

    if (ctime > dat->timer_input) {
        input_data umove = MOVE_NONE;

        // get the input data and apply it to move
        if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) umove |= MOVE_LEFT;
        if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) umove |= MOVE_RIGHT;
        if (keys[SDL_SCANCODE_DOWN] || keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_SPACE]) umove |= MOVE_DOWN;
        if (keys[SDL_SCANCODE_Q]) umove |= MOVE_ROTLEFT;
        if (keys[SDL_SCANCODE_E]) umove |= MOVE_ROTRIGHT;

        if (umove != MOVE_NONE) {
            dat->timer_input = ctime + 60;
            move |= umove;
        }
    }

    // update the block position
    if (move != MOVE_NONE)
        place_update(dat, move);
}

void game_free(game_data* const dat) {
    audio_wav_unload(&dat->music);
    audio_device_free(dat->audio_device);

    // clear each row
    for (int8_t i = 0; i < ROWS; i++) {
        free(dat->rows[i]);
        dat->rows[i] = NULL;
    }
}
