#include <Game.h>
#include <vector>

#include <Util.h>
#include <Random.h>

#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include <imgui.h>

#include <algorithm>
#include <iostream>

Ball::Ball(sf::Vector2f position_in, sf::Vector2f vel_in)
	: velocity(vel_in)
	, pos(position_in)
{ }

Brick::Brick(sf::Vector2f position_in)
	: pos(position_in)
{ }

Player::Player(sf::Vector2f position_in)
	: pos(position_in)
{ }


Game::Game()
	: window({sf::VideoMode({ 1920u, 1080u }), "breakout"})
{ 
	window.setFramerateLimit(frameRate);

	if (!ImGui::SFML::Init(window)) return;

	player.pos = playerStartPos;

	tempCircle.setOrigin({ballRadius, ballRadius});

	bricks.reserve(totalBricks);
	bricks.emplace_back(sf::Vector2f{200, 100});
	specialBricks.reserve(totalSpecialBricks);
	balls.reserve(10);
	ballsToAdd.reserve(10);

	ballsToAdd.emplace_back(playerStartPos.x, playerStartPos.y - 300);
}

void Game::input()
{
	while (const std::optional event = window.pollEvent())
	{
		ImGui::SFML::ProcessEvent(window, *event);

		if (event->is<sf::Event::Closed>())
		{
			window.close();
		}

		if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>())
		{
			switch (keyPressed->scancode) 
			{
				case sf::Keyboard::Scan::Escape:
					window.close();
					break;
				
				case sf::Keyboard::Scan::Left:
				case sf::Keyboard::Scan::A:
					player.left = true;
					break;

				case sf::Keyboard::Scan::Right:
				case sf::Keyboard::Scan::D:
					player.right = true;
					break;

				case sf::Keyboard::Scan::F1:
					windowCollasped = !windowCollasped;
					break;
					
				default:
					break;
			}
		}

		if (const auto* keyReleased = event->getIf<sf::Event::KeyReleased>())
		{
			switch (keyReleased->scancode) 
			{
				case sf::Keyboard::Scan::Left:
				case sf::Keyboard::Scan::A:
					player.left = false;
					break;

				case sf::Keyboard::Scan::Right:
				case sf::Keyboard::Scan::D:
					player.right = false;
					break;

				default:
					break;
			}
		}

		if (const auto* MouseButtonPressed = event->getIf<sf::Event::MouseButtonPressed>())
		{
			switch (MouseButtonPressed->button) 
			{
			case sf::Mouse::Button::Left:
			specialBricks.emplace_back((sf::Vector2f)MouseButtonPressed->position);
			break;
			default:
			break;
			}

		}
	}	
}

void Game::collision()
{
	
	checkBallCollision();
	
	const sf:: FloatRect playerBounds = {player.pos, playerHalfSize};
	//player wall collide
	if (playerBounds.position.x + playerBounds.size.x > window.getSize().x)
	{
		player.pos.x = window.getSize().x - playerHalfSize.x;
	}
	
	if (playerBounds.position.x < playerHalfSize.x)
	{
		player.pos.x = playerHalfSize.x;
	}
}

void Game::checkBallCollision()
{
	for (auto& ball : balls)	
	{
		const sf::FloatRect ballBounds = {ball.pos, {ballRadius, ballRadius}};
		const sf::FloatRect previousBallBounds = {ball.previousPos, {ballRadius, ballRadius}};

		for (auto& brick : bricks)
		{
			const sf::FloatRect brickBounds = {brick.pos, brickHalfSize};
			auto intersect = getOverlap(ballBounds, brickBounds);
			auto previousIntersect = getOverlap(previousBallBounds, brickBounds);

			if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0)
			{
				brick.alive = false;
				ball.velocity.x *= -1;
				if (ball.pos.x < brick.pos.x)
				{
					ball.pos.x -= intersect.x;
				}
				else 
				{
					ball.pos.x += intersect.x;
				}
			}

			// coming from y direction
			if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
			{
				brick.alive = false;
				ball.velocity.y *= -1;
				if (ball.pos.y < brick.pos.y)
				{
					ball.pos.y -= intersect.y;
				}
				else 
				{
					ball.pos.y += intersect.y;
				}
			}
		}

		for (auto& brick : specialBricks)
		{
			const sf::FloatRect brickBounds = {brick.pos, brickHalfSize};
			const auto intersect = getOverlap(ballBounds, brickBounds);
			const auto previousIntersect = getOverlap(previousBallBounds, brickBounds);
			bool spawnExtraBall{false};

			// coming from x direction
			if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0)
			{
				brick.alive = false;
				spawnExtraBall = true;
				ball.velocity.x *= -1;
				if (ball.pos.x < brick.pos.x)
				{
					ball.pos.x -= intersect.x;
				}
				else 
				{
					ball.pos.x += intersect.x;
				}
			}

			// coming from y direction
			if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
			{
				brick.alive = false;
				spawnExtraBall = true;
				ball.velocity.y *= -1;
				if (ball.pos.y < brick.pos.y)
				{
					ball.pos.y -= intersect.y;
				}
				else 
				{
					ball.pos.y += intersect.y;
				}
			}

			if (spawnExtraBall)
			{
				ballsToAdd.emplace_back(ball.pos);
			}
		}

		
		//wall collide
		if (ballBounds.position.y + ballBounds.size.y > window.getSize().y)
		{
			ball.alive = false;
		}

		if (ball.pos.x - ballRadius <= 0)
		{
			ball.pos.x = ballRadius;
			ball.velocity.x *= -1;
		}

		if (ball.pos.x + ballRadius >= window.getSize().x)
		{
			ball.pos.x = window.getSize().x - ballRadius;
			ball.velocity.x *= -1;
		}

		if (ball.pos.y - ballRadius < 0)
		{
			ball.pos.y = ballRadius;
			ball.velocity.y *= -1;
		}

		// player collide
		const sf:: FloatRect playerBounds = {player.pos, playerHalfSize};
		const sf:: FloatRect previousPlayerBounds = {player.previousPos, playerHalfSize};
		
		auto intersect = getOverlap(ballBounds, playerBounds);
		auto previousIntersect = getOverlap(previousBallBounds, previousPlayerBounds);

		// coming from x direction
		if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0)
		{
			ball.velocity.x *= -1;
			if (ball.pos.x < player.pos.x)
			{
				ball.pos.x -= intersect.x;
			}
			else 
			{
				ball.pos.x += intersect.x;
			}
		}

		// coming from y direction
		if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
		{
			ball.velocity.y *= -1;
			if (ball.pos.y < player.pos.y)
			{
				ball.pos.y -= intersect.y;
			}
			else 
			{
				ball.pos.y += intersect.y;
			}
		}

	}
}

