#include "stdafx.h"
#include "Bacterium.h"

#include "CellWarsGameManager.h"
#include "CellWarsMTManager.h"
#include "Infrastructure\Camera.h"
#include "Cell.h"
#include "Macrophage.h"
#include "MultiSelectionCircle.h"
#include "SelectionCircle.h"
#include "SelectionCircleButtons.h"

#include "Util\MathUtil.h"
#include <math.h>

//#include <stdio.h>
const float Bacterium::STANDARD_SIZE = 3;
const float Bacterium::STANDARD_HEALTH_POINTS = 100;
const float Bacterium::STANDARD_DAMAGE = 20;
const float Bacterium::STANDARD_ATTACK_DISTANCE = 1.5f;
const float Bacterium::STANDARD_MAX_RESOURCES = 50;
const float Bacterium::STANDARD_SENSOR_DISTANCE = 5.0f;
const float Bacterium::STANDARD_OWN_MOVEMENT_SPEED = 3.0f;
const float Bacterium::STAT_INCREASE_PER_LEVEL_FACTOR = 0.25f;

const float Bacterium::FLOAT_CHECK_ACCURACY_FACTOR = 0.99f;
const float Bacterium::DRIFT_AWAY_DISTANCE = 1.0f;
const float Bacterium::MAX_TIME_SINCE_LAST_MOVE = 0.15f;
const float Bacterium::STAGGER_VELOCITY = 2.5f;
const float Bacterium::STAGGER_PHASE_VELOCITY_FACTOR = 1.5f;

Bacterium::Bacterium(Vector2 spawnPos, CellWarsMTManager *useThisMultiTouchManager) : 
MobileMicrobe(), Touchable(useThisMultiTouchManager), Damageable(STANDARD_HEALTH_POINTS), 
	upgradeLevel(0), resources(0), selectedByThisCircle(NULL), devourByThisMacrophage(NULL),
	buttons(new std::vector<SelectionButton*>()), attackSound(NULL), moveSound(NULL), 
	isAttackingThisFrame(false), lastFrameVelocitySoundFactor(0), 
	movementParticles(new ParticleActor())
{
	SetPosition(spawnPos);

	resetPositionVelocityHistory(spawnPos);
	
	SetColor(1,1,1,1);
	ClearSpriteInfo();
	SetSize(0);
	ChangeSizeTo(getCurrentSizeForLevel(), 1, true);
	SetName("Bacterium");
	SetSprite("Resources/Images/bacterium.png");
	theWorld.Add(this, LayerEnum::Bacterium);

	useThisMTManager = useThisMultiTouchManager;
	
	setPriorityLevel(0);

	healthBar = new Actor();
	healthBar->SetColor(1,1,1,1);
	healthBar->ClearSpriteInfo();
	healthBar->SetSize(0);
	healthBar->ChangeSizeTo(getCurrentSizeForLevel(), 1, true);
	healthBar->SetName("HealthBar");
	healthBar->SetSprite("Resources/Images/health.png");
	theWorld.Add(healthBar, LayerEnum::HealthBar);

	resourceBar = new Actor();
	resourceBar->SetColor(1,1,1,1);
	resourceBar->ClearSpriteInfo();
	resourceBar->SetSize(0);
	resourceBar->ChangeSizeTo(getCurrentSizeForLevel(), 1, true);
	resourceBar->SetName("Resources");
	resourceBar->SetSprite("Resources/Images/resources.png");
	theWorld.Add(resourceBar, LayerEnum::ResourceBar);
	
	selectionCircle = new SelectionCircle(this);
	
	buttons->push_back(new SelectionCircleButtonReplicate(0, useThisMultiTouchManager, this));
	buttons->push_back(new SelectionCircleButtonUpgrade(1, useThisMultiTouchManager, this));	
	buttons->push_back(new SelectionCircleButtonReplicate(0, useThisMultiTouchManager, this, true));
	buttons->push_back(new SelectionCircleButtonUpgrade(1, useThisMultiTouchManager, this, true));
	
	attackSound = theSound.PlaySound(theCellWarsGame.getAttackSample(), 0, true/*no loop*/, 0);
	moveSound = theSound.PlaySound(theCellWarsGame.getMoveSample(), 0, true/*no loop*/, 0);

	movementParticles->SetPosition(this->GetPosition());
	movementParticles->SetRotation(this->GetRotation());
	movementParticles->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
	movementParticles->SetEndColor(Color(1.0f, 1.0f, 1.0f, 0.0f));
	movementParticles->SetSize(0.25f);
	movementParticles->SetSprite("Resources/Images/flowparticle.png");
	movementParticles->SetParticlesPerSecond(0);
	movementParticles->SetMaxParticles(100);
	movementParticles->SetParticleLifetime(0.5f);
	movementParticles->SetSpread(MathUtil::Pi * 0.15f);
	movementParticles->SetSpeedRange(2.5f, 5.0f);
	movementParticles->SetGravity(Vector2(0));
	theWorld.Add(movementParticles, LayerEnum::MacrophageTentacles);

	staggerPhase = MathUtil::RandomFloatInRange(0, MathUtil::TwoPi);
	staggerDir = 1;

	//std::cout << "Bacterium " << _name << std::endl;
}

