/* 
 * Tactic is the legal property of its developers, whose names are too numerous 
 * to list here.  Please refer to the COPYRIGHT file distributed with this 
 * source distribution. 
 * 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * 
 * This library 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 
 * Lesser General Public License for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library; if not, write to the Free Software 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 *
 * See README for more informations
 *
 * Unit.cpp
 *
 * ...
 * Date 22/02/06
 *
 */

#include "Unit.h"
#include "GroundMap.h"
#include "Config.h"
#include "CollisionManager.h"
#include "Renderer.h"
// include des actions
#include "UnitAction.h"
#include "ActionMove.h"
#include "ActionHit.h"
#include "ActionGoTo.h"
#include "ActionWait.h"
#include "ActionMount.h"
#include "ActionFollow.h"
#include "ActionCharge.h"
#include "ActionEndOrder.h"
#include "DustGenerator.h"

#include "GroupAI.h"


#include "MemoryManager/debugNew.h"

using namespace std;


Unit::Unit(unitEnumeration::Armor a, Weapon * w, Point2D p):
	armor(a),
	weapon(w),
	mount(NULL),
	// appel au super constructeur
	Entity(p) {
	this->wait();
	// on doit obligatoirement crée le moteur de poussiere dans Unit
	this->dustGenerator = new ParticleSystem::DustGenerator(this);
	RenderSystem::Renderer::getInstance().addDustParticle(dustGenerator);
	dustGenerator->allowDust(false);
	group = NULL;
	lastNotifyAttack = Point2D(-1,-1);
	reflexTicks = 8;
}

Unit::Unit(unitEnumeration::Armor a, Weapon * w, Point2D p, Mount * m):
	armor(a),
	weapon(w),
	mount(m),
	// appel au super constructeur
	Entity(p) {
	this->wait();
	// on doit obligatoirement crée le moteur de poussiere dans Unit
	this->dustGenerator = new ParticleSystem::DustGenerator(this);
	RenderSystem::Renderer::getInstance().addDustParticle(dustGenerator);
	dustGenerator->allowDust(false);
	group = NULL;
	lastNotifyAttack = Point2D(-1,-1);
	reflexTicks = 8;
}

Unit::Unit(void) : Entity()  {
	weapon = NULL;
	mount = NULL;
	group = NULL;
	this->wait();
	// on doit obligatoirement crée le moteur de poussiere dans Unit
	this->dustGenerator = new ParticleSystem::DustGenerator(this);
	RenderSystem::Renderer::getInstance().addDustParticle(dustGenerator);
	dustGenerator->allowDust(false);
	lastNotifyAttack = Point2D(-1,-1);
	reflexTicks = 8;
}

Unit::~Unit(void) {
	// on libère l'espace utilsé par les actions dans la pile
	for(vector<UnitAction*>::iterator it = stackAction.begin(); it != stackAction.end(); ++it) {
		delete *it;
	}
	// on detruit l'arme de l'unitée
	delete weapon;
}

/**
* Fait évoluer l'unité à chaque top du moteur 3D selon la pile d'action de l'unité.
* Ne réévalue pas la stratégie globale de l'unité.
* Doit réagir à la vision d'un ennemi.
* 
*/
void Unit::update() {
	if(!isAlive()) {
		// on est mort!!!
		if (state != unitEnumeration::DEAD){
			kill();
		}
		return;
	}
	// gestion des "réflexes"
	////////// GESTION DES REFLEXES //////////
	// nb: le réflexe suite à une attaque d'un ennemy est géré dans notifyAttack
	if (group != NULL) {
		if(reflexTicks<=0) {
			// a-t-on un cheval à portée?
			if (!isMounted() && state != unitEnumeration::MOUNTING) {
				Mount* mount = group->getVisibleFreeMountInRange(position,7);
				if (mount!=NULL) {
					//cerr << "coucou"<<endl;
					goAndMount(mount);
				}
			}
			// a-t-on un ennemi à portée?
			else if (group->getState()->type == StateName::MOVE || group->getState()->type == StateName::EVALUATION) {
				EnnemyUnit* ennemy = group->getEnnemyManager()->getEnnemyInRange(position, weapon->maxRange, 0.5f);
				if (ennemy) {
					attack(*ennemy);
					group->update();
					// launching update
				}
			}
			reflexTicks = 6;
		} else {
			reflexTicks--;
		}
	}
	/////////////////////////////////////////

/*	if (group->getState()->toString() != "Fight") {
		FreeMountManager::getInstance().
	}*/

	// lancement de l'action en cours
	UnitAction * action = stackAction.back();
	stackAction.pop_back();
	action->execute();
	// l'action s'occupe elle même de sa suppression ou pas (appel au UnitActionManager)
}


