/**
 *  Triangles
 *  Copyright (C) 2016 POSITIVE MENTAL ATTITUDE
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <cmath>
#include <thread>
#include <sstream>
#include "IngameState.hpp"
#include "Player.hpp"

IngameState::IngameState(): State(), _player(), _paused(false), _blurSize(0.f), _frameAlpha(0.f), _wormhole(), _starsCollected(0) {}

IngameState::~IngameState()
{
	Echo::debug("Flushing state");
	_lightSystem.removeLight(_player.light());
	_lightSystem.removeLight(_wormhole.light());
}

Rect Player::getBounds()
{
	Rect rect;
	sf::Vector2f A = _vertices[0].position;
	sf::Vector2f B = _vertices[1].position;
	sf::Vector2f C = _vertices[2].position;
	
	rect.top = std::min({A.y, B.y, C.y});
	rect.bottom = std::max({A.y, B.y, C.y});
	rect.left = std::min({A.x, B.x, C.x});
	rect.right = std::max({A.x, B.x, C.x});
	
	return rect;
}

void IngameState::init()
{
	_background.setTexture(_context->assets->loadTexture("data/background/Background.jpg"));
	_context->assets->loadTexture("data/background/Background.jpg").setRepeated(true);

	bool illuminati = (rand() % 10 == 0);
	if(_context->nevermore)
	{
		_player.setType(Player::Nevermore);
		_player.setTexture(_context->assets->loadTexture("data/triangle/Nevermore.png"));
	}
	else if(illuminati)
	{
		_player.setType(Player::Illuminati);
		_player.setTexture(_context->assets->loadTexture("data/triangle/Illuminati.png"));
	}
	else
	{
		_player.setType(Player::Tropical);
		_player.setTexture(_context->assets->loadTexture("data/triangle/Tropical.png"));
	}
	_player.setContext(_context); 
	_player.setPosition(556.f, 2200.f);
	 
	_foreground.create("data/background/Foreground.jpg", "data/background/Foreground_normal.jpg", "data/background/75pxjitter03.png");
	_context->foreground = &_foreground;
	
	_background.setTextureRect(sf::IntRect(0, 0, _foreground.getSize().x, _foreground.getSize().y));
	
	_pauseFrame.setFillColor(sf::Color(0, 0, 0, 155));
	_pauseFrame.setPosition(0, 0);
	
	_unshadowShader.loadFromFile("data/light/unshadowShader.frag", sf::Shader::Fragment);
    _lightOverShapeShader.loadFromFile("data/light/lightOverShapeShader.frag", sf::Shader::Fragment);
    _normalsShader.loadFromFile("data/light/normalsShader.frag", sf::Shader::Fragment);
	_penumbraTexture.loadFromFile("data/light/penumbraTexture.png");
	_penumbraTexture.setSmooth(true);
	
	_blurV.loadFromFile("data/shaders/old_pi_blur/blur.vert", "data/shaders/old_pi_blur/blur-v.frag");
	_blurH.loadFromFile("data/shaders/old_pi_blur/blur.vert", "data/shaders/old_pi_blur/blur-h.frag");
	
	_marioSound.setBuffer(_context->assets->loadSound("data/audio/Mario.ogg"));
	_dotaSound.setBuffer(_context->assets->loadSound("data/audio/Victory.ogg"));
	
	_wormhole.load(_context->assets->loadTexture("data/triangle/Wormhole.png"), _context->assets->loadTexture("data/triangle/Wormhole_light.png"));
	_wormhole.setPosition(_foreground.getWormholePosition());
	
	sf::Vector2f starPosition;
	while(_foreground.getStarPosition(starPosition))
		_star.emplace_back(_context->assets->loadTexture("data/triangle/Star.png"), _context->assets->loadTexture("data/light/Star.png"), starPosition);
	_starsCount = _star.size();
	
	_statsTime.setFont(_context->assets->loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_statsHull.setFont(_context->assets->loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_statsStars.setFont(_context->assets->loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_statsTime.setCharacterSize(30);
	_statsHull.setCharacterSize(30);
	_statsStars.setCharacterSize(30);
	
	// Global window is not initialized yet in this method. If you need _context->window->getSize(), go to refresh() instead.
	
	_music.openFromFile("data/audio/Ossuary 6 - Air.ogg");
	_music.setLoop(true);
	_music.play();
	_totalTime.restart();
	_status = State::Ongoing;
}

void IngameState::refresh()
{
	sf::Vector2u res = _context->window->getSize();
	//_background.setTextureRect(sf::IntRect(0, 0, _context->window->getSize().x, _context->window->getSize().y));
	_camera.setSize(static_cast<sf::Vector2f>(_context->window->getSize()));
	_camera.setTwilightViewport(_context->window->getSize().x / 2.f - 240.f/* * (bool) _czyWOgóleTwilightViewportMaByć*/, _context->window->getSize().y / 2.f - 240.f);
	//_camera.setTwilightViewport(0, 0);
	_camera.setCenter(_player.getPosition());
	_foreground.setResolution(res);
	
	_pauseFrame.setSize((sf::Vector2f)res);
	
	_wasted.setScale(_context->window->getSize().x / 1920.f,  _context->window->getSize().y / 1080.f);
	_wasted.setPosition(_context->window->getSize().x / 2.f, _context->window->getSize().y * 1.5f);
	_statsTime.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
	_statsHull.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
	_statsStars.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
	
	_backgroundH.create(_context->window->getSize().x, _context->window->getSize().y);
	_backgroundV.create(_context->window->getSize().x, _context->window->getSize().y);

	_lightSystem.create(sf::FloatRect{{0.f, 0.f}, {0.f, 0.f}}, {_context->window->getSize().x, _context->window->getSize().y}, _penumbraTexture, _unshadowShader, _lightOverShapeShader, _normalsShader);
	_lightSystem.normalsEnabled(true);
	_lightSystem.addLight(_player.light());
	_lightSystem.addLight(_wormhole.light());
	unsigned k = _star.size();
	for(unsigned i = 0; i < k; ++i)
		_lightSystem.addLight(_star[i].light());
}

