//
// Created by MightyPork on 2017/06/08.
// originally written 2016/9/2 for STM32F103 bluepill, adapted
//

#include "game.h"

#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <avr/io.h>
#include <iopins.h>
#include <usart.h>
#include "lib/color.h"
#include "leds.h"
#include "lib/timebase.h"
#include "display.h"
#include "pinout.h"
#include "rng.h"

//region Colors

#define C_DARK rgb24(0,0,0)
#define C_DIMWHITE rgb24(30,30,30)
#define C_OKGREEN rgb24(20,160,0)
#define C_CRIMSON rgb24(220,0,5)

#define C_DIMRED rgb24(100,0,0)
#define C_DIMGREEN rgb24(0,100,0)
#define C_DIMBLUE rgb24(0,0,80)
#define C_DIMYELLOW rgb24(50,45,0)

#define C_BRTRED rgb24(255,0,0)
#define C_BRTGREEN rgb24(0,255,0)
#define C_BRTBLUE rgb24(0,0,255)
#define C_BRTYELLOW rgb24(127,110,0)

// assign to positions

#define C_DIM1 C_DIMRED
#define C_DIM2 C_DIMGREEN
#define C_DIM3 C_DIMBLUE
#define C_DIM4 C_DIMYELLOW

#define C_BRT1 C_BRTRED
#define C_BRT2 C_BRTGREEN
#define C_BRT3 C_BRTBLUE
#define C_BRT4 C_BRTYELLOW

//endregion

enum GameState_enum {
	STATE_NEW_GAME, // new game, waiting for key
	STATE_REPLAY, // showing sequence
	STATE_USER_INPUT, // waiting for user input of repeated sequence
	STATE_SUCCESS_EFFECT, // entered OK, show some fireworks
	STATE_FAIL_EFFECT, // entered wrong, show FAIL animation, then reset.
};

/** Current game state */
enum GameState_enum GameState = STATE_NEW_GAME;

volatile bool holding_new_game_button = false;

/** Screen colors */
uint32_t screen[4] = {0, 0, 0, 0};
const uint32_t brt[4] = {C_BRT1, C_BRT2, C_BRT3, C_BRT4};
const uint32_t dim[4] = {C_DIM1, C_DIM2, C_DIM3, C_DIM4};
const uint32_t dark[4] = {C_DARK, C_DARK, C_DARK, C_DARK};
const uint32_t dimwhite[4] = {C_DIMWHITE, C_DIMWHITE, C_DIMWHITE, C_DIMWHITE};

#define REPLAY_INTERVAL 400
#define REPLAY_INTERVAL_GAP 75
#define SUC_EFF_TIME 500
#define FAIL_EFF_TIME 1000

/** Nr of revealed colors in sequence */
uint8_t game_revealed_n;
/** Nr of next color to replay/input */
uint8_t game_replay_n;
/** Nr of succ repeated colors */
uint8_t game_repeat_n;

void enter_state(enum GameState_enum state);

/** Show current screen colors */
void show_screen()
{
	leds_set(screen);
}

/** Enter state - callback for delayed state change */
void deferred_enter_state(void *state)
{
	// clear flag that button was held
	holding_new_game_button = false;

	enter_state((enum GameState_enum) state);
}

/** Future task CB in replay seq */
void replay_callback(void *onOff)
{
	bool on = (bool) onOff;

	screen[0] = C_DARK;
	screen[1] = C_DARK;
	screen[2] = C_DARK;
	screen[3] = C_DARK;

	if (on) {
		uint8_t color = rng_next_item();
		game_replay_n++;
		screen[color] = brt[color];
		show_screen();
		schedule_task(replay_callback, (void *) 0, REPLAY_INTERVAL, false);
	} else {
		// turning off
		show_screen();

		// Schedule next turning ON
		if (game_replay_n < game_revealed_n) {
			schedule_task(replay_callback, (void *) 1, REPLAY_INTERVAL_GAP, false);
		} else {
			enter_state(STATE_USER_INPUT);
			//schedule_task(deferred_enter_state, (void *) STATE_USER_INPUT, 50, false);
		}
	}
}

/** SUCCESS effect */
void suc_eff_callback(void *onOff)
{
	bool on = (bool) onOff;

	if (on) {
		display_show_number(game_revealed_n-1);
		for (uint8_t i = 0; i < 4; i++) screen[i] = C_OKGREEN;
		schedule_task(suc_eff_callback, 0, SUC_EFF_TIME, false);
	} else {
		for (uint8_t i = 0; i < 4; i++) screen[i] = C_DARK;

		schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 250, false);
	}

	show_screen();
}

/** ERROR effect */
void fail_eff_callback(void *onOff)
{
	bool on = (bool) onOff;

	if (on) {
		for (int i = 0; i < 4; i++) screen[i] = C_CRIMSON;
		schedule_task(fail_eff_callback, 0, FAIL_EFF_TIME, false);
	} else {
		for (int i = 0; i < 4; i++) screen[i] = C_DARK;

		schedule_task(deferred_enter_state, (void *) STATE_NEW_GAME, 250, false);
	}

	show_screen();
}

