/**
 *  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 "Player.hpp"
#include "Assets.hpp"

Player::Player(): 
	Triangle(), 
	InputTarget(_inputMap), 
	Collidable(),
	_movement(0.f), 
	_movementSpeed(2.f), 
	_movementMomentum(0.f),
	_movementAcceleration(10.f),
	_movementSuppresion(10.f),
	_rotation(0.f), 
	_rotationSpeed(0.6f), 
	_rotationMomentum(0.f),
	_rotationAcceleration(38.f),
	_rotationSuppresion(20.f),
	_hit(false),
	_dead(false),
	_particleSystem(1920, 1080)
{
	/**
	 *  Default binding
	 */
	 if(sf::Joystick::isConnected(0))
	 {
		_inputMap.map(1, Input(sf::Joystick::Axis::Y, false));
		_inputMap.map(2, Input(sf::Joystick::Axis::Y, true));
		_inputMap.map(3, Input(sf::Joystick::Axis::Z, false));
		_inputMap.map(4, Input(sf::Joystick::Axis::Z, true));
		_inputMap.map(5, Input(7));
	 }
	 else
	 {
		_inputMap.map(1, Input(sf::Keyboard::W));
		_inputMap.map(2, Input(sf::Keyboard::S));
		_inputMap.map(3, Input(sf::Keyboard::A));
		_inputMap.map(4, Input(sf::Keyboard::D));
		_inputMap.map(5, Input(sf::Keyboard::Space));
	 }
	
	/**
	 *  Input lambdas
	 */
	bind(1, [this](const sf::Event&, float power)
	{
		_movement += power;
	}); 
	bind(2, [this](const sf::Event&, float power)
	{
		_movement -= power * 0.8f;
	});
	bind(3, [this](const sf::Event&, float power)
	{
		_rotation -= power;
	});
	bind(4, [this](const sf::Event&, float power)
	{
		_rotation += power;
	});
	bind(5, [this](const sf::Event&, float)
	{
		if(_bulletTimer.getElapsedTime().asMilliseconds() > 1500)
		{
			// Does not check if _context != nullptr
			// so please do remember to call setContext().
			_bullets.emplace_back(getRotation(), getPosition(), 
								_context->assets->loadSound("data/audio/Shotgun.ogg"), 
								_context->assets->loadSound("data/audio/Bomb.ogg"),
								_context->foreground);
			_bullets.back().setTexture(_innerTexture);
			_bulletTimer.restart();
			_movementMomentum -= 3.f;
		}
	});
	
	/**
	*  Particles
	*/

    _particleSystem.setDissolutionRate(255.f);

	/**
	 *  Technical
	 */
	
	genCollisionBox("data/hitbox/Player.png", sf::Vector2f(71.f, 100.f));
	
	_bullets.reserve(10);
	_bulletTimer.restart();
	 
	_durability = 0.99f; 
}

void Player::setContext(Context* context)
{
	_context = context;
	_glass.setBuffer(_context->assets->loadSound("data/audio/GlassHit.ogg"));
	_deathSound.setBuffer(_context->assets->loadSound("data/audio/GlassRekt.ogg"));
}

bool Player::isDead()
{
	return _dead;
}

void Player::setTexture(sf::Texture& texture)
{
	_innerTexture = texture;
	_image = texture.copyToImage();
	_texture = &_innerTexture;
	vertexFit();
	
	unsigned pointCount = 0;
	for(unsigned i = 0; i < _image.getSize().x; ++i)
		for(unsigned j = 0; j < _image.getSize().y; ++j)
			if(_image.getPixel(i, j).a > 0)
				++pointCount;
	setPointCount(pointCount);
}

bool Player::checkCollision(Foreground& foreground)
{
	updateTransform(getTransform());
	int k = pixelPerfect(foreground);
	if(k == -1)
	{
		if(_glass.getStatus() != sf::Sound::Status::Playing)
			_glass.setVolume(0.f);
		_hit = false;
		return false;
	}
	float i = 10.f;
	while(k != -1)
	{
		sf::Vector2f pos = _cmap[k];
		sf::Vector2f center = sf::Vector2f(_image.getSize().x / 2.f, 100.f);
		if(center.x + pos.x >= 0 && center.x + pos.x < _image.getSize().x and center.y + pos.y >= 0 && center.y + pos.y < _image.getSize().y)
			_image.setPixel(center.x + pos.x, center.y + pos.y, sf::Color(0, 0, 0, 0));
		for(int i = -1; i <= 1; i++)
			for(int j = -1; j <= 1; j++)
			{
				if(i == 0 && j == 0) 
					continue;
				if(pos.x + center.x + i < 0 || pos.y + center.y + j < 0 or pos.x + center.x + i > _image.getSize().x || pos.y + center.y + j > _image.getSize().y)
					continue;
				if(_image.getPixel(pos.x + center.x + i, pos.y + center.y + j).a > 0)
				{
					sf::Vector2f vec;
					vec.x = pos.x + i;
					vec.y = pos.y + j;
					if(std::find(_cmap.begin(), _cmap.end(), vec) == _cmap.end())
						if(std::find(_cbanned.begin(), _cbanned.end(), vec) == _cbanned.end())
							_cmap.push_back(vec);
				}
			}
		_cmap.erase(_cmap.begin() + k);
		_cbanned.push_back(pos);
		k = pixelPerfect(foreground);
		if(i < 100.f) i += 0.1f;
	}
	if(i > _glass.getVolume())
		_glass.setVolume(i);
	if(_glass.getStatus() != sf::Sound::Status::Playing) _glass.play();
	
	_innerTexture.loadFromImage(_image);
	_hit = true;
	if(_pointCount * _durability < _cbanned.size())
	{
		_dead = true;
		_deathSound.play();
		return true;
	}
	else
		return false;
}