Bacterium::~Bacterium()
{
	delete buttons;
	//std::cout << "~Bacterium " << _name << std::endl;
}

void Bacterium::PreDestroy()
{
	if (devourByThisMacrophage)
	{
		devourByThisMacrophage->targetBacteriumDied();
		devourByThisMacrophage = NULL;
	}
	theCellWarsGame.removeBacteriumFromGame(this);

	useThisMTManager->removeSelectedBacteriumFromSelectionCircles(this);
	useThisMTManager = NULL;

	healthBar->Destroy();
	resourceBar->Destroy();

	selectionCircle->Destroy();

	std::vector<SelectionButton*>::iterator buttonIt = buttons->begin();
	while(buttonIt != buttons->end())
	{
		(*buttonIt)->Destroy();
		buttonIt = buttons->erase(buttonIt);
	}
	
	theSound.StopSound(attackSound);
	attackSound = NULL;
	theSound.StopSound(moveSound);
	moveSound = NULL;
	
	movementParticles->Destroy();
	//std::cout << "predestroy Bacterium " << _name << std::endl;
}

void Bacterium::die()
{
	this->Destroy();
}

void Bacterium::setUpgradeLevel(int newLevel)
{
	float lifePerc = getLifePercentage();
	upgradeLevel = newLevel;
	ChangeSizeTo(getCurrentSizeForLevel(), 1, true);
	setMaxLifePoints(getCurrentHPForLevel());
	setLifePercentage(lifePerc);
	movementParticles->SetSize(0.25f * (1 + STAT_INCREASE_PER_LEVEL_FACTOR * upgradeLevel));
}

void Bacterium::upgrade()
{
	if (hasFullResources())
	{
		theCellWarsGame.playUpgradeSound();

		setUpgradeLevel(upgradeLevel + 1);
		resources = 0;
		theCellWarsGame.addUpgrade();
	}
}

Bacterium* Bacterium::replicate()
{
	static const float INITIAL_MOVE_IMPULSE = 10;
	
	if (hasFullResources())
	{
		theCellWarsGame.playReplicateSound();

		Bacterium* copy = theCellWarsGame.spawnSingleBacterium(GetPosition());

		if (copy)
		{
			copy->SetSize(0);
			copy->setUpgradeLevel(upgradeLevel);
			copy->currentVelocity = (MathUtil::RandomVector() * INITIAL_MOVE_IMPULSE);

			resources = 0;
			theCellWarsGame.addReplication();
		}
		return copy;
	}
	else
		return NULL;
}

void Bacterium::ReceiveMessage(Message *m)
{
	MobileMicrobe::ReceiveMessage(m);
}

void Bacterium::Render()
{
	MobileMicrobe::Render();
}