void Unit::goTo(vector<Point2D> path) {
	
	//setState(unitEnumeration::MOVE);
	//this->setAnimation(RenderableEntity::WALK, isMounted());
	//if (isMounted()){
	//	mount->setAnimation(RenderableEntity::WALK, false);
	//}

	vector<Point2D>::reverse_iterator it;
	it = path.rbegin(); 
	while( it != path.rend() ) {
		pushAction(new ActionMove(this,unitEnumeration::MOVE, *it));
		++it;
	}
}

void Unit::goTo(const Point2D& destination,ground::pathFinding::PathFinderMode mode){
	pushAction(new ActionGoTo(this,unitEnumeration::MOVE,mode,destination));
}

void Unit::follow(Entity* entity, int distance, bool loop) {
	pushAction(new ActionFollow(this,unitEnumeration::MOVE, entity, (float)(distance),loop));
}

void Unit::follow(Entity* entity, bool loop) {
	pushAction(new ActionFollow(this,unitEnumeration::MOVE, entity,loop));
}

void Unit::goAndMount(Mount * m) {
	pushAction(new ActionMount(this,unitEnumeration::MOUNTING, m ));
}

void Unit::mountOn(Mount * m) {
	if (m->isAlive()){
		if(isMounted()){
			dismount();
		}
		FreeMountManager::getInstance().removeMount(m);
		this->mount = m;	
		//m->setPosition(this->position);
		this->setPosition(m->getPosition());
		if(this->armyColor[0] == 1.0f && this->armyColor[1] == 0.0f && this->armyColor[2] == 0.0f){
			m->setArmy("Rouge");
		}else if(this->armyColor[0] == 0.0f && this->armyColor[1] == 0.0f && this->armyColor[2] == 1.0f){
			m->setArmy("Bleu");
		}else{
			m->setArmy("Neutre");
		}
		m->setOrientation(this->orientation);
		m->changeState();
		setAnimation(getCurrentAnimation(),true);
		// le collision manager ne doit plus gerer cette monture à part mais elle est gerée par
		// l'unitée, on la retire donc du collisionManager
		CollisionManager::getInstance().removeCollidable(mount);
	}
}

void Unit::dismount() {
	if (mount->isAlive()){
		FreeMountManager::getInstance().addMount(this->mount);
		mount->changeState();
		mount->setArmy("Neutre");
 		mount->setAnimation(RenderSystem::RenderableEntity::WAIT,false);
		// la monture doit de nouveau etre gerée par le collision manager comme une entitée à part entiere
		CollisionManager::getInstance().addCollidable(mount);
		this->mount = NULL;
		setAnimation(getCurrentAnimation(),false);
	}else{
		// on ne remet pas la monture dans le freemountManager et dans le gestionnaire de collisions car elle est morte
		mount->changeState();
		mount->setArmy("Neutre");
		mount->setAnimation(RenderSystem::RenderableEntity::DEATH,false);
		this->mount = NULL;
		setAnimation(getCurrentAnimation(),false);
	}
}