void IngameState::coreThink(const sf::Event& event)
{
	if(event.type == sf::Event::Closed or (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Escape))
		_status = State::Menu;
	if(event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::P and !_player.isDead())
		_paused = !_paused;
	#ifdef TRIANGLES_DEBUG
	if(event.type == sf::Event::JoystickButtonPressed)
		Echo::debug("Joystick button pressed -- ", event.joystickButton.button);
	#endif
	_player.think(event);
}
void IngameState::coreInput()
{
	if(_paused)
		return; 

	_player.think();
}
void IngameState::coreUpdate(sf::Time delta)
{
	float seconds = delta.asSeconds();
	if(_player.isDead())
	{
		if(_blurSize < 4.f)
			_blurSize += delta.asSeconds();
			
		/** 
		 * This will dynamically fade the grey pause foreground from 0 to 100 alpha.
		 */
		if(_frameAlpha < 100.f)
			_frameAlpha += (delta.asSeconds() * 25.f);
		sf::Color color = _pauseFrame.getFillColor();
		color.a = (int)_frameAlpha;
		_pauseFrame.setFillColor(color);
		
		_blurH.setParameter("blurSize", _blurSize / _context->window->getSize().x);
		_blurV.setParameter("blurSize", _blurSize / _context->window->getSize().y);
		
		if((_wasted.getPosition().y > _context->window->getSize().y / 2.f) 
		|| (_wasted.getPosition().y > -int(_context->window->getSize().y) / 2.f 
		&& _marioSound.getStatus() == sf::Sound::Status::Stopped && !_player.hasWon()))
		{
			_wasted.move(0.f, -400.f * delta.asSeconds() * (_context->window->getSize().y / 1080.f));
			if(_player.hasWon())
			{
				_statsTime.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
				_statsHull.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
				_statsStars.setPosition(_wasted.getPosition().x, _wasted.getPosition().y + 100.f);
			}
		}
		else if(_wasted.getPosition().y < -int(_context->window->getSize().y / 2.f))
			_status = State::Menu;
	}

	if(_paused)
		return;
		
	if(sf::Joystick::isConnected(0))
		_context->window->lockMouse();

	const sf::Vector2f pos = _player.getPosition();
	_player.update(delta);
	_camera.move(_player.getPosition() - pos);
	_foreground.setPosition(_camera.getCenter() - _camera.getSize() / 2.f);
	
	if(_player.checkCollision(_foreground)) /// True if the target died.
	{
		_paused = true;
		_wasted.setTexture(_context->assets->loadTexture("data/background/Wasted.png"));
		_wasted.setOrigin(sf::Vector2f(_context->assets->loadTexture("data/background/Wasted.png").getSize() / 2u));
		_music.stop();
		_marioSound.play();
	}
	else if(_player.isDead() && _player.hasWon())
	{
		_paused = true;
		_wasted.setTexture(_context->assets->loadTexture("data/background/Victory.png"));
		_wasted.setOrigin(sf::Vector2f(_context->assets->loadTexture("data/background/Victory.png").getSize() / 2u));
		std::ostringstream oss;
		int minutesElapsed = int(_totalTime.getElapsedTime().asSeconds()) / 60;
		int secondsElapsed = int(_totalTime.getElapsedTime().asSeconds()) % 60;
		oss << "Time: " << minutesElapsed << ":" << (secondsElapsed < 10 ? "0" : "") << secondsElapsed;
		_statsTime.setString(oss.str());
		oss.str(std::string());
		oss.clear();
		oss << "Hull: " << int(_player.getHullLeft()) << "%";
		_statsHull.setString(oss.str());
		oss.str(std::string());
		oss.clear();
		oss << "Stars: " << _starsCollected << '/' << _starsCount;
		_statsStars.setString(oss.str());
		_statsTime.setOrigin(_statsTime.getLocalBounds().width / 2.f, _statsTime.getLocalBounds().height / 2.f + 60.f);
		_statsHull.setOrigin(_statsTime.getLocalBounds().width / 2.f, _statsTime.getLocalBounds().height / 2.f - _statsHull.getLocalBounds().height + 30.f);
		_statsStars.setOrigin(_statsTime.getLocalBounds().width / 2.f, _statsTime.getLocalBounds().height / 2.f - _statsHull.getLocalBounds().height  - _statsStars.getLocalBounds().height);
		_music.stop();
		_dotaSound.play();
	}
	
	_wormhole.update(delta);
	
	float gravity = _wormhole.gravity(_player.getPosition());
	if(gravity < 1000.f)
	{
		float k = 1000000.f - gravity * gravity;
		if(k > 0.f)
		{
			sf::Vector2f offset = k * seconds * 0.00025f * _wormhole.direction(_player.getPosition());
			_camera.move(offset);
			_player.move(offset);
			_player.rotate(k * seconds * 0.0001f);
		}
		if(gravity < 200.f)
			_player.setScale(gravity / 200.f, gravity / 200.f);
		else
			_player.setScale(1.f, 1.f);
	}
	
	for(unsigned i = 0; i < _star.size(); ++i)
	{
		if(_star[i].gravity(_player.getPosition(), delta))
		{
			_lightSystem.removeLight(_star[i].light());
			_star.erase(_star.begin() + i);
			_starsCollected++;
		}
	}
	
	{
		const double degree = 3.14159265358 / 180.;
		static float offset = 0.f;
		static bool direction = false;
		sf::Vector2f movement;
		movement.x = std::sin((direction ? 152.f : 332.f) * degree) * (direction ? 1500.f : 100.f) * seconds;
		movement.y = -std::cos((direction ? 152.f : 332.f) * degree) * (direction ? 1500.f : 100.f) * seconds;
		offset += movement.y;
		if(offset < -256.f)
			direction = true;
		if(offset >= 0.f)
			direction = false;
	}
}
void IngameState::coreRender(const bool shaders)
{
	if(_player.isDead())
	{
		_backgroundH.draw(_background);
		_backgroundH.setView(*_camera.getView());
		_backgroundH.draw(_foreground);
		_backgroundH.draw(_wormhole);
		_backgroundH.draw(_player);
		sf::Sprite sprite3(_lightSystem.getLightingTexture());
		sf::RenderStates lightRenderStates;
		lightRenderStates.blendMode = sf::BlendMultiply;
		_backgroundH.setView(_context->window->getDefaultView());
		_backgroundH.draw(sprite3, lightRenderStates); 

		sf::Sprite sprite(_backgroundH.getTexture());
		sprite.setScale(1.f, -1.f);
		sprite.setOrigin(0, _context->window->getSize().y);
		sprite.setPosition(0.f, 0.f);
		_backgroundV.draw(sprite, &_blurH);

		sf::Sprite sprite2(_backgroundV.getTexture());
		sprite2.setScale(1.f, -1.f);
		sprite2.setOrigin(0, _context->window->getSize().y);
		sprite2.setPosition(0.f, 0.f);
		_context->window->draw(sprite2, &_blurH);
		_context->window->draw(_pauseFrame);
		_context->window->draw(_wasted);
		_context->window->draw(_statsTime);
		_context->window->draw(_statsHull);
		_context->window->draw(_statsStars);
	}
	else
	{
		_context->window->setView(_camera.getView());
		_context->window->draw(_background);
		
		
		_foreground.renderNormals(true);
		_lightSystem.normalsTargetSetView(*_camera.getView());
		_lightSystem.normalsTargetClear();
		_lightSystem.normalsTargetDraw(_foreground);
		_lightSystem.normalsTargetDisplay();
		_lightSystem.render(*_camera.getView(), _unshadowShader, _lightOverShapeShader, _normalsShader);
		
		
		_foreground.renderNormals(false);
		_context->window->draw(_foreground);
		for(unsigned i = 0; i < _star.size(); ++i)
			_context->window->draw(_star[i]);
		_context->window->draw(_wormhole);
		_context->window->draw(_player);
		
		
		_context->window->setView();
		sf::Sprite sprite(_lightSystem.getLightingTexture());
		sf::RenderStates lightRenderStates;
		lightRenderStates.blendMode = sf::BlendMultiply;
		_context->window->draw(sprite, lightRenderStates); 
		
		if(_paused)
			_context->window->draw(_pauseFrame);
	}
}