void Bacterium::Update(float dt)
{
	if (IsDestroyed())
		return;

	//Vector2 movementSinceLastFrame(GetPosition() - getLastPosition());

	if (devourByThisMacrophage)
		currentVelocity *= 0;
	else if (isAlreadyTouched())
		currentVelocity = lastVelocities[bufferCycleVar];

	bool noDampingAndInertia = true;
	if (!devourByThisMacrophage && !isAlreadyTouched())
	{
		//std::cout << "Update set pos of " << this->GetName() << " from " << this->GetPosition().X << ", " << this->GetPosition().Y << std::endl;
		noDampingAndInertia = false;

		moveToNextTarget(dt);
		attackNextTarget(dt);
		
		if (!isAttackingThisFrame)
		{
			driftAway();
			stagger(dt);
		}

		//std::cout << "Update set pos of " << this->GetName() << " to " << this->GetPosition().X << ", " << this->GetPosition().Y << std::endl;
	}
	setIgnoreDampingAndInertia(noDampingAndInertia);
	MobileMicrobe::Update(dt);
	
	//updateHPString(this->GetPosition(), this->GetSize());
	
	if (isAlreadyTouched() || isCurrentlySelected())
	{
		selectionCircle->SetAlpha(1);

		if (!isCurrentlySelected())
		{
			std::vector<SelectionButton*>::iterator buttonIt = buttons->begin();
			while(buttonIt != buttons->end())
			{
				(*buttonIt)->SetAlpha(1);
				(*buttonIt)->setActive();
				++buttonIt;
			}
		}
		else
		{
			std::vector<SelectionButton*>::iterator buttonIt = buttons->begin();
			while(buttonIt != buttons->end())
			{
				(*buttonIt)->SetAlpha(0);
				(*buttonIt)->setInactive();
				++buttonIt;
			}
		}
	}
	else
	{
		selectionCircle->SetAlpha(0);

		std::vector<SelectionButton*>::iterator buttonIt = buttons->begin();
		while(buttonIt != buttons->end())
		{
			(*buttonIt)->SetAlpha(0);
			(*buttonIt)->setInactive();
			++buttonIt;
		}
	}

	updateHealthBar();
	updateResourceBar();

	float velocity;
	//if (isAlreadyTouched())
	//	velocity = movementSinceLastFrame.Length() / dt;
	//else
		velocity = currentVelocity.Length();
	float nominalSoundAndParticleFactor = ((velocity) / getCurrentOwnMovementForLevel()) - 1;
	if (nominalSoundAndParticleFactor < 0)
		nominalSoundAndParticleFactor = 0;
	else if (nominalSoundAndParticleFactor > 1)
		nominalSoundAndParticleFactor = 1;

	float velocitySoundAndParticleFactor = (nominalSoundAndParticleFactor + lastFrameVelocitySoundFactor) * 0.5f;

	/*
	if (velocitySoundAndParticleFactor > 0.1f)
	{
		std::cout << nominalSoundAndParticleFactor << " = (" << velocity << "/ 5) - 0.5f" << std::endl;
		std::cout << velocitySoundAndParticleFactor << " = " << nominalSoundAndParticleFactor << " + " << lastFrameVelocitySoundFactor << "/2" << std::endl;
	}
	*/

	float screenMiddleXInAngelUnits = (theCamera.GetWorldMaxVertex().X + theCamera.GetWorldMinVertex().X) * 0.5f;
	float screenSizeXInAngelUnits = (theCamera.GetWorldMaxVertex().X - theCamera.GetWorldMinVertex().X);
	float pan = ((GetPosition().X / screenSizeXInAngelUnits) - 0.5f) * 2;
	if (pan < -1)
		pan = -1;
	else if (pan > 1)
		pan = 1;

	float attackeding = 0;
	if (devourByThisMacrophage || isAttackingThisFrame)
		attackeding = 1;

	if (attackSound)
	{
		theSound.SetVolume(attackSound, theCellWarsGame.getMainVolume() * attackeding);
		theSound.SetPan(attackSound, pan);
	}
	
	if (moveSound)
	{
		float numBactFactor = (float)theCellWarsGame.getBacteriaList()->size();
		if (numBactFactor > 0)
			numBactFactor = 1 / numBactFactor;
		theSound.SetVolume(moveSound, theCellWarsGame.getMainVolume() * velocitySoundAndParticleFactor * numBactFactor);
		theSound.SetPan(moveSound, pan);
	}

	movementParticles->SetRotation(GetRotation());
	movementParticles->SetPosition(GetPosition());
	movementParticles->SetParticlesPerSecond(100 * 0.5f * velocitySoundAndParticleFactor);
	float velocitySmoothingFactor = lastFrameVelocitySoundFactor / nominalSoundAndParticleFactor;
	movementParticles->SetSpeedRange(velocity * velocitySmoothingFactor * 0.25f, velocity * velocitySmoothingFactor * 0.5f);

	lastFrameVelocitySoundFactor = velocitySoundAndParticleFactor;
}

