
#include "ActionHit.h"
#include "ActionFollow.h"
#include "UnitEnumeration.h"
#include "Unit.h"
#include "EnnemyUnit.h"
#include "Weapon.h"
#include "UnitAction.h"
#include "Renderer.h"
#include "MemoryManager/debugNew.h"
#include "DebugAI.h"

ActionHit::ActionHit(Unit* u,unitEnumeration::UnitState state, EnnemyUnit* e,float followOffset) : UnitAction(u,state){
	unit = u;
	ennemy = e;
	//timeBeforeHit = 25;
	timeBeforeHit = unit->getWeapon()->weaponSpeed;
	this->followOffset = followOffset;
}


ActionHit::~ActionHit(void)
{
}

void ActionHit::execute(){
	/**
	 * on restaure l'etat de l'unité
	 */
	unit->setState(this->unitState);
	unit->allowDust(false);
	// si l'unité est morte, on arrete de l'attaquer
	if(!ennemy->isAlive()){
		dispose();
		return;
	}
	// distance sans altitude entre les 2 unités
	float distance = (unit->getPosition() - ennemy->getPosition()).getLength();
	unit->setOrientation(unit->getPosition().getAngle(ennemy->getPosition()));

	if(distance > unit->getWeapon()->maxRange) {
		// l'unité n'est pas à porté, on doit se rapprocher de l'ennemy
		timeBeforeHit = unit->getWeapon()->weaponSpeed;
		unit->pushAction(this);
		unit->pushAction(new ActionFollow(unit,this->unitState, ennemy->getUnit(), unit->getWeapon()->maxRange-(unit->getRadius()+ennemy->getRadius()+followOffset), false));
		return;
	}
	else if(timeBeforeHit>0) {

		// on est en attaque, on prépare le coup
		if(unit->getCurrentAnimation()!=RenderSystem::RenderableEntity::FIGHT){
 			unit->setAnimation(RenderSystem::RenderableEntity::FIGHT, unit->isMounted());
		}

		//if(debug) printf("\n attaque dans %i top ",this->timeBeforeHit);
		// on ne tappe pas tout de suite, il faut attendre encore..
		timeBeforeHit--;
		unit->pushAction(this);

	} else {

		// on donne le coup
		if(unit->getCurrentAnimation()!=RenderSystem::RenderableEntity::FIGHT){
 			unit->setAnimation(RenderSystem::RenderableEntity::FIGHT, unit->isMounted());
		}
		processHit();
		ennemy->notifyAttack(unit->getPosition());

		// a-t-on tué l'ennemy? si non, on continu de l'attaquer
		if(ennemy->isAlive()){
			unit->attack(*ennemy);
		}
		dispose();

	} // fin de else
}

