/* 
 * 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
 *
 * GroupAI.cpp
 *
 * This class is used to manage group of units
 *
 * Date 15/02/06
 *
 */

#include "GroupAI.h"
#include "Config.h"
#include "DebugAI.h"

using namespace std;

GroupAI::GroupAI(void){
	// TODO faire comme constructeur qui suit
	hp = 0;
	maxHP = 0;
	pt2d = Point2D(0, 0);
}

GroupAI::GroupAI(InferenceEngine* ie, EnnemyManager* ennemyManager){
	// Initilisation of basics attributes
	DebugAI::trace('i', "init GroupAI\n");
	hp = 0;
	maxHP = 0;
	pt2d = Point2D(0, 0);
	myIe = ie;
	this->ennemyManager = ennemyManager;

	infoGround = NULL;

	// Initialisation of FuzzyValues
	allyHPHigh.setFuzzyNumber(&allyHP);
	allyHPMiddle.setFuzzyNumber(&allyHP);
	allyHPLow.setFuzzyNumber(&allyHP);

	farZone.setFuzzyNumber(&distance);
	closedZone.setFuzzyNumber(&distance);

	ennemyHPHigh.setFuzzyNumber(&ennemyHP);
	ennemyHPMiddle.setFuzzyNumber(&ennemyHP);
	ennemyHPLow.setFuzzyNumber(&ennemyHP);

	forceHigh.setFuzzyNumber(&force);
	forceMiddle.setFuzzyNumber(&force);
	forceLow.setFuzzyNumber(&force);

	freshZone.setFuzzyNumber(&fresh);
	outDatedZone.setFuzzyNumber(&fresh);

	nbEnnemisUnitHigh.setFuzzyNumber(&nbEnnemis);
	nbEnnemisUnitMiddle.setFuzzyNumber(&nbEnnemis);
	nbEnnemisUnitLow.setFuzzyNumber(&nbEnnemis);

	nbVisibleUnitHigh.setFuzzyNumber(&nbVisibleUnit);
	nbVisibleUnitMiddle.setFuzzyNumber(&nbVisibleUnit);
	nbVisibleUnitLow.setFuzzyNumber(&nbVisibleUnit);
	nbEnnemyIsNotZero.setFuzzyNumber(&nbVisibleUnit);

	//fvIsNotRegrouping.setFuzzyNumber(&fnIsNotRegrouping);

	// Initialisation of FuzzyNumbers
	allyHP = AllyHealthPoint(&allyHPHigh, &allyHPMiddle, &allyHPLow);
	allyHP.setAlly(this);

	distance = Distance(&farZone, &closedZone);
	
	ennemyHP = EnnemyHealthPoint(&ennemyHPHigh, &ennemyHPMiddle, &ennemyHPLow);

	force = Force(&forceHigh, &forceMiddle, &forceLow);
	force.setAlly(this);

	fresh = Fresh(&freshZone, &outDatedZone);

	nbEnnemis = NbEnnemis(&nbEnnemisUnitHigh, &nbEnnemisUnitMiddle, &nbEnnemisUnitLow);
	
	nbEnnemis.setAlly(this);

	nbVisibleUnit = NbVisibleUnit(&nbVisibleUnitHigh, &nbVisibleUnitMiddle, &nbVisibleUnitLow, &nbEnnemyIsNotZero);
	nbVisibleUnit.setEnnemy(ennemyManager);

	/*fnIsNotRegrouping = FNIsNotRegrouping(&fvIsNotRegrouping);
	fnIsNotRegrouping.setAlly(this);*/

	// Put the FuzzyNumbers in the vector
	vectFN.push_back(&allyHP);
	vectFN.push_back(&distance);
	vectFN.push_back(&ennemyHP);
	vectFN.push_back(&force);
	vectFN.push_back(&fresh);
	vectFN.push_back(&nbEnnemis);
	vectFN.push_back(&nbVisibleUnit);
	//vectFN.push_back(&fnIsNotRegrouping);

	// Intializing FuzzyRules

	frReconnaissance01 = FuzzyRule(&nbVisibleUnitLow, &implicationOp, &fvReconnaissance);
	frReconnaissance02 = FuzzyRule(&freshZone, &implicationOp, &fvReconnaissance);

	frAttack01 = FuzzyRule(&nbEnnemisUnitLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvAttack);
	frAttack02 = FuzzyRule(&ennemyHPLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvAttack);
	frAttack03 = FuzzyRule(&allyHPHigh, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvAttack);
	frAttack04 = FuzzyRule(&closedZone, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvAttack); // closedZone is a mistype: read closeZone

/*	frAttack01 = FuzzyRule(&nbEnnemisUnitLow, &implicationOp, &fvAttack);
	frAttack02 = FuzzyRule(&ennemyHPLow, &implicationOp, &fvAttack);
	frAttack03 = FuzzyRule(&allyHPHigh, &implicationOp, &fvAttack);
	frAttack04 = FuzzyRule(&closedZone, &implicationOp, &fvAttack);*/

	frFlee01 = FuzzyRule(&nbEnnemisUnitHigh, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvFlee);
	frFlee02 = FuzzyRule(&ennemyHPHigh, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvFlee);
	frFlee03 = FuzzyRule(&allyHPLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvFlee);
	frFlee04 = FuzzyRule(&closedZone, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvFlee);
	frFlee05 = FuzzyRule(&forceLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvFlee);

	/*frRegroup01 = FuzzyRule(&nbEnnemisUnitHigh, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvRegroup);
	frRegroup02 = FuzzyRule(&ennemyHPHigh, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvRegroup);
	frRegroup03 = FuzzyRule(&allyHPLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvRegroup);
	frRegroup04 = FuzzyRule(&closedZone, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvRegroup);
	frRegroup05 = FuzzyRule(&forceLow, &andOp, &nbEnnemyIsNotZero, &implicationOp, &fvRegroup);*/

	// Evaluation State
	evaluationRules.push_back(frReconnaissance01);
	evaluationRules.push_back(frReconnaissance02);
	evaluationRules.push_back(frAttack01);
	evaluationRules.push_back(frAttack02);
	evaluationRules.push_back(frAttack03);
	evaluationRules.push_back(frAttack04);
	evaluationRules.push_back(frFlee01);
	evaluationRules.push_back(frFlee02);
	evaluationRules.push_back(frFlee03);
	evaluationRules.push_back(frFlee04);
	evaluationRules.push_back(frFlee05);

	// Fight State
	fightRules.push_back(frAttack01);
	fightRules.push_back(frAttack02);
	fightRules.push_back(frAttack03);
	fightRules.push_back(frAttack04);
	fightRules.push_back(frFlee01);
	fightRules.push_back(frFlee02);
	fightRules.push_back(frFlee03);
	fightRules.push_back(frFlee04);
	fightRules.push_back(frFlee05);

	// Flee State
	fleeRules.push_back(frFlee01);
	fleeRules.push_back(frFlee02);
	fleeRules.push_back(frFlee03);
	fleeRules.push_back(frFlee04);
	fleeRules.push_back(frFlee05);

	// Move State
	moveRules.push_back(frReconnaissance01);
	moveRules.push_back(frReconnaissance02);
	moveRules.push_back(frAttack01);
	moveRules.push_back(frAttack02);
	moveRules.push_back(frAttack03);
	moveRules.push_back(frAttack04);
	moveRules.push_back(frFlee01);
	moveRules.push_back(frFlee02);
	moveRules.push_back(frFlee03);
	moveRules.push_back(frFlee04);
	moveRules.push_back(frFlee05);

	// Order
	orderFlee = new OrderFlee(this);
	orderReconnaissance = new OrderReconnaissance(this);
	orderAttack = new OrderAttack(this);
	orderEvaluation = new OrderEvaluation(this);
	orderRegroup = new OrderRegroup(this);

	this->ennemyGroup = NULL;

	// Intializing automate:
	evaluationState = new Evaluation(this, &evaluationRules);
	fightState = new Fight(this, &fightRules);
	fleeState = new Flee(this, &fleeRules);
	moveState = new Move(this, &moveRules);
	obeyState = new Obey(this, &obeyRules);
	errorState = new ErrorState(this, &errorRules); // errorRules should be an empty vector

	evaluationState->setStates(fightState, fleeState, moveState, obeyState, errorState);
	fightState->setStates(evaluationState, fleeState, moveState, obeyState, errorState);
	fleeState->setStates(evaluationState, fightState, moveState, obeyState, errorState);
	moveState->setStates(evaluationState, fightState, fleeState, obeyState, errorState);
	obeyState->setStates(evaluationState, fightState, fleeState, moveState, errorState);

	myState = evaluationState;
	myOrder = orderEvaluation;

	obey = false;

	// launch update() after constructing object
}