void Bacterium::stagger(float dt)
{
	float oldStaggerPhase = staggerPhase;
	staggerPhase += dt * STAGGER_PHASE_VELOCITY_FACTOR * staggerDir;

	if (MathUtil::RandomFloat() < 0.1f * dt)
	{
		staggerDir *= -1;
	}

	if (MathUtil::Abs(oldStaggerPhase) < MathUtil::Pi && MathUtil::Abs(staggerPhase) >= MathUtil::Pi)
	{
		if (MathUtil::RandomBool())
		{
			oldStaggerPhase -= MathUtil::Pi;
		}
	}
	
	if (MathUtil::Abs(oldStaggerPhase) < 2 * MathUtil::Pi && MathUtil::Abs(staggerPhase) >= 2 * MathUtil::Pi)
	{
		if (MathUtil::RandomBool())
		{
			oldStaggerPhase = 0;
		}
		else
		{
			oldStaggerPhase = MathUtil::Pi;
		}
	}

	Vector2 staggerMove(STAGGER_VELOCITY, 0);
	staggerMove = Vector2::Rotate(staggerMove, oldStaggerPhase);
	staggerMove *= dt;

	SetPosition(GetPosition() + staggerMove);
	fingerToThisOffset -= staggerMove;
	//currentVelocity += staggerMove * dt;
}

void Bacterium::moveToNextTarget(float dt)
{
	// macrophages have priority
	float distToNearestMacrophage = 0;
	float distToNearestCell = 0;

	bool hasTarget = false;

	Macrophage* nearestMacrophage = theCellWarsGame.getNearestMacrophage(this->GetPosition());
	Cell* nearestCell = theCellWarsGame.getNearestCell(this->GetPosition());

	if (nearestMacrophage)
	{
		distToNearestMacrophage = (nearestMacrophage->GetPosition() - this->GetPosition()).Length();
		if (distToNearestMacrophage < getCurrentSensorDistForLevel() && 
			currentVelocity.Length() < getCurrentOwnMovementForLevel())
		{
			hasTarget = true;
			if (distToNearestMacrophage > (0.75f * getCurrentAttackDistForLevel()))
			{
				currentVelocity = (nearestMacrophage->GetPosition() - this->GetPosition());
				currentVelocity.Normalize();
				currentVelocity *= getCurrentOwnMovementForLevel();
			}
		}
	}

	if (!hasTarget && nearestCell)
	{
		if (!hasFullLife() || !hasFullResources())
		{
			distToNearestCell = (nearestCell->GetPosition() - this->GetPosition()).Length();
			if (distToNearestCell < getCurrentSensorDistForLevel() && 
				currentVelocity.Length() < getCurrentOwnMovementForLevel())
			{
				hasTarget = true;
				if (distToNearestCell > (0.75f * getCurrentAttackDistForLevel()))
				{
					currentVelocity = (nearestCell->GetPosition() - this->GetPosition());
					currentVelocity.Normalize();
					currentVelocity *= getCurrentOwnMovementForLevel();
				}
				return;
			}
		}
	}
}

