//	Tank.cpp - Class representing a player tank.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	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, either version 3 of the License, or
//	(at your option) any later version.
//
//	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 "Modifiers/Dispensers/SingleShotDispenser.h"
#include "ShockwaveEntity.h"
#include "drawing/Drawer.h"
#include "TankModifiers.h"
#include "MineModifiers.h"
#include "MiscHelper.h"
#include "Zombie.h"
#include "Bullet.h"
#include "Puddle.h"
#include "Tank.h"
#include "Mine.h"
#include "Bee.h"

#include <sstream>

//------------------------------------------------------------------------------
Tank::Tank(const TwoFloats& pos, const ThreeFloats& col, int ind):
Collidable(L"Tank"),
position(pos),
radius(16.0f),
colour(col),
armour(1.0f),
angle(0.0f),
speed(0.0f),
speedMax(2.0f),
globalSpeed(1.0f),
turbo(0),
backImage(L"TankBack"),
lineImage(L"TankLine"),
index(ind),
regen(0.0f),
noRecoil(false),
bulletsHeal(false),
diarrheaMines(false),
kamikaze(false),
kamikazeCount(0),
bounceVector(0.0f),
desiredSpeed(0.0f),
angleIncrement(0.0f),
angleAmount(0.02f),
firingPrimary(false),
primaryCount(0.0f),
primaryMax(20.0f),
firingSecondary(false),
secondaryCount(0.0f),
secondaryMax(192.0f),
leftIsDown(false),
rightIsDown(false),
dead(false),
trailCount(1)
{
	int i;
	float tempf;
	std::wstringstream tempstr;

	primaryDispensers.push_back(shared_ptr<PrimaryDispenser>(new SingleShotDispenser()));

	tempstr << L"Player";
	tempstr << index+1;
	actualName = tempstr.str();

	if(position.y > 360.0f)
		angle = NIALL_PI;

	particles.setColour(ThreeFloats(1.0f, 0.75f, 0.25f));
	particles.setFadeColour(0.25f);

	for(i=0;i<TrailSize;++i)
	{
		trailPos.push_back(position);
		trailSize.push_back(32.0f);
		trailCol.push_back(colour);

		if((i > 2) && (i < (TrailSize-3)))
			tempf = fakeExp((float)(i-3)/(float)(TrailSize-6));
		else if(i < 3)
			tempf = 0.0f;
		else if(i >(TrailSize-4))
			tempf = 1.0f;
		trailAlpha.push_back(tempf);
	}
	tempf = 1.0f;
}

//------------------------------------------------------------------------------
Tank::~Tank()
{

}

//------------------------------------------------------------------------------
void Tank::updatePlayerInput(GamePlayState& state,
							 const TwoFloats& arenaTopLeft,
							 const TwoFloats& arenaBottomRight)
{
	unsigned int i;
	TwoFloats direction(sinf(angle), -cosf(angle));

	if(angleIncrement != 0.0f)
		angle += angleIncrement;
	if(speed < desiredSpeed)
	{
		if((desiredSpeed-speed) > 1.0f)
			speed += (0.02f * speedMax);
		else
			speed += (0.01f * speedMax);
		if(speed > desiredSpeed)
			speed = desiredSpeed;
	}
	else if(speed > desiredSpeed)
	{
		if((speed-desiredSpeed) > 1.0f)
			speed -= (0.02f * speedMax);
		else
			speed -= (0.01f * speedMax);
		if(speed < desiredSpeed)
			speed = desiredSpeed;
	}

	if(!turbo)
		position += direction * speed * globalSpeed;

	//Update weapons.
	if(firingPrimary && (primaryCount <= 0.0f))
	{
		bool recoil = !noRecoil;

		for(i=0;i<primaryDispensers.size();++i)
		{
			if(!primaryDispensers[i]->firePrimary(*this, state))
				recoil = false;
		}

		//Setup up effects.
		if(recoil)
		{
			recoilVector.x = direction.x * 2.0f;
			recoilVector.y = direction.y * 2.0f;
			particles.setGravity(direction);
			particles.trigger(position+(direction * 16.0f));
		}
		else
		{
			particles.setGravity(0.0f);
			particles.trigger(position);
		}
	}
	if(firingSecondary && (secondaryCount <= 0.0f))
	{
		state.addMine(position, actualName, colour);

		for(i=0;i<secondaryTriggers.size();++i)
			secondaryTriggers[i]->trigger(*this, state);
	}
}