/**
 * @brief Enter a game state
 * @param state
 */
void enter_state(enum GameState_enum state)
{
	GameState = state;

	switch (state) {
		case STATE_NEW_GAME:
			usart_puts("State: new game\r\n");
			// new game - idle state before new game is started

			// all dimly lit
			for (int i = 0; i < 4; i++) screen[i] = 0; //C_DIMWHITE
			break;

		case STATE_REPLAY:
			usart_puts("State: replay\r\n");
			game_replay_n = 0;
			rng_restart();

			// Start replay
			replay_callback((void *) 1);
			break;

		case STATE_USER_INPUT:
			usart_puts("State: repeat\r\n");
			memcpy(screen, dim, sizeof(screen));

			// Start entering & checking
			game_repeat_n = 0;
			rng_restart();
			break;

		case STATE_SUCCESS_EFFECT:
			usart_puts("State: succ\r\n");
			memcpy(screen, dim, sizeof(screen));
			//suc_eff_callback((void *) 1);
			schedule_task(suc_eff_callback, (void *) 1, 250, false);
			break;

		case STATE_FAIL_EFFECT:
			usart_puts("State: fail\r\n");
			memcpy(screen, dim, sizeof(screen));
			//fail_eff_callback((void *) 1);
			schedule_task(fail_eff_callback, (void *) 1, 250, false);
			break;
	}

	show_screen();
}

/** Prepare new sequence, using time for seed. */
void prepare_sequence()
{
	rng_set_seed(time_ms);
	rng_restart();
}

volatile uint16_t idle_cnt = 0;

/** game main function */
void game_main(void)
{
	display_show(SEG_G, SEG_G); // two dashes...
	enter_state(STATE_NEW_GAME);
	// we'll init the sequence when user first presses a button - the time is used as a seed

	enum GameState_enum last_state = STATE_NEW_GAME;
	while (1) {
		if (GameState == last_state) {
			if (GameState == STATE_NEW_GAME) {
				if (idle_cnt == 25 && !holding_new_game_button) {
					usart_puts("clear highscore display\r\n");
					display_show(SEG_G, SEG_G);
				}

				if (idle_cnt == 3000) {
					usart_puts("automatic shutdown\r\n");
					screen[0] = C_CRIMSON;
					screen[1] = C_CRIMSON;
					screen[2] = C_CRIMSON;
					screen[3] = C_CRIMSON;
					show_screen();
					delay_s(1);
					pin_down(PIN_PWR_HOLD);
					while(1); // wait for shutdown
				}
			} else {
				if (idle_cnt > 200) {
					// reset state
					usart_puts("game reset, user walked away\r\n");
					enter_state(STATE_NEW_GAME);
					show_screen();
					display_show(SEG_G, SEG_G);
					idle_cnt = 0;
				}
			}
		} else {
			last_state = GameState;
			idle_cnt = 0;
		}

		idle_cnt++;
		delay_ms(100);
	}
}

/**
 * @brief Handle a button press. Callback for debouncer.
 * @param button: button identifier
 * @param press: press state (1 = just pressed, 0 = just released)
 */
void game_button_handler(uint8_t button, bool press)
{
	// convert to 0-3
	button--;

	switch (GameState) {
		case STATE_NEW_GAME:
			if (press) {
				usart_puts("pressed a new-game button\r\n");
				// feedback
				display_show_number(0); // show 0
				holding_new_game_button = true;
			}

			if (!press) { // released
				usart_puts("game begins\r\n");
				// user wants to start playing
				prepare_sequence();
				game_revealed_n = 1; // start with 1 revealed

				// darken
				//memcpy(screen, dark, sizeof(screen));
				//show_screen();

				// start playback with a delay
				// this makes it obvious the playback is not a feedback to the pressed button
				schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 500, false);
				//enter_state(STATE_REPLAY);
			}
			break;

		case STATE_USER_INPUT:
			// Reset idle counter, so it doesn't cut off in the middle of input
			idle_cnt = 0;

			// user is entering a color
			memcpy(screen, dim, sizeof(screen));

			if (press) {
				// Button is down
				screen[button] = brt[button];
			} else {
				// Button is released
				// Verify correctness
				uint8_t expected = rng_next_item();
				if (expected == button) {
					usart_puts("good key!\r\n");
					game_repeat_n++;
					if (game_repeat_n == game_revealed_n) {
						usart_puts("repeated all, good work!\r\n");
						game_revealed_n++;
						enter_state(STATE_SUCCESS_EFFECT);
					}
				} else {
					usart_puts("oops bad key\r\n");
					enter_state(STATE_FAIL_EFFECT);
				}
			}

			show_screen();
			break;

		default:
			usart_puts("discard button press, not expecting input now\r\n");
			break;
	}
}