/*
GroupAI::GroupAI(InferenceEngine* ie, std::string state, FuzzyValueName order){
	// TODO make the appropriate constructor when the classes will be implemented
	myIe = ie;
	myState = new Evaluation();
	myOrder = Order();
	
}
*/
GroupAI::~GroupAI(void){
	delete orderEvaluation;
	delete orderReconnaissance;
	delete orderAttack;
	delete orderFlee;

	delete evaluationState;
	delete moveState;
	delete fightState;
	delete fleeState;
	delete obeyState;
	delete errorState;
}

void GroupAI::updateData(){
	// Update the pointers in the FuzzyNumbers
	Point2D positionGroupe;
	//distance.setInfoGround(infoGround);
	// La position du groupe allié est celle de la première unité de la liste
	if(units.size() != 0){
		positionGroupe = units.at(0)->getPosition();
	}
	else{
		positionGroupe = Point2D(0, 0);
	}
	pt2d = positionGroupe;
	// Update the ennemyGroup
	ennemyGroup = ennemyManager->getClosestGroup(positionGroupe);
	if(ennemyGroup == NULL){
		ennemyGroup = &emptyEnnemyGroup;
	}
	pt2dZone = ennemyGroup->getPosition();
	distance.setAllyCoordinates(positionGroupe);
	// La position qui sert à l'évaluation de la distance est la position du groupe ennemi le plus proche
	distance.setPoint2D(ennemyGroup->getPosition());
	ennemyHP.setEnnemy(ennemyGroup);
	force.setEnnemy(ennemyGroup);
	fresh.setInfoGround(infoGround); 
	fresh.setPt2D(positionGroupe);
	nbEnnemis.setEnnemy(ennemyGroup);
	support.setAlly(ally);
	for(int i=0; i < vectFN.size(); i++){
		vectFN.at(i)->update();
	}
}

