/*
 * game_engine.c
 *
 *  Created on: 29 nov. 2019
 *      Author: JackCarterSmith
 */

#include "game_engine.h"
#include "string.h"
#include "stm32412g_discovery.h"
#include "stm32412g_discovery_lcd.h"




RNG_HandleTypeDef RngHandle;
PP_BallTypeDef ballDatas;
PP_BallTypeDef displayedBall;
MenuTypdef currMenu = MENU_MAIN;
Game_ModeTypdef currGameMode = SOLO_MODE;
uint16_t currScore;
uint32_t oldTicks = 0;

/**
  * @brief  Main control byte for game engine
  */
struct gameControlByte {
	unsigned start_disp:1;
	unsigned scoreUpdFlag:1;
	unsigned playerFail:1;
	unsigned reserved3:1;
	unsigned reserved4:1;
	unsigned reserved5:1;
	unsigned reserved6:1;
	unsigned reserved7:1;
} gCtrlByte;


/**
  * @brief  Generate new ball's pos and acc from random seed
  */
void generateNewBall(Game_ModeTypdef type, uint32_t seed) {
	switch (type) {
	case MULTI_MODE:
		break;
	case SOLO_MODE:
	default:
		ballDatas.posX = (seed & 0xFF) % 240;
		ballDatas.posY = ((seed & 0xFF00 >> 7) % 180) + 60;
		if (ballDatas.posX < 0) ballDatas.posX = -ballDatas.posX;
		if (ballDatas.posY < 0) ballDatas.posY = -ballDatas.posY;
		ballDatas.accX = ((seed & 0xFF0000 >> 15) % 8) + 4;
		ballDatas.accY = ((seed & 0xFF000000 >> 31) % 8) + 4;
		if (seed & 0x1) ballDatas.accX = -ballDatas.accX;
		if (seed & 0x2 >> 1) ballDatas.accY = -ballDatas.accY;
		break;
	}
}

/**
  * @brief  Collision and animation engine for the ball/bar
  */
void ballRayTracingEngine(PP_BarTypeDef *barDatas) {
	if (ballDatas.posX + ballDatas.accX > 236) {
		ballDatas.posX = 236;
		ballDatas.accX = -ballDatas.accX;
	} else if (ballDatas.posX + ballDatas.accX < 0) {
		ballDatas.posX = 0;
		ballDatas.accX = -ballDatas.accX;
	} else {
		ballDatas.posX += ballDatas.accX;
	}

	if ((ballDatas.posY + ballDatas.accY > BSP_LCD_GetYSize() - 16) && ballDatas.posX > barDatas->pos - 19 && ballDatas.posX < barDatas->pos + 19) {
		currScore++;
		gCtrlByte.scoreUpdFlag = 1;
		ballDatas.accY = -ballDatas.accY;
	} else {
		if (ballDatas.posY + ballDatas.accY > 236) {
			ballDatas.posY = 236;
			ballDatas.accY = -ballDatas.accY;
		} else if (ballDatas.posY + ballDatas.accY < 26) {
			ballDatas.posY = 26;
			ballDatas.accY = -ballDatas.accY;
		} else {
			ballDatas.posY += ballDatas.accY;
		}
	}
}

/**
  * @brief  Refresh ball displayed on screen
  */
void updateBallDisplay() {
	BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
	BSP_LCD_FillRect(displayedBall.posX, displayedBall.posY, 4, 4);	//Clean previous ball
	BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
	BSP_LCD_FillRect(ballDatas.posX, ballDatas.posY, 4, 4);	//Draw the ball at new pos
	displayedBall.posX = ballDatas.posX;
	displayedBall.posY = ballDatas.posY;

	if (ballDatas.posY >= 236) displayEndMenu();
}

/**
  * @brief  Refresh bar displayed on screen
  */
void updatePongBar(PP_BarTypeDef *barDatas) {
	BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
	BSP_LCD_FillRect(0, BSP_LCD_GetYSize() - 12, BSP_LCD_GetXSize(), 7);	//Clean previous bar
	BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
	if (barDatas->pos + barDatas->acc > BSP_LCD_GetXSize() - 20) {
		barDatas->pos = BSP_LCD_GetXSize() - 20;
	} else if (barDatas->pos + barDatas->acc < 20) {
		barDatas->pos = 20;
	} else {
		barDatas->pos += barDatas->acc;
	}
	BSP_LCD_FillRect(barDatas->pos - 20, BSP_LCD_GetYSize() - 12, 40, 7);	//Draw the bar at new pos
}

/**
  * @brief  Refresh displayed score on game screen
  */
void updateScoreArea(Game_ModeTypdef type, uint16_t newScore) {
	char sTextBuffer[32];

	BSP_LCD_SetFont(&Font24);
	BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
	BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
	sprintf(sTextBuffer, "%d", newScore);
	BSP_LCD_DisplayStringAt(6, 2, (uint8_t *)sTextBuffer, RIGHT_MODE);
	BSP_LCD_FillRect(10, 24, BSP_LCD_GetXSize() - 20, 1);
}

/**
  * @brief  Get a new random number
  */
uint32_t genSeed() {
	uint32_t tmp = 0;

	if (HAL_RNG_Init(&RngHandle) == HAL_OK) {
		tmp = HAL_RNG_GetRandomNumber(&RngHandle);
	} else BSP_LED_On(LED_RED);
	if (HAL_RNG_DeInit(&RngHandle) != HAL_OK) BSP_LED_On(LED_RED);

	return tmp;
}

/**
  * @brief  Main function called when need to update screen
  */
void refreshCurrScreen(uint32_t _t, PP_BarTypeDef *_barDatas) {
	char sTextBuffer[32];

	if (currMenu == MENU_GAME) {
		if (currGameMode == SOLO_MODE) {
			if (_t - oldTicks >= 10 || _t - oldTicks < 0) {
				ballRayTracingEngine(_barDatas);
				if (gCtrlByte.scoreUpdFlag) {
					gCtrlByte.scoreUpdFlag = 0;
					updateScoreArea(SOLO_MODE, currScore);
				}
				updateBallDisplay();
				updatePongBar(_barDatas);

				oldTicks = _t;
			}
		} else if (currGameMode == MULTI_MODE) {
			//Nothing
		}
	} else if (currMenu == MENU_MAIN || currMenu == MENU_END) {
		if (_t - oldTicks >= 30 || _t - oldTicks < 0) {
			if (gCtrlByte.start_disp) {
			  strcpy(sTextBuffer, "           \0");
			  gCtrlByte.start_disp = 0;
			} else if (!gCtrlByte.start_disp) {
			  strcpy(sTextBuffer, "Press START\0");
			  gCtrlByte.start_disp = 1;
			}
			BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - 5 * 12, BSP_LCD_GetYSize() / 2 + 40, (uint8_t *)sTextBuffer, LEFT_MODE);

			oldTicks = _t;
		}
	}
}

/**
  * @brief  Just a cool screen to display when the board start
  */
void displayLoadingScreen() {
	char sTextBuffer[32];

	BSP_LCD_SetBackColor(LCD_COLOR_BLACK);
	BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
	BSP_LCD_SetFont(&Font16);
	strcpy(sTextBuffer, "2P - ProjectPong\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 48, (uint8_t *)sTextBuffer, LEFT_MODE);

	/* Print loading screen */
	strcpy(sTextBuffer, "Loading...\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 + 40, (uint8_t *)sTextBuffer, LEFT_MODE);

	HAL_Delay(2000);
}

void displayStartMenu(PP_HighScoresTypeDef hsDatas) {
	char sTextBuffer[32];

	currMenu = MENU_MAIN;

	BSP_LCD_Clear(LCD_COLOR_BLACK);
	BSP_LCD_SetBackColor(LCD_COLOR_BLACK);
	BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
	BSP_LCD_SetFont(&Font16);

	strcpy(sTextBuffer, "High Scores:\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 70, (uint8_t *)sTextBuffer, LEFT_MODE);
	sprintf(sTextBuffer, "Solo - %d", hsDatas.Solo_HS);
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 54, (uint8_t *)sTextBuffer, LEFT_MODE);
	sprintf(sTextBuffer, "Multi - %d", hsDatas.Multi_HS);
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 40, (uint8_t *)sTextBuffer, LEFT_MODE);
	BSP_LCD_FillRect(BSP_LCD_GetXSize() / 2 - 20, BSP_LCD_GetYSize() - 12, 40, 7);

	/* READY TO START GAME! */
	strcpy(sTextBuffer, "Press START\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - 5 * 12, BSP_LCD_GetYSize() / 2 + 40, (uint8_t *)sTextBuffer, LEFT_MODE);
}

/**
  * @brief  Generate and display new game
  */
void displayGameArea(Game_ModeTypdef type, PP_BarTypeDef *_barDatas) {
	uint32_t seed = 0;
	currScore = 0;
	_barDatas->pos = BSP_LCD_GetXSize() / 2;
	gCtrlByte.scoreUpdFlag = 1;
	gCtrlByte.playerFail = 0;

	currMenu = MENU_GAME;

	BSP_LCD_Clear(LCD_COLOR_BLACK);
	BSP_LCD_FillRect(BSP_LCD_GetXSize() / 2 - 20, BSP_LCD_GetYSize() - 12, 40, 7);
	seed = genSeed();

	switch (type) {
	case MULTI_MODE:
		break;
	case SOLO_MODE:
	default:
		BSP_LCD_SetBackColor(LCD_COLOR_BLACK);
		BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
		updateScoreArea(type, 0);
		break;
	}
	generateNewBall(type,seed);
	updateBallDisplay();
}

void displayEndMenu() {
	char sTextBuffer[32];

	currMenu = MENU_END;

	BSP_LCD_SetFont(&Font16);

	strcpy(sTextBuffer, "YOU HAVE FAILED!\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 54, (uint8_t *)sTextBuffer, LEFT_MODE);
	sprintf(sTextBuffer, "SCORE: %d", currScore);
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - (strlen(sTextBuffer)/2 * BSP_LCD_GetFont()->Width), BSP_LCD_GetYSize() / 2 - 40, (uint8_t *)sTextBuffer, LEFT_MODE);

	//TODO: Save new HighScore in EEPROM if greather than the old one

	/* READY TO START NEW GAME! */
	strcpy(sTextBuffer, "Press START\0");
	BSP_LCD_DisplayStringAt(BSP_LCD_GetXSize() / 2 - 5 * 12, BSP_LCD_GetYSize() / 2 + 40, (uint8_t *)sTextBuffer, LEFT_MODE);
}

MenuTypdef getCurrMenu() {
	return currMenu;
}