void ActionHit::processHit(){
	float damage = 0;
	float critical = 0;
	float fatal = 0;
	// % de chance de toucher l'unité et non le cheval si elle est montée.
	float chanceToHitMounted = 0;	
	// l'unité est a distance de tir, ie entre minRange et maxRange.	
	bool archerIsAtRange = false;	
	float distance = (unit->getPosition() - ennemy->getPosition()).getLength();

	if(unit->getWeapon()->rangedDamage > 0 && distance > unit->getWeapon()->minRange) {
		// on attaque à distance avec un archer
		// dommage de base (à distance)
		archerIsAtRange = true;
		damage = unit->getWeapon()->rangedDamage;

		DebugAI::trace('h', " attaque d'un archer a distance ");

	} else {
		// unité cac
		// dommage au cac
		damage = unit->getWeapon()->damage;
		DebugAI::trace('h', " attaque au corps a corps ");
	}

	// on détermine la situation des combattants (montés ou non)
	if (unit->isMounted()) {
		if (ennemy->isMounted()) {
			critical = unit->getWeapon()->criticalMountedVsMounted;
			fatal = unit->getWeapon()->fatalMountedVsMounted;
			chanceToHitMounted = unit->getWeapon()->chanceToHitMountedUnitMounted;
			DebugAI::trace('h', "attaque d'une unite montee sur une unite montee");
		}
		else {
			critical = unit->getWeapon()->criticalMounted;
			fatal = unit->getWeapon()->fatalHitMounted;
			DebugAI::trace('h', "attaque d'une unite montee sur une unite non montee");
		}
	}
	else {
		if (ennemy->isMounted()) {
			critical = unit->getWeapon()->criticalVsMounted;
			fatal = unit->getWeapon()->fatalVsMounted;
			chanceToHitMounted = unit->getWeapon()->chanceToHitMountedUnit;
			DebugAI::trace('h', "attaque d'une unite non montee sur une unite montee");
		}
		else {
			critical = unit->getWeapon()->criticalHit;
			fatal = unit->getWeapon()->fatalHit;
			DebugAI::trace('h', "attaque d'une unite non montee sur une unite non montee");
		}
	}


	if (archerIsAtRange) {
		// différence d'altitude entre les deux unités
		float altitudeDifference = (unit->getAltitude() - ennemy->getAltitude());
		DebugAI::trace('h', "tir de l'archer: La difference d'altitude  est de" + Converter::floatToString(altitudeDifference));
		if(altitudeDifference >= unit->getWeapon()->minAltitudeDifference) {
			critical *= unit->getWeapon()->altitudeModifier;
			fatal *= unit->getWeapon()->altitudeModifier;
			DebugAI::trace('h', "\tbonus d'attaque grace a l'altitude!");
		}
		if(altitudeDifference <= -unit->getWeapon()->minAltitudeDifference) {
			critical /= (unit->getWeapon()->altitudeModifier);
			fatal /= (unit->getWeapon()->altitudeModifier);
			DebugAI::trace('h', "\tmalus d'attaque a cause de l'altitude!");
		}

		// prise en compte de la distance (modification des dégats) 
		// TRES COMPLIQUE
		float a = unit->getWeapon()->minRange 
				+ (unit->getWeapon()->maxRange 
				- unit->getWeapon()->minRange)/2;
		float b = (float) abs( (int)(distance - a) )/a;
		float c;
		if(distance < a) {
			c = 1 + b*(unit->getWeapon()->proximityModifier);
		}
		else if(distance > a) {
			c = 1/(1 + b*(unit->getWeapon()->proximityModifier));
		}
		else {c = 1;}
		DebugAI::trace('h', "\ttir de l'archer: prise en compte de la distance de" + Converter::floatToString(distance));
		DebugAI::trace('h', "\tles damage sont multiplie par" + Converter::floatToString(c));
		damage *= c; 
	}


	// prise en compte de l'état de mouvement :
	// L'unité meme est en mouvement
	if (unit->isMoving()) {
		critical *= unit->getWeapon()->movementModifier;
		fatal *= unit->getWeapon()->movementModifier;
		DebugAI::trace('h', "l'unite attaquante est en mouvement");
	}

	// L'ennemi est en mouvement
	if (ennemy->isMoving()) {
		DebugAI::trace('h', "l'unite qui est attaquee est en mouvement");
		if (archerIsAtRange) {
			// archer a distance de tir
			critical *= unit->getWeapon()->movingEnnemyModifier;
			fatal *= unit->getWeapon()->movingEnnemyModifier;
		}
		else if (unit->getType()==unitEnumeration::INFANTRY){
			// l'unité est un fantassin
			if (ennemy->isFleeing()) {
				// l'ennemi est en fuite
				critical *= unit->getWeapon()->movingEnnemyModifier;
				fatal *= unit->getWeapon()->movingEnnemyModifier;
			}
		}
		else {
			// l'unité est un piquier
			if (ennemy->isCharging()) {
				// l'ennemi est en charge
				critical *= unit->getWeapon()->movingEnnemyModifier;
				fatal *= unit->getWeapon()->movingEnnemyModifier;
			}
		}
	}

	// prise en compte de la charge
	if (unit->isCharging()) {
		DebugAI::trace('h', "l'unite attaquante est en charge");
		critical *= unit->getWeapon()->chargeModifier;
		fatal *= unit->getWeapon()->chargeModifier;
	}


	// combatPosture?
	if (unit->isInCombatPosture() && !archerIsAtRange) {
		DebugAI::trace('h', "l'unite attaquante est en posture de combat");
		critical *= unit->getWeapon()->combatPostureModifer;
		fatal *= unit->getWeapon()->combatPostureModifer;
	}

	// Calcul des % de critique et fatal apres les modifiers
	int rand1 = (int) ( rand()%100 ) ;

	if(rand1 <= fatal) {
		// coup fatal
		// on affiche des particules pour signaler le coup fatal
		RenderSystem::Renderer::getInstance().addSparkParticle(unit->getPosition(),true,unit->getRadius()*2);
		DebugAI::trace('h', "COUP FATAL! L'UNITE ENNEMI EST TUEE DIRECT!");
		damage = (float)(ennemy->getHealthPoint());
	} else {
		if(rand1 <= critical) {
			// coup critique
			// on affiche des etincelle pour signaler le coup critique
			RenderSystem::Renderer::getInstance().addSparkParticle(ennemy->getPosition());
			if (archerIsAtRange){
				DebugAI::trace('h', "COUP CRITIQUE! les damage sont multiplie par" + Converter::floatToString(unit->getWeapon()->criticalDamageRangedMultiplier));
				damage *= unit->getWeapon()->criticalDamageRangedMultiplier;
			} else {
				DebugAI::trace('h', "COUP CRITIQUE! les damage sont multiplie par" + Converter::floatToString(unit->getWeapon()->criticalDamageMultiplier));
				damage *= unit->getWeapon()->criticalDamageMultiplier;
			}
		}


		// prise en compte de l'armure, modifie les dégats
		// L'ennemi est il monté ?
		if (ennemy->isMounted()) {
			DebugAI::trace('h', "l'unite qui est attaquee est montee");
			// Touche-t-on le cheval ?
			int rand2 = (int) ( rand()%100 );
			if (rand2 <= chanceToHitMounted) {
				// on touche l'unité, on modifie les dégats 
				// suivant l'arme et l'armure.
				switch (ennemy->getArmorType()) {
					case unitEnumeration::LIGHT  :
						DebugAI::trace('h', "l'unite attaquee a une armure legere");
						damage *= unit->getWeapon()->vsLightModifier;
						break;
					case unitEnumeration::MEDIUM : 
						DebugAI::trace('h', "l'unite attaquee a une armure moyenne");
						damage *= unit->getWeapon()->vsMediumModifier; 
						break;
					case unitEnumeration::HEAVY  : 
						DebugAI::trace('h', "l'unite attaquee a une armure lourde");
						damage *= unit->getWeapon()->vsHeavyModifier; 
						break;
					default : break;
				}
			} else {
				// on touche le cheval: damage
				DebugAI::trace('h', "\nFIN DE L'ATTAQUE avec" + Converter::floatToString((int)damage) + "damage sur la monture");
				ennemy->decreaseMountHealthPoint((int)damage);
				damage = 0;
			}
		}
	}

	if(damage!=0) {
		// enlever les points de vies à l'ennemi : damage
		DebugAI::trace('h', "FIN DE L'ATTAQUE  avec" + Converter::floatToString((int)damage) + "damage");
		ennemy->decreaseHealthPoint((int)damage);
	}

}