void GroupAI::updateOrder(){	
	
	myIe->inferate(myState->getListOfFuzzyRules());
	// you have to get the result in the InferenceEngine
	FuzzyValue fuzzyV = myIe->getResult();
	fuzzyValueName newOrder = fuzzyV.getName();
	setOrder(newOrder);
	// Following lines are for test purpose
	// setOrder(ORDER_RECONNAISSANCE);
	// setOrder(ORDER_EVALUATION);
	// setOrder(ORDER_ATTACK);
	// setOrder(ORDER_FLEE);
}

void GroupAI::update(){
	if (!isObeying()) {
		// Change the current State if it is necessary
		updateData();
		updateOrder();
		State* tmpState = myState->change(myOrder);
		myState = tmpState;
		//std::string pause;
		//cin >> pause;
	}
}

std::string GroupAI::toString(){
	std::string thisGroupAI;

	// The current order
	thisGroupAI = "The current Order of this Group is : ";
	thisGroupAI += '\n';
	thisGroupAI += '\t';
	thisGroupAI += "No order, test";		// have to be delete after the tests
	//thisGroupAI += myOrder.getType();
	thisGroupAI += '\n';
	
	// Number of units in the group
	thisGroupAI += '\t';
	thisGroupAI += "There are ";
	thisGroupAI += Converter::floatToString(units.size());
	thisGroupAI += " unit(s) in this group";
	thisGroupAI += '\n';
	thisGroupAI += '\t';
	thisGroupAI += "HP : ";
	thisGroupAI += Converter::floatToString(hp);
	thisGroupAI += '\n';
	thisGroupAI += '\t';
	thisGroupAI += "HP max : ";
	thisGroupAI += Converter::floatToString(maxHP);
	thisGroupAI += '\n';
	thisGroupAI += '\t';
	thisGroupAI += "Position : (";
	thisGroupAI += Converter::floatToString(pt2d.getX());
	thisGroupAI += ", ";
	thisGroupAI += Converter::floatToString(pt2d.getY());
	thisGroupAI += ")";
	thisGroupAI += '\n';

	// The current state
	// All the FuzzyRules are given with state
	thisGroupAI += '\t';
	thisGroupAI += "The current State of this Group is : ";
	thisGroupAI += '\n';
	thisGroupAI += '\t';
	thisGroupAI += "No state, test";
	//thisGroupAI += myState->toString();
	thisGroupAI += '\n';
	
	thisGroupAI += '\t';
	thisGroupAI += "associated with the following FuzzyNumber : ";
	thisGroupAI += '\n';
	thisGroupAI += '\n';
	for(int i=0; i < vectFN.size(); i++){
		thisGroupAI += vectFN.at(i)->toString();
	}

	return thisGroupAI;
}