void Player::keyboardControls(sf::Time delta)
{
	float seconds = delta.asSeconds();
	const double degree = 3.14159265358 / 180.;
	
	if(_hit)
	{
		if(_movement > 0 || _movementMomentum > 0)
			_movementMomentum -= seconds * 15.f;
		if(_movement < 0 || _movementMomentum < 0)
			_movementMomentum += seconds * 15.f;
		_movement *= 0.6f;
	}

	_movementMomentum += _movementAcceleration * seconds * _movement;

	if(_movement == 0.f)
	{
		if(_movementMomentum > _movementSuppresion * seconds)
			_movementMomentum -= _movementSuppresion * seconds;
		else if(_movementMomentum < -_movementSuppresion * seconds)
			_movementMomentum += _movementSuppresion * seconds;
		else
			_movementMomentum = 0.f;
	}

	sf::Vector2f movement;
	movement.x = std::sin(getRotation() * degree) * 60.f * _movementSpeed * _movementMomentum * seconds;
	movement.y = -std::cos(getRotation() * degree) * 60.f * _movementSpeed * _movementMomentum * seconds;
	
	move(movement);

	_particleSystem.fuel(movement.x * 20.f, getRotation());
	_particleSystem.fuel(movement.y * 20.f, getRotation());

	_movement = 0.f;

	_rotationMomentum += _rotationAcceleration * seconds * _rotation;

	if(_rotationMomentum > _rotationSuppresion * seconds)
		_rotationMomentum -= _rotationSuppresion * seconds;
	else if(_rotationMomentum < -_rotationSuppresion * seconds)
		_rotationMomentum += _rotationSuppresion * seconds;
	else
		_rotationMomentum = 0.f;

	float rotation = 90.f * _rotationSpeed * _rotationMomentum * seconds;
	rotate(rotation);

	_rotation = 0.f;
}

void Player::padControls(sf::Time delta)
{
	float seconds = delta.asSeconds();
	const double degree = 3.14159265358 / 180.;
	
	sf::Vector2f v = sf::Vector2f(
	sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::X) / 100.f, 
	sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::Y) / 100.f);
	
	float curr = getRotation();
	static float supposed;
	if(v.x != 0 or v.y != 0)
		supposed = atan2(v.x, -v.y) / degree;

	if(supposed < 0.f) 
		supposed += 360.f;

	float c = supposed - curr;
	if(curr > 180.f && supposed < 180.f)
	{
		float a = (supposed - curr);
		float b = (supposed - curr + 360.f);
		if(std::fabs(a) < std::fabs(b))
			c = a;
		else
			c = b;
	}
	if(curr < 180.f && supposed > 180.f)
	{
		float a = (supposed - curr);
		float b = (supposed - curr - 360.f);
		if(std::fabs(a) < std::fabs(b))
			c = a;
		else
			c = b;
	}
	
	if(v.x != 0 or v.y != 0) 
	{
		if(c > 0.f)
			_rotationMomentum += _rotationAcceleration * seconds;
		else if(c < 0.f)
			_rotationMomentum -= _rotationAcceleration * seconds; 
	}
		
	if(_rotationMomentum > _rotationSuppresion * seconds)
		_rotationMomentum -= _rotationSuppresion * seconds;
	else if(_rotationMomentum < -_rotationSuppresion * seconds)
		_rotationMomentum += _rotationSuppresion * seconds;
	else
		_rotationMomentum = 0.f;
		
	float rotation = 90.f * _rotationSpeed * _rotationMomentum * seconds;
	
	rotate(rotation);
	
	if((v.x != 0 || v.y != 0) && c < 1.f)
		_movement = std::fabs(v.x + v.y);
	
	if(_hit)
	{
		if(_movement > 0 || _movementMomentum > 0)
			_movementMomentum -= seconds * 15.f;
		if(_movement < 0 || _movementMomentum < 0)
			_movementMomentum += seconds * 15.f;
		_movement *= 0.6f;
	}

	_movementMomentum += _movementAcceleration * seconds * _movement;

	if(_movement == 0.f)
	{
		if(_movementMomentum > _movementSuppresion * seconds)
			_movementMomentum -= _movementSuppresion * seconds;
		else if(_movementMomentum < -_movementSuppresion * seconds)
			_movementMomentum += _movementSuppresion * seconds;
		else
			_movementMomentum = 0.f;
	}

	sf::Vector2f movement;
	movement.x = std::sin(getRotation() * degree) * 60.f * _movementSpeed * _movementMomentum * seconds;
	movement.y = -std::cos(getRotation() * degree) * 60.f * _movementSpeed * _movementMomentum * seconds;
	
	move(movement);
	
	_particleSystem.fuel(movement.x * 20.f, getRotation());
	_particleSystem.fuel(movement.y * 20.f, getRotation());

	_movement = 0.f;
}

void Player::update(sf::Time delta)
{	
	//if(sf::Joystick::isConnected(0))
		//padControls(delta);
	//else
		keyboardControls(delta);
	
	for(unsigned i = 0; i < _bullets.size(); ++i)
	{
		_bullets[i].update(delta);
		if(_bullets[i].isDead())
			_bullets.erase(_bullets.begin() + i);
	}

	_particleSystem.setPosition(getPosition());

	_particleSystem.clear();
    _particleSystem.update(delta);
    _particleSystem.render();
}

void Player::draw(sf::RenderTarget& target, sf::RenderStates states) const
{	
	target.draw(_particleSystem.getSprite());
	
	for(unsigned i = 0; i < _bullets.size(); ++i)
		target.draw(_bullets[i]);
		
	states.transform *= getTransform();

	if(_texture != nullptr)
		states.texture = (_texture);
    
	target.draw(_vertices, states);
}