void Game::updateEntities()
{
	balls.erase(std::remove_if(balls.begin(), balls.end(), [](const Ball& ball){ return !ball.alive; }), balls.end());
	bricks.erase(std::remove_if(bricks.begin(), bricks.end(), [](const Brick& brick){ return !brick.alive; }), bricks.end());
	specialBricks.erase(std::remove_if(specialBricks.begin(), specialBricks.end(), [](const Brick& brick){ return !brick.alive; }), specialBricks.end());

	const auto limit = ballsToAdd.size();

	for (int i = 0; i < limit; i++)
	{
		balls.emplace_back(ballsToAdd[i], velocityInRandomDir(ballMaxSpeed / numPhysicsUpdates));
		ballsToAdd[i] = {0,0};
	}

	ballsToAdd.clear();
}

void Game::checkEndGame()
{
	if (balls.empty())
	{
		player.lives--;
		ballsToAdd.emplace_back(sf::Vector2f{player.pos.x, player.pos.y - 500});
	}


	if (player.lives <= 0)
	{

	}
}

void Game::movement()
{
	for (auto& ball : balls)
	{
		ball.previousPos = ball.pos;
		ball.pos += ball.velocity;
	}

	int dir = player.right - player.left;

	player.previousPos = player.pos;
	player.pos.x += (playerSpeed / numPhysicsUpdates) * dir;
}

void Game::imgui()
{
	ImGui::SFML::Update(window, clock.restart());
	
	ImGui::Begin("menu");
	ImGui::SetWindowCollapsed(windowCollasped);
	ImGui::Text("Controls:");
	ImGui::Indent();
	ImGui::Text("A/D or arrow keys: move left and right");
	ImGui::Text("R: reset game");
	ImGui::Text("F1: expand/collaspe this window");
	ImGui::Text("left click: spawn brick");
	ImGui::Unindent();

	ImGui::SliderFloat("Player Speed", &playerSpeed, 1.f, 50.f);
	ImGui::SliderFloat("Ball Speed", &ballMaxSpeed, 1.f, 50.f);
	if (ImGui::SliderFloat("Player Length", &playerSize.x, 1.f, 600.f))
	{
		playerHalfSize.x = playerSize.x / 2;
	}

	ImGui::ColorEdit3("Player Color", playerColor.imgui());
	ImGui::ColorEdit3("Ball Color", ballColor.imgui());
	ImGui::ColorEdit3("Brick Color", brickColor.imgui());
	ImGui::ColorEdit3("Special Brick Color", specialBrickColor.imgui()); 

	ImGui::End();
}

void Game::render()
{
		window.clear();
		
		tempRect.setOrigin(brickHalfSize);
		tempRect.setSize(brickSize);
		for (auto& brick : bricks)
		{
			tempRect.setPosition(brick.pos);
			tempRect.setFillColor(brickColor.sfml());
			window.draw(tempRect);
		}
		
		for (auto& brick : specialBricks)
		{
			tempRect.setPosition(brick.pos);
			tempRect.setFillColor(specialBrickColor.sfml());
			window.draw(tempRect);
		}
		
		tempRect.setOrigin(playerHalfSize);
		tempRect.setSize(playerSize);
		tempRect.setPosition(player.pos);
		tempRect.setFillColor(playerColor.sfml());
		window.draw(tempRect);

		tempCircle.setRadius(ballRadius);
		for (auto& ball : balls)
		{
			tempCircle.setPosition(ball.pos);
			tempCircle.setFillColor(ballColor.sfml());
			window.draw(tempCircle);
		}

		ImGui::SFML::Render(window);
		
		window.display();
}

void Game::run()
{
	while(window.isOpen())
	{		
		input();

		for (int i = 0; i <= numPhysicsUpdates; i++)
		{
			updateEntities();

			movement();
			
			collision();
		}

		checkEndGame();
		
		imgui();

		render();
	}

	ImGui::SFML::Shutdown();
}