// Accessors
Mount* GroupAI::getVisibleFreeMountInRange(Point2D pt, float range) 
{
	float currentDistance;
	float closestDistance = 9999.0f;
	Mount* mount = NULL;

	std::map<void*,Mount*>* freeMounts = FreeMountManager::getInstance().getAllFreeMount();

	for(std::map<void*,Mount*>::iterator it=freeMounts->begin(); it!=freeMounts->end(); ++it) {
		currentDistance = (pt - (it->second)->getPosition()).getLength();
		if (infoGround->isVisible((it->second)->getPosition()))
		{
			if ( currentDistance <= range && currentDistance < closestDistance ) {
				mount = (it->second);
				closestDistance = currentDistance;
			}
			//if (closestDistance < range) return mount;
		}
	}
	return mount;
}

State* GroupAI::getState() {
	return myState;
}
int GroupAI::getNumber(void){
	return units.size();
}

int GroupAI::getHP(void){
	int hp = 0;
	for(std::vector<Unit*>::iterator it = units.begin(); it != units.end(); ++it){
		hp += (*it)->getHealthPoint();
	}
	return hp;
}

int GroupAI::getHPMax(void){
	static float infantryMaxHP = Config::getInstance().getVariable("Infantry","healthPoint");
	static float lancerMaxHP = Config::getInstance().getVariable("Lancer","healthPoint");
	static float archerMaxHP = Config::getInstance().getVariable("Archer","healthPoint");
	int ret = 0;
	for(std::vector<Unit*>::iterator it = units.begin(); it != units.end(); ++it){
		switch((*it)->getType()){
			case unitEnumeration::ARCHER:
				ret += archerMaxHP;
				break;
			case unitEnumeration::INFANTRY:
				ret += infantryMaxHP;
				break;
			case unitEnumeration::LANCER:
				ret += lancerMaxHP;
				break;
		}
	}
	return ret;
}


Point2D GroupAI::getPosition(){
	Point2D ret;
	for (std::vector<Unit*>::iterator it = units.begin();  it != units.end(); ++it){
		ret += (*it)->getPosition();
	}
	float x =ret.getX();
	float y =ret.getY();
	x = x /(units.size());	
	y = y /(units.size());
	//cout << "\n x:"<<x<<" y:"<<y<<"\n";
	ret = Point2D(x, y);
	return ret;
}


int GroupAI::getForce(void){
	int ret = 0;
	for(std::vector<Unit*>::iterator it = units.begin(); it != units.end(); ++it){
		ret += (*it)->getWeapon()->damage;
	}
	return ret; 
}