void Bacterium::driftAway()
{
	Bacterium* nearestBact = theCellWarsGame.getNearestBacteriumActor(this);
	
	if (nearestBact)
	{
		Vector2 dist(GetPosition() - nearestBact->GetPosition());
		if (dist.Length() < getCurrentDriftAwayDistForLevel() && 
			currentVelocity.Length() < STANDARD_OWN_MOVEMENT_SPEED)
		{
			currentVelocity = Vector2::Normalize(dist);
			currentVelocity *= STANDARD_OWN_MOVEMENT_SPEED;
		}
		else if (dist.Length() > getCurrentDriftAwayDistForLevel() && 
			currentVelocity.Length() < STANDARD_OWN_MOVEMENT_SPEED)
		{
			if (useThisMTManager)
			{
				Vector2 velocityToSelectionCircleCenters(0);
				std::list<MultiSelectionCircle*>* selectionCircles = useThisMTManager->getSelectionCircleList();
				for (std::list<MultiSelectionCircle*>::iterator selCirIT(selectionCircles->begin()); 
					selCirIT != selectionCircles->end(); ++selCirIT)
				{
					if ((*selCirIT)->hasBacteriumSelected(this))
					{
						Vector2 velToCenter((*selCirIT)->GetPosition() - GetPosition());
						if (velToCenter.Length() > 2 * getCurrentDriftAwayDistForLevel())
						{
							velToCenter = Vector2::Normalize(velToCenter);
							velToCenter *= STANDARD_OWN_MOVEMENT_SPEED * 0.5f;
							velocityToSelectionCircleCenters += velToCenter;
						}
					}
				}
				currentVelocity += velocityToSelectionCircleCenters;
			}
		}
	}
}

void Bacterium::attackNextTarget(float dt)
{
	bool isAttackingMacrophage = false;
	bool isAttackingCell = false;

	// macrophages have priority
	float distToNearestMacrophage = 0;
	float distToNearestCell = 0;

	Macrophage* nearestMacrophage = theCellWarsGame.getNearestMacrophage(this->GetPosition());
	Cell* nearestCell = theCellWarsGame.getNearestCell(this->GetPosition());

	float currentMaxRes = getCurrentMaxResForLevel();
	float currentDamage = getCurrentDamageForLevel();
	float currentMaxHP = getCurrentHPForLevel();

	if (nearestMacrophage)
	{
		distToNearestMacrophage = (nearestMacrophage->GetPosition() - this->GetPosition()).Length();
		if (distToNearestMacrophage < getCurrentAttackDistForLevel())
		{
			nearestMacrophage->addLifePoints(-currentDamage * dt);
			isAttackingMacrophage = true;
		}
	}

	if (!isAttackingMacrophage && nearestCell)
	{
		distToNearestCell = (nearestCell->GetPosition() - this->GetPosition()).Length();
		if (distToNearestCell < getCurrentAttackDistForLevel() && 
			(!hasFullResources() || !hasFullLife()))
		{
			nearestCell->addLifePoints(-currentDamage * dt);
			isAttackingCell = true;
		}
	}

	if (isAttackingCell/* || isAttackingMacrophage*/)
	{
		if (!hasFullLife())
		{
			setLifePoints(getLifePoints() + currentDamage * dt * 1.5f);
			if (getLifePoints() > currentMaxHP)
				setLifePoints(currentMaxHP);
		}
		else if (!hasFullResources())
		{
			resources += currentDamage * dt;
			if (resources > currentMaxRes)
				resources = currentMaxRes;
		}
	}

	isAttackingThisFrame = isAttackingMacrophage || isAttackingCell;
}

void Bacterium::updatePositionVelocityHistory()
{
	// TUIO triggers an additional fingermove-callback before the fingerup
	// we don't want an velocity = 0 entry in the buffer
	if ((lastPositions[bufferCycleVar] - this->GetPosition()).Length() != 0)
	{
		const float MIN_DELTA_TIME = 0.00001f;

		int lastPosIndex = bufferCycleVar;

		// increment cycle pointer only if in next frame of the game thread
		if (lastTimes[bufferCycleVar] != theWorld.GetCurrentTimeSeconds())
		{
			++bufferCycleVar;
			bufferCycleVar %= BACTERIUM_VELOCITY_BUFFER_SIZE;
		}
		else
		{
			--lastPosIndex;
			if (lastPosIndex < 0)
				lastPosIndex = BACTERIUM_VELOCITY_BUFFER_SIZE - 1;
		}

		lastPositions[bufferCycleVar] = this->GetPosition();

		// update move time
		lastTimes[bufferCycleVar] = theWorld.GetCurrentTimeSeconds();
		//if (lastTimes[bufferCycleVar] == 0)
		//	lastTimes[bufferCycleVar] = MIN_DELTA_TIME;

		// calc velocity for last frame
		lastVelocities[bufferCycleVar] = (lastPositions[bufferCycleVar] - 
			lastPositions[lastPosIndex]) / (lastTimes[bufferCycleVar] - lastTimes[lastPosIndex]);
		
		//std::cout << "updatePos lastVel[" << bufferCycleVar << "] = " << lastVelocities[bufferCycleVar].X << ", " << lastVelocities[bufferCycleVar].Y << std::endl;

		// calc avg velocity
		/*
		// old plain avg
		avgVelocity = Vector2();
		for (int i = 0; i < BACTERIUM_VELOCITY_BUFFER_SIZE; ++i)
			avgVelocity += lastVelocities[i];
		avgVelocity /= BACTERIUM_VELOCITY_BUFFER_SIZE;
		*/
		// weighted avg
		avgVelocity = Vector2();
		float totalTimeInBuffer = 0;
		for (int i = 0; i < BACTERIUM_VELOCITY_BUFFER_SIZE; ++i)
		{
			// skip never used zero vectors
			if (lastVelocities[i].Length() != 0)
			{
				avgVelocity += lastVelocities[i] * lastTimes[i];
				totalTimeInBuffer += lastTimes[i];
			}
		}
		avgVelocity /= totalTimeInBuffer;
	}
}

