// Included with assignment
#include <iostream>
#include <iomanip>
#include <vector>
#include <cstring>
#include <tuple>

// Included by 21015407 GARETH CHAPPELL
// For rand() function
#include <stdlib.h>

// For time value
#include <time.h>

// Included with assignment
// Accessing other files
#include <context.hpp>
#include <app.hpp>

using namespace std;

// NOTE:
// I *still* can't get the batch files to work, and I don't know why.

// Init values used to determine the width/
// Modified the window dimension values from what was assigned by the assignment, so they're easier to work with in setting up UI and math stuff.
const int width = 800;
const int height = 600;

// GAME SCREENS

// 1. Title Screen - The player starts on the title screen. When they press A or D, the title screen closes, and the gameplay begins.

// 2. Gameplay - The player controls a paddle with the A/D keys. The player must keep hitting a moving ball with the paddle
// to prevent it from hitting the bottom of the screen, which ends the gameplay and displays the game over screen. A timer is displayed on the
// top left of the screen, indicating the player's score.

// 3. Game Over - When the player fails at the gameplay, the game over screen is displayed, showing their time score. Player can then press A or D
// to play again.

class MyApp: public uwe::App
{

private:

// Init int value for generating random numbers
int random_x;

// Init values for top left of ball
int ball_x_topleft;
int ball_y_topleft;

// Init value for top left of paddle
int paddle_x_topleft;

// Init int values for ball X/Y co-ordinates
int ball_x;
int ball_y;

// Init int values for ball X/Y gradient values
// These values are incremented/decremented every minute, increasing the difficulty over time.
int ball_gradient_x;
int ball_gradient_y;

// Init int value for paddle X co-ordinates
// Paddle Y is always 650
int paddle_x;

// Init boolean flags for game screens
bool title_active = true;
bool gameplay_active = false;
bool gameover_active = false;

// Init int value for timer, in seconds and minutes, and a counter to add 1 second per 60 frames.
int counter;
int time_seconds;
int time_minutes;

public:
    MyApp(int width, int height, std::string title);
    ~MyApp();

    void begin() override;
    void update() override;
    void draw() override;

    void key_pressed(uwe::Scancode scancode, bool repeat) override;

    void mouse_pressed(int x, int y, uwe::Button button) override;
    void mouse_released(int x, int y, uwe::Button button) override;
    void mouse_moved(int x, int y) override;
};

MyApp::MyApp(int width, int height, std::string title)
{
    init(width, height, title);
}

MyApp::~MyApp()
{

}

void MyApp::begin()
{
	// Init the random number seed with the time value.
	srand (time(NULL));

	// Init the font used for UI text.
	UI_font = create_font("./assets/fonts/FreeSans.ttf", 15, uwe::Colour::white());
	
	// Adjust ball X/Y values
	ball_x = 400;
	ball_y = 600;
	
	// Randomise the X gradient for the start.
	random_x = rand() % 1 - 2;
	
	switch (random_x)
	{
		case 1:
		ball_gradient_x = -1;
		break;
		
		case 2:
		ball_gradient_x = 1;
		break;
	}
	
	// Set the Y gradient for the ball.
	ball_gradient_y = -1;
	
}

// This functions occur every frame (60 times per second)
void MyApp::update()
{
	if (gameplay_active == true)
	{
		// Update paddle topleft
		paddle_x_topleft = paddle_x - 25;
		
		// Update ball topleft
		ball_x_topleft = ball_x - 10;
		ball_y_topleft = ball_y - 10;
		
		// Update ball X/Y
		ball_x = ball_x + ball_gradient_x;
		ball_y = ball_y + ball_gradient_y;
		
		// If counter value is 60
		// (i.e Every second)
		if (counter == 60)
		{
			// Increment seconds timer, reset counter
			timer_seconds++;
			counter = 0;

			// Adjust the gradients (in other words, the speed of the ball)
			// Increment ball gradient X if positive
			if (ball_gradient_x > 0)
			{
				ball_gradient_x++;
			}

			// Decrement ball gradient X if positive
			if (ball_gradient_x < 0)
			{
				ball_gradient_x--;
			}

			// Increment ball gradient Y if positive
			if (ball_gradient_y > 0)
			{
				ball_gradient_y++;
			}

			// Decrement ball gradient Y if positive
			if (ball_gradient_y < 0)
			{
				ball_gradient_y--;
			}
		}

		// If timer seconds value is 60
		if (timer_seconds == 60)
		{

			// Increment minute timer, reset seconds
			timer_minutes++;
			timer_seconds = 0;
		}

		// If timer minutes is greater than 99, which would cause a minor visual error.
		if (timer_minutes > 99)
		{
			timer_seconds = 0;
			timer_minutes = 0;
		}

		// If ball is touching either left wall and right wall, and not touching the top or bottom wall.
		if ((ball_x == 120 || ball_x == 680) && (ball_y != 680 && ball_y != 120))
		{
			// Multiply the X gradient by -1 to 'invert' the X gradient
			// (i.e If the X gradient is 1, it is now -1. If the X gradient is -1, it is now 1.)
			ball_gradient_x = ball_gradient_x * -1;
		}

		// If ball is not touching either the left or right wall, and touching the top wall.
		if ((ball_x != 120 && ball_x != 680) && ball_y == 120)
		{
			// Multiply the Y gradient by -1 to invert the Y gradient.
			// (i.e If the Y gradient is 1, it is now -1. If the Y gradient is -1, it is now 1.
			ball_gradient_y = ball_gradient_y * -1;

			// 50% chance to invert the X gradient.
			random_x = rand() % 1 - 2;

			switch (random_x)
			{
				case 1:
				ball_gradient_x = ball_gradient_x * -1;
				break;

				case 2:
				break;
			}
		}

		// If the ball is touching the left or right wall, and is touching the top wall (i.e a perfect corner shot)
		if ((ball_x == 120 || ball_x == 680) && ball_y == 120)
		{
			// Invert both gradients
			ball_gradient_x = ball_gradient_x * -1;
			ball_gradient_y = ball_gradient_y * -1;
		}

		// If the ball is touching the bottom wall (game over)
		if (ball_y == 680)
		{
			gameplay_active = false;
			gameover_active = true;
		}

		// If ball is touching the top of the paddle, and within the X co-ordinate of the paddle
		if (ball_y == 630 && (ball_x > paddle_x - 50) && (ball_x < paddle_x + 50))
		{
			// Invert the Y gradient
			ball_gradient_y = ball_gradient_y * -1;

			// 50% chance to invert the X gradient, introducing a degree of randomness to the game
			random_x = rand() % 1 - 2;

			switch (random_x)
			{
				case 1:
				ball_gradient_x = ball_gradient_x * -1;
				break;

				case 2:
				break;
			}

		}


	}
}