Point2D GroupAI::getZone(void){
	return ennemyGroup->getPosition();
}

std::vector<Unit*>* GroupAI::getUnits()
{
	return &units;
}

ground::GroundArmyInfo* GroupAI::getGroundArmyInfo()
{
	return infoGround;
}
EnnemyManager* GroupAI::getEnnemyManager() {
	return ennemyManager;
}

GroupEnnemy* GroupAI::getGroupToAttack() {
	return ennemyGroup;
}

FuzzyValueName GroupAI::getOrderType(void){
	return myOrder->getType();
}

bool GroupAI::isRegrouped() {
	return orderRegroup->isRegrouped();
}

bool GroupAI::isObeying() {
	return obey;
}
// Modificators
void GroupAI::setOrder(FuzzyValueName newOrder) {
	if (newOrder != ORDER_NO_ORDER) {
		switch (newOrder) {
			case ORDER_FLEE :
				myOrder = orderFlee;
				break;
			case ORDER_RECONNAISSANCE :
				myOrder = orderReconnaissance;
				break;
			case ORDER_ATTACK :
				myOrder = orderAttack;
				break;
			case ORDER_EVALUATION :			
				myOrder = orderEvaluation;
				break;
			case ORDER_REGROUP :
				myOrder = orderRegroup;
				break;
			default :
				std::cerr << "Error : wrong Order" << std::endl;
		}
		myOrder->execute();
	}
}


void GroupAI::setInfoGround(ground::GroundArmyInfo* infoG){
	infoGround = infoG;
}

void GroupAI::setGroupEnnemy(GroupEnnemy* ennemy){
	ennemyGroup = ennemy;
	// on passe le groupe d'ennemis au fuzzy values
	distance.setPoint2D(ennemyGroup->getPosition());
	ennemyHP.setEnnemy(ennemyGroup);
	force.setEnnemy(ennemyGroup);
	nbEnnemis.setEnnemy(ennemyGroup);
}

void GroupAI::setAlly(GroupAI* ally){
	this->ally = ally;
}

void GroupAI::setState(StateName state){
	switch (state) {
		case EVALUATION :
			DebugAI::trace('z', "Forcing State -> Evaluation");
			myState = evaluationState;
			break;
		case FIGHT :
			DebugAI::trace('z', "Forcing State -> Fight");
			myState = fightState;
			break;
		case FLEE :
			DebugAI::trace('z', "Forcing State -> Flee");
			myState = fleeState;
			break;
		case MOVE :			
			DebugAI::trace('z', "Forcing State -> Move");
			myState = moveState;
			break;
		default :
			std::cerr << "Error : wrong state" << std::endl;
	}
}



void GroupAI::setObey(bool b) {
	obey = b;
}
// Fonctions for units
void GroupAI::addUnit(Unit* unit){
	if(!containUnit(unit)){
		units.push_back(unit);
		unit->setGroupAI(this);
	}
}

void GroupAI::removeUnit(Unit* unit){
	for (std::vector<Unit*>::iterator it = units.begin();  it != units.end(); ++it){
		if(*it == unit){
			units.erase(it);
			return;
		}
	}
}

void GroupAI::removeDeadUnits() {
	std::vector<Unit*>::iterator it = units.begin();
	while(it != units.end()){
		if((*it)->isDead()) {
			it = units.erase(it);
			if (it == units.end()) break;
		}
		++it;
	}
}

bool GroupAI::containUnit(Unit* unit){
	for (std::vector<Unit*>::iterator it = units.begin();  it != units.end(); ++it){
		if(*it == unit){
			return true;
		}
	}
	return false;
}

void GroupAI::removeAllUnit(){
	units.clear();
}

void GroupAI::resetActions() {
	for (std::vector<Unit*>::iterator it = units.begin();  it != units.end(); ++it){
		(*it)->wait();
	}
}

void GroupAI::notifyEndOrder(Unit* u) {
	myOrder->notifyEndOrder(u);
}