void Unit::attack(EnnemyUnit &ennemy) {
	if (isMounted()) {
		// si on est à cheval, on doit charger pour coller l'unité
		pushAction(new ActionCharge(this,unitEnumeration::CHARGE,&ennemy));
	} else {
		// l'action Hit s'occupe de s'approcher de l'ennemy s'il n'est pas à portée
		pushAction(new ActionHit(this,unitEnumeration::COMBAT_POSTURE,&ennemy));
	}
}

void Unit::notifyAttack(Point2D posit) {
	if (group != NULL) {
		if (group->getState()->type == StateName::MOVE || group->getState()->type == StateName::EVALUATION) {
			// on vient de nous attaquer alors qu'on n'est pas en attaque ni en fuite
			Point2D caseEnnemy = Point2D((int)(position.getX()+0.5f),(int)(position.getY()+0.5f));
			if(lastNotifyAttack == caseEnnemy) {
				// si on s'est déja fait attaquer par quelqu'un qui était sur cette case 
				// (et comme on a toujours pas bougé) alors on avance vers la position de l'ennemy
				this->wait(10);
				this->goTo(posit);
				group->update();
				lastNotifyAttack = Point2D(-1,-1);
			} else if(lastNotifyAttack == Point2D(-1,-1)) {
				// on se retourne vers la position de l'ennemi
				lastNotifyAttack = caseEnnemy;
				this->setOrientation(position.getAngle(posit));
				group->update();
			}
		}
	}
}

void Unit::eraseAllAction() {
	for(vector<UnitAction*>::iterator it = stackAction.begin(); it != stackAction.end(); ++it) {
 		delete *it;
	}
	stackAction.clear();
	pushAction(new ActionWait(this,unitEnumeration::WAIT));
}


void Unit::decreaseHealthPoint(int decreasePoint) {
	healthPoint -= decreasePoint ;
	if(healthPoint <= 0) {
		if(isMounted()){
			dismount();
		}
		CollisionManager::getInstance().removeCollidable(this);
		this->setAnimation(RenderableEntity::DEATH, isMounted());
		if (isMounted()){
			mount->setAnimation(RenderableEntity::DEATH, false);
		}
		setState(unitEnumeration::DEAD);
		// on lance le moteur de particules de sang
		RenderSystem::Renderer::getInstance().addBloodParticle(this->position);
		this->allowDust(false);
		// on supprime toute les action que l'unité etait en train de faire
		eraseAllAction();
	}
}


void Unit::decreaseMountHealthPoint(int decreasePoint) {
	if(mount != NULL) {
		mount->decreaseHealthPoint(decreasePoint);
		if (!mount->isAlive()){
			dismount();
		}
	}
}


void Unit::pushAction(UnitAction * a) {
	if (isAlive()){
		stackAction.push_back(a);
	}
}

void Unit::wait(int nbTop) {
	if (isAlive()){
		if (nbTop != -1){
			pushAction(new ActionWait(this,unitEnumeration::WAIT,nbTop));
		}else{
			this->eraseAllAction();
		}
	}
}


void Unit::kill() {
	decreaseHealthPoint(healthPoint);
}


/**
* WARNING: version pour les tests uniquement
* TODO: modifier la version pour l'intégration avec le terrain
*/
void Unit::setGroupAI(GroupAI* g)
{
	group = g;
}

float Unit::getAltitude() {
	return RenderSystem::Renderer::getInstance().getElevation(position);
}

float Unit::getSpeed() {
	if(!isMounted()) {
		float nextX = position.getX() + cosf(orientation*(3.141f/180));
		float nextY = position.getY() + sinf(orientation*(3.141f/180));
		return speed
				* ground::GroundMap::getInstance().getGroundCaseFactor(position,Point2D(nextX,nextY));
	}
	else {
		float nextX = position.getX() + cosf(orientation*(3.141f/180));
		float nextY = position.getY() + sinf(orientation*(3.141f/180));
		return mount->getSpeed()
				* ground::GroundMap::getInstance().getGroundCaseFactor(position,Point2D(nextX,nextY));
	}
}