// When a key is pressed, this function is called.
void MyApp::key_pressed(uwe::Scancode scancode, bool repeat)
{
    switch (scancode)
	{
		// If 'A' key is pressed
		case uwe::Scancode::A:
		{
			// If title active
			if (title_active == true)
			{
				gameplay_active = true;
				title_active = false;
			}

			// If gameplay active and the paddle isn't touching the left wall
			if (gameplay_active == true && paddle_x != 125)
			{
				paddle_x--;
			}

			// If gameover active
			if (gameover_active == true)
			{
				gameplay_active = true;
				gameover_active = false;
			}

		}

		// If 'D' key is pressed
		case uwe::Scancode::D:
		{
			// If title active
			if (title_active == true)
			{
				gameplay_active = true;
				title_active = false;
			}

			// If gameplay active and the paddle isn't touching the right wall
			if (gameplay_active == true && paddle_x != 675)
			{
				paddle_x++;
			}

			// If gameover active
			if (gameover_active == true)
			{
				gameplay_active = true;
				gameover_active = false;
			}
		}

         default:
		{
            // nothing see here
        }
    }
}

// When the mouse button is pressed, this function is called.
void MyApp::mouse_pressed(int x, int y, uwe::Button button)
{
	// Unused.
}

// When the mouse button is released, this function is called.
void MyApp::mouse_released(int x, int y, uwe::Button button)
{
	// Unused.
}

// When the mouse is moved, this function is called.
void MyApp::mouse_moved(int x, int y)
{
	// Unused.
}

// This function is called per frame, to draw the contents of the window.
void MyApp::draw()
{
	// Cover the screen in black
	// This happens with every frame to avoid visual cluttering.
    clear(uwe::Colour::black());

	// Set the draw colour as white
	set_draw_color(uwe::Colour(255,255,255,255));

	// Title Screen
	if (title_active == true)
	{
		
		// Draw text
		draw_font(UI_Font, "Bouncy Ball Game!", 100, 100);
		draw_font(UI_Font, "Instructions:", 100, 125);
		draw_font(UI_Font, "> Keep the ball away from the bottom of the screen for as long as possible. Hit it with your paddle!", 100, 150);
		draw_font(UI_Font, "> Use the A and D keys to move the paddle left and right.", 100, 175);
		draw_font(UI_Font, "> Good luck!", 100, 200);
		
	}

	// Gameplay
	if (gameplay_active == true)
	{
		
		// Timer
		draw_font(UI_Font, timer_minutes, 100, 50);
		draw_font(UI_Font, ":", 130, 50);
		draw_font(UI_Font, timer_seconds, 100, 50);
		
		// Boundary
		draw_line(100, 100, 700, 100); // Top left to top right
		draw_line(700, 100, 700, 700); // Top right to bottom right
		draw_line(700, 700, 100, 700); // Bottom right to bottom left
		draw_line(100, 700, 100, 100); // Bottom left to top left
		
		// Ball
		draw_rect(ball_x_topleft, ball_y_topleft, 20, 20);
		
		// Paddle
		draw_rect(paddle_x_topleft, 650, 50, 10);
		
	}

	if (gameover_active == true)
	{
		
		// Draw text
		draw_font(UI_Font, "Game over!", 100, 100);
		draw_font(UI_Font, "Score:", 100, 125);
		draw_font(UI_Font, timer_minutes, 100, 150);
		draw_font(UI_Font, "minutes!", 145, 150);
		draw_font(UI_Font, "Press A or D to restart!", 100, 175);
		
	}

}

int main(int argc, char *argv[]) {
    uwe::App* app = new MyApp{width, height, "21015407 Assignment RESIT - BOUNCY BALL"};

    app->run();

    return 0;
}