//------------------------------------------------------------------------------
void Tank::updateCounters(GamePlayState& state,
						  const TwoFloats& arenaTopLeft,
						  const TwoFloats& arenaBottomRight)
{
	unsigned int i;
	float turboSpeed;
	TwoFloats direction(sinf(angle), -cosf(angle));

	//Update position.
	if(turbo > 0)
	{
		turboSpeed = -(1.0f + (13.0f * ((float)turbo/30.0f)));
		--turbo;

		position += direction * turboSpeed * globalSpeed;
	}
	position += bounceVector;
	position += recoilVector;

	//Update the tank's trail.
	if(speedMax < 3.0f)
	{
		--trailCount;
		if(!trailCount)
		{
			trailPos.erase(trailPos.begin());
			trailPos.push_back(position);

			trailCount = 2;
		}
	}
	else
	{
		trailPos.erase(trailPos.begin());
		trailPos.push_back(position);
	}

	//Update the bounce and recoil vectors.
	if(bounceVector.x != 0.0f)
	{
		bounceVector.x *= 0.95f;
		if(fabsf(bounceVector.x) < 0.1f)
			bounceVector.x = 0.0f;
	}
	if(bounceVector.y != 0.0f)
	{
		bounceVector.y *= 0.95f;
		if(fabsf(bounceVector.y) < 0.1f)
			bounceVector.y = 0.0f;
	}

	if(recoilVector.x != 0.0f)
	{
		recoilVector.x *= 0.95f;
		if(fabsf(recoilVector.x) < 0.1f)
			recoilVector.x = 0.0f;
	}
	if(recoilVector.y != 0.0f)
	{
		recoilVector.y *= 0.95f;
		if(fabsf(recoilVector.y) < 0.1f)
			recoilVector.y = 0.0f;
	}

	//Keep us within the arena's bounds.
	if(1)
	{
		if((position.x-16.0f) < arenaTopLeft.x)
			position.x = arenaTopLeft.x + 16.0f;
		else if((position.x+16.0f) > arenaBottomRight.x)
			position.x = arenaBottomRight.x - 16.0f;

		if((position.y-16.0f) < arenaTopLeft.y)
			position.y = arenaTopLeft.y + 16.0f;
		else if((position.y+16.0f) > arenaBottomRight.y)
			position.y = arenaBottomRight.y - 16.0f;
	}

	//Update primary fire.
	if(primaryCount > 0.0f)
	{
		primaryCount -= 1.0f;
		if(primaryCount < 0.0f)
			primaryCount = 0.0f;
	}
	else if(firingPrimary)
		primaryCount = primaryMax;

	//Update secondary fire.
	if(secondaryCount > 0.0f)
	{
		secondaryCount -= 1.0f;
		if(secondaryCount < 0.0f)
			secondaryCount = 0.0f;
	}
	else if(firingSecondary)
		secondaryCount = secondaryMax;

	//If diarrhea mines are active, drop them here.
	if(diarrheaMines && (secondaryCount <= 0.0f))
	{
		state.addMine(position, actualName, colour);
		secondaryCount = secondaryMax;

		for(i=0;i<secondaryTriggers.size();++i)
			secondaryTriggers[i]->trigger(*this, state);
	}

	//Regenerate armour if that pickup's been collected.
	if(armour < 1.0f)
	{
		armour += regen;
		if(armour > 1.0f)
			armour = 1.0f;
	}

	//Reduce armour if the Bullets heal! modifier has been activated.
	if(bulletsHeal)
	{
		armour -= 0.0005f;
		if(armour < 0.0f)
		{
			armour = 0.0f;
			setDead();
		}
	}

	//Update kamikaze count.
	if(kamikazeCount > 0)
		--kamikazeCount;

	//Update particles.
	particles.update();

	//Reset global speed.
	globalSpeed = 1.0f;
}

//------------------------------------------------------------------------------
void Tank::startTurn()
{
	desiredSpeed = 0.0f;
}

//------------------------------------------------------------------------------
void Tank::drawTracks(shared_ptr<Drawer>& d, float timestepInterp)
{
	d->drawConnectedLine(L"TankTrail",
						 trailPos,
						 trailSize,
						 trailCol,
						 trailAlpha);
}

//------------------------------------------------------------------------------
void Tank::draw(shared_ptr<Drawer>& d, float timestepInterp)
{
	d->drawRotImage(backImage,
					position-radius,
					angle,
					radius);
	d->drawRotImage(lineImage,
					position-(radius+((radius/16.0f)*4.0f)),
					angle,
					(radius+((radius/16.0f)*4.0f)),
					1.0f,
					colour);

	particles.draw(d, timestepInterp);
}

//------------------------------------------------------------------------------
void Tank::keyDown(const std::wstring& action)
{
	if(action == L"Forward")
		desiredSpeed = -speedMax;
	else if(action == L"Backward")
		desiredSpeed = speedMax;
	else if(action == L"Left")
	{
		angleIncrement = -(angleAmount * speedMax);
		leftIsDown = true;
	}
	else if(action == L"Right")
	{
		angleIncrement = (angleAmount * speedMax);
		rightIsDown = true;
	}
	else if(action == L"Primary")
		firingPrimary = true;
	else if(action == L"Secondary")
		firingSecondary = true;
}

//------------------------------------------------------------------------------
void Tank::keyUp(const std::wstring& action)
{
	if((action == L"Forward") || (action == L"Backward"))
		desiredSpeed = 0.0f;
	else if((action == L"Left") || (action == L"Right"))
	{
		if(action == L"Left")
			leftIsDown = false;
		if(action == L"Right")
			rightIsDown = false;

		if(leftIsDown)
			angleIncrement = -(angleAmount * speedMax);
		else if(rightIsDown)
			angleIncrement = (angleAmount * speedMax);
		else
			angleIncrement = 0.0f;
	}
	else if(action == L"Primary")
		firingPrimary = false;
	else if(action == L"Secondary")
		firingSecondary = false;
}

//------------------------------------------------------------------------------
void Tank::move(const std::wstring& action, float value)
{
	float tempf = value;

	if(fabsf(tempf) < 0.2f)
		tempf = 0.0f;
	else if(tempf > 0.0f)
		tempf = (tempf-0.2f) * (1.0f/0.8f);
	else if(tempf < 0.0f)
		tempf = (tempf+0.2f) * (1.0f/0.8f);

	if(action == L"Forward")
		desiredSpeed = speedMax * tempf;
	else if(action == L"Backward")
		desiredSpeed = speedMax * tempf;
	else if(action == L"Left")
		angleIncrement = (angleAmount * speedMax) * tempf;
	else if(action == L"Right")
		angleIncrement = (angleAmount * speedMax) * tempf;
}

//------------------------------------------------------------------------------
bool Tank::getPlayerInput() const
{
	float tempf;
	if(desiredSpeed != 0.0f)
		tempf = desiredSpeed;

	return (firingPrimary ||
			(firingSecondary && !diarrheaMines) ||
			leftIsDown ||
			rightIsDown ||
			(desiredSpeed != 0.0f) ||
			(angleIncrement != 0.0f));

}

//------------------------------------------------------------------------------
void Tank::setPosition(const TwoFloats& pos, bool resetTail)
{
	unsigned int i;

	position = pos;

	if(resetTail)
	{
		for(i=0;i<trailPos.size();++i)
			trailPos[i] = position;
	}
}

//------------------------------------------------------------------------------
void Tank::setDead()
{
	dead = true;
	position = -512.0f;
}

//------------------------------------------------------------------------------
void Tank::addPrimaryDispenser(shared_ptr<PrimaryDispenser>& dispenser)
{
	primaryDispensers.push_back(dispenser);
}

//------------------------------------------------------------------------------
void Tank::addCollisionModifier(shared_ptr<TankCollisionModifier>& mod)
{
	collisionModifiers.push_back(mod);
}