void Unit::setPosition(Point2D p) {
	position = p;
	if (isMounted()){
		mount->setPosition(p);
	}
}

void Unit::setOrientation(float o){
	if (isMounted()){
		mount->setOrientation(o);
	}
	Entity::setOrientation(o);
}


unitEnumeration::UnitState Unit::getState() {
	return state;
}

void Unit::setState(unitEnumeration::UnitState s) {
	if (this->state != unitEnumeration::DEAD){
		state = s;
	}
}

float Unit::getCollisionRadius(){
	if (isMounted()){
		return mount->getCollisionRadius();
	}else{
		return Entity::getCollisionRadius();
	}
}

float Unit::getRadius(){
	if (isMounted()){
		return mount->getRadius();
	}else{
		return Entity::getRadius();
	}
}

Weapon* Unit::getWeapon() {
	return weapon;
}

void Unit::setWeapon(Weapon* w ) {
	weapon = w;
}

unitEnumeration::Armor Unit::getArmorType() {
	return armor;
}

void Unit::setArmorType(unitEnumeration::Armor a) {
	armor = a;
}

bool Unit::isMounted() {
	return (mount!=NULL);
}

bool Unit::isDead() {
	return state==unitEnumeration::DEAD;
}

bool Unit::isMoving() {
	return state==unitEnumeration::MOVE;
}

bool Unit::isCharging() {
	return state==unitEnumeration::CHARGE;
}

bool Unit::isInCombatPosture() {
	return state==unitEnumeration::COMBAT_POSTURE;
}

bool Unit::isFleeing() {
	return state==unitEnumeration::FLEE;
}

bool Unit::isWaiting() {
	return state==unitEnumeration::WAIT;
}

Point2D & Unit::getVisibilityPosition(){
	return this->position;
}
float Unit::getVisibilityRadius(){
	return 10;
}
float Unit::getHeadOffset(){
	if (isMounted()){
		return 5;
	}else{
		return 3;
	}
}
float Unit::getCutOffAngle(){
	return 180;
}
float Unit::getViewAngle(){
	return this->orientation;
}

Mount * Unit::getMount() {
	return this->mount;
}

GroupAI* Unit::getGroup() {
	return this->group;
}

void Unit::notifyEndOrder(std::string s) {
	pushAction(new ActionEndOrder(this, unitEnumeration::WAIT, s));
}

void Unit::signalCollision(){
	if (this->getCurrentAnimation() == RenderableEntity::WAIT){
		//state = Unit::MOVE;
		//this->setAnimation(RenderableEntity::WALK, isMounted());
		pushAction(new ActionMove(this,unitEnumeration::MOVE, Point2D((int)(position.getX()+0.5f),(int)(position.getY()+0.5f))));
	}
}

void Unit::load(std::string type){
	Config& config = Config::getInstance();
	setHealthPoint((int)config.getVariable(type,"healthPoint"));
	setSpeed(config.getVariable(type,"speed"));
	setRadius(config.getVariable(type,"radius"));
	std::string armor = config.getValue(type,"armor");
	if (armor == "LIGHT") {
		setArmorType(unitEnumeration::LIGHT);
	} else if (armor == "MEDIUM") {
		setArmorType(unitEnumeration::MEDIUM);
	} else if (armor == "HEAVY") {
		setArmorType(unitEnumeration::HEAVY);
	} else {
		setArmorType(unitEnumeration::LIGHT);
	}
	setWeapon(new Weapon(config.getValue(type,"weapon")));
}

void Unit::setAnimation(RenderSystem::RenderableEntity::AnimationType nextAnimation, bool mounted){
	RenderableEntity::setAnimation(nextAnimation,mounted);
	if (isMounted()){
		mount->setAnimation(nextAnimation,false);
	}
}

bool Unit::canCrossForest(){
	return !isMounted();
}