void Bacterium::resetPositionVelocityHistory(Vector2 resetPos)
{
	for (int i = 0; i < BACTERIUM_VELOCITY_BUFFER_SIZE; ++i)
	{
		lastPositions[i] = resetPos;
		lastTimes[i] = 0;
		lastVelocities[i] = Vector2();
	}

	avgVelocity = Vector2();
	bufferCycleVar = 0;
}

void Bacterium::fingerDown(Vector2 posDownInAngelCoords)
{
	Touchable::fingerDown(posDownInAngelCoords);

	if (!devourByThisMacrophage)
	{
		resetPositionVelocityHistory(this->GetPosition() - fingerToThisOffset);

		currentVelocity *= 0;
	}
}

void Bacterium::fingerMove(Vector2 moveToInAngelCoords)
{
	Touchable::fingerMove(moveToInAngelCoords);
	
	if (!devourByThisMacrophage)
	{
		this->SetPosition(moveToInAngelCoords - fingerToThisOffset);

		updatePositionVelocityHistory();
	}
}

void Bacterium::fingerUp(Vector2 lastPosInAngelCoords)
{
	Touchable::fingerUp(lastPosInAngelCoords);

	if (!devourByThisMacrophage)
	{
		this->SetPosition(lastPosInAngelCoords - fingerToThisOffset);

		/*
		std::cout << "finger up of " << this->GetName() << std::endl;
		for (int i = 0; i < BACTERIUM_VELOCITY_BUFFER_SIZE; ++i)
			std::cout << "lastVel[" << i << "] at " << lastTimes[i] << " = " << lastVelocities[i].X << ", " << lastVelocities[i].Y << std::endl;
		*/

		int lastCycleVar = bufferCycleVar - 1;
		if (lastCycleVar < 0)
			lastCycleVar = BACTERIUM_VELOCITY_BUFFER_SIZE - 1;
		if (theWorld.GetCurrentTimeSeconds() - lastTimes[bufferCycleVar] < MAX_TIME_SINCE_LAST_MOVE)
		{
			//currentVelocity = lastVelocities[bufferCycleVar];
			currentVelocity = avgVelocity;
		}
		else
			currentVelocity *= 0;

		//std::cout << "currentVelocity = " << currentVelocity.X << ", " << currentVelocity.Y << std::endl;
	}
}

void Bacterium::updateHealthBar()
{
	if (isCurrentlySelected() || isAlreadyTouched())
	{
		healthBar->SetAlpha(1);
		healthBar->SetSize(GetSize() * getLifePercentage());
		healthBar->SetPosition(GetPosition());
	}
	else
		healthBar->SetAlpha(0);
}

void Bacterium::updateResourceBar()
{
	if (isCurrentlySelected() || isAlreadyTouched())
	{
		resourceBar->SetAlpha(1);
		resourceBar->SetSize(GetSize() * resources / getCurrentMaxResForLevel());
		resourceBar->SetPosition(GetPosition());
	}
	else
		resourceBar->SetAlpha(0);
}