//------------------------------------------------------------------------------
void Tank::addSecondaryTrigger(shared_ptr<SecondaryModifier> trigger)
{
	secondaryTriggers.push_back(trigger);
}

//------------------------------------------------------------------------------
void Tank::increasePrimaryRate()
{
	if(primaryMax > 6.0f)
		primaryMax -= 2.0f;
}

//------------------------------------------------------------------------------
void Tank::increaseSecondaryRate()
{
	if(secondaryMax > 40.0f)
		secondaryMax -= 40.0f;
}

//------------------------------------------------------------------------------
void Tank::setArmourRegen()
{
	regen = 0.001f;
}

//------------------------------------------------------------------------------
void Tank::setNoRecoil()
{
	noRecoil = true;
}

//------------------------------------------------------------------------------
void Tank::increaseSpeed()
{
	if(speedMax < 8.0f)
	{
		if(fabsf(speed) == speedMax)
		{
			if(speed > 0.0f)
				desiredSpeed = speedMax+1.0f;
			else if(speed < 0.0f)
				desiredSpeed = -(speedMax+1.0f);
		}
		if(fabsf(angleIncrement) == (0.02f * speedMax))
		{
			if(angleIncrement > 0.0f)
				angleIncrement = (0.02f * (speedMax+1.0f));
			else if(angleIncrement < 0.0f)
				angleIncrement = -(0.02f * (speedMax+1.0f));
		}

		speedMax += 1.0f;
	}
}

//------------------------------------------------------------------------------
void Tank::setGiant()
{
	int i;

	backImage = L"BigTankBack";
	lineImage = L"BigTankLine";
	radius = 32.0f;
	angleAmount = 0.01f;

	for(i=0;i<TrailSize;++i)
		trailSize[i] = radius*2.0f;
}

//------------------------------------------------------------------------------
void Tank::setBulletsHeal()
{
	bulletsHeal = true;
}

//------------------------------------------------------------------------------
void Tank::setDiarrheaMines()
{
	diarrheaMines = true;
}

//------------------------------------------------------------------------------
void Tank::setKamikaze()
{
	kamikaze = true;
}

//------------------------------------------------------------------------------
void Tank::multiplyGlobalSpeed(float val)
{
	globalSpeed *= val;
}

//------------------------------------------------------------------------------
void Tank::incrementHealth(float val)
{
	armour += val;
	if(armour > 1.0f)
		armour = 1.0f;
	else if(armour < 0.0f)
	{
		armour = 0.0f;
		setDead();
	}
}

//------------------------------------------------------------------------------
void Tank::setTurbo()
{
	//desiredSpeed = speedMax * 8.0f;
	turbo = 30;
}

//------------------------------------------------------------------------------
void Tank::updateBounceVector(const TwoFloats& bounce)
{
	const TwoFloats origin;

	bounceVector += bounce;

	if(bounceVector.x > 28.0f)
		bounceVector.x = 28.0f;
	else if(bounceVector.x < -28.0f)
		bounceVector.x = -28.0f;

	if(bounceVector.y > 28.0f)
		bounceVector.y = 28.0f;
	else if(bounceVector.y < -28.0f)
		bounceVector.y = -28.0f;
}

//------------------------------------------------------------------------------
bool Tank::interestedInCollisionsWith(const std::wstring& entity) const
{
	return true;
}

//------------------------------------------------------------------------------
bool Tank::collide(const shared_ptr<Collidable>& other,
				   GamePlayState& state)
{
	unsigned int i;
	bool retval = false;
	const std::wstring& n(other->getName());
	const TwoFloats arenaTopLeft(32.0f, 64.0f);
	const TwoFloats arenaBottomRight(state.getDrawingAreaSize().x-32.0f,
									 state.getDrawingAreaSize().y-32.0f);

	if((n == L"Wall") ||
	   (n == L"MADButton") ||
	   (n == L"Turret") ||
	   (n == L"ZombieSpawner") ||
	   ((n == L"Tank") && noRecoil))
	{
		float offset;
		const TwoFloats origin;
		TwoFloats diff(other->getPosition() - position);
		float dist = origin.getDistance(diff);
		float rad = radius + other->getRadius();

		diff = diff.normal();
		offset = (rad - dist);
		position -= diff * offset;
	}
	else if(n == L"Tank")
	{
		TwoFloats tempPos(other->getPosition());
		TwoFloats correctionVector(position-tempPos);
		float rad = other->getRadius()+getRadius()+1.5f;

		position = tempPos + (correctionVector.normal() * rad);
		bounceVector = correctionVector.normal() * 6.0f;

		retval = true;
	}
	else if(n == L"Bullet")
	{
		if(!bulletsHeal)
		{
			armour -= 0.02f;
			if(armour < 0.0f)
			{
				std::wstringstream tempstr;
				shared_ptr<Bullet> bullet(dynamic_pointer_cast<Bullet>(other));

				armour = 0.0f;

				tempstr << L"Killed by " << bullet->getParent();
				state.setDeathMessage(index,
									  tempstr.str(),
									  bullet->getColour());
			}
		}
		else
		{
			armour += 0.01f;
			if(armour > 1.0f)
				armour = 1.0f;
		}

		if(!noRecoil)
			recoilVector = (position - other->getPosition()).normal() * 2.0f;
	}
	else if(n == L"Shockwave")
	{
		armour -= 0.005f;
		if(armour < 0.0f)
		{
			shared_ptr<ShockwaveEntity> shockwave(dynamic_pointer_cast<ShockwaveEntity>(other));

			armour = 0.0f;
			state.setDeathMessage(index,
									L"Killed by Shockwave",
									shockwave->getColour());
		}

		if(!noRecoil)
			recoilVector = (position - other->getPosition()).normal() * 2.0f;
	}
	else if(n == L"Mine")
	{
		armour -= 0.15f;
		if(armour < 0.0f)
		{
			std::wstringstream tempstr;
			shared_ptr<Mine> mine(dynamic_pointer_cast<Mine>(other));

			armour = 0.0f;

			tempstr << L"Killed by " << mine->getOwner();
			state.setDeathMessage(index,
								  tempstr.str(),
								  mine->getColour());
		}

		if(!noRecoil)
			recoilVector = (other->getPosition() - position).normal() * 2.0f;
	}
	else if(n == L"Puddle")
	{
		shared_ptr<Puddle> puddle = dynamic_pointer_cast<Puddle>(other);

		if(puddle)
			puddle->collideTank(*this, state);
	}
	else if(n == L"Bee")
	{
		shared_ptr<Bee> bee(dynamic_pointer_cast<Bee>(other));

		if(bee->getColour() != colour)
		{
			armour -= 0.01f;
			if(armour < 0.0f)
			{
				armour = 0.0f;

				state.setDeathMessage(index, L"Killed by bee", bee->getColour());
			}
		}
	}
	else if(n == L"Zombie")
	{
		shared_ptr<Zombie> zombie(dynamic_pointer_cast<Zombie>(other));

		armour -= 0.1f;
		if(armour < 0.0f)
		{
			armour = 0.0f;

			state.setDeathMessage(index, L"Killed by zombie", zombie->getColour());
		}
	}

	if((n == L"Tank") && kamikaze && !kamikazeCount)
	{
		armour -= 0.25f;
		if(armour <= 0.0f)
		{
			armour = 0.0f;

			state.setDeathMessage(index, L"Suicide", colour);
		}
		state.addShockwave(position, colour);
		kamikazeCount = 60;
	}

	for(i=0;i<collisionModifiers.size();++i)
		collisionModifiers[i]->collide(*this, state, other);

	//Make sure the tank remains within the arena's bounds.
	{
		if((position.x-16.0f) < arenaTopLeft.x)
			position.x = arenaTopLeft.x + 16.0f;
		else if((position.x+16.0f) > arenaBottomRight.x)
			position.x = arenaBottomRight.x - 16.0f;

		if((position.y-16.0f) < arenaTopLeft.y)
			position.y = arenaTopLeft.y + 16.0f;
		else if((position.y+16.0f) > arenaBottomRight.y)
			position.y = arenaBottomRight.y - 16.0f;
	}

	return retval;
}

//------------------------------------------------------------------------------
bool Tank::operator==(const Tank& other) const
{
	return (this == &other);
}
