#include "allegro.h"

#include "UnitInput.h"
#include "gameConstants.h"
#include "Unit.h"
#include "gameConstants.h"
#include "tasmMath.h"
#include "Round.h"

#include <vector>
#include <string>
#include <algorithm>
#include <queue>
using namespace std;

UnitInput :: UnitInput ()
{
  targetAffiliation = affiliationPlayer;
  targetPriorityHP = 0;
  targetPriorityDist = 0;
  switchTarget = switchTargetNever;

  homeTimeInbetweenCorrection = 3;
  homeAngleCorrection = itofix (15);

  attackPeriodIndex = 0;

  attackDistMin = itofix (0);
  attackDistMax = itofix (30000);
  attackAngleSpread = itofix (256);

  careAboutArena = false;

  //counters
  setCounters ();
}

UnitInput :: UnitInput (UnitInput * utCopy, int sub)
{
  //w00t! Copy constructor!

  targetPriorityHP = utCopy -> targetPriorityHP;
  targetPriorityDist = utCopy -> targetPriorityDist;
  switchTarget = utCopy -> switchTarget;
  homeTimeInbetweenCorrection = utCopy -> homeTimeInbetweenCorrection;
  homeAngleCorrection = utCopy -> homeAngleCorrection;
  targetAffiliation = utCopy -> targetAffiliation;
  attackPeriodIndex = utCopy -> attackPeriodIndex;
  attackDistMin = utCopy -> attackDistMin;
  attackDistMax = utCopy -> attackDistMax;
  attackAngleSpread = utCopy -> attackAngleSpread;
  careAboutArena = utCopy -> careAboutArena;

  setCounters ();

  //adjust sizes by subtypes
}

void UnitInput :: setCounters ()
{
  walkingDir = itofix (0);
  targetLocated = false;
  targetUnit = NULL;
  homeTimeInbetweenCorrectionCounter = 0;
  inAttackRange = false;
}

void UnitInput :: decideAction (Unit * thisUnit, UnitList * units, Round * rnd)
{
  //decides the action to take this round.

  //make sure the unit is active. (not dead or stunned)
  int cA = thisUnit -> getCharAnim ();
  bool shouldWalk = true;

  if (cA == charAnimDead || cA == charAnimStun)
    {
      return;
    }

  //Here's the plan:

  //get the target
  //get into range
  //face the desired range towards the target
  //attack
  //should you switch targets?

  //get the target
  if (!units -> thisUnitExists (targetUnit) || !targetLocated)
    {
      locateTarget (thisUnit, units);
    }

  if (targetLocated)
    {
      shouldWalk = aimIt (thisUnit, rnd);
    }

  moveIt (thisUnit, shouldWalk);

  if (inAttackRange)
    {
      attackP (thisUnit, 1);
      switchTargets (thisUnit, units);
    }
}

bool  UnitInput :: aimIt (Unit * thisUnit, Round * rnd)
{
  bool shouldWalk = true;
  fixed diffAngle;
  fixed diffAngle2;
  fixed distToTarget;
  fixed angleToTarget;
  fixed angCorrect;
  bool atEdgeOfArena = false;
  int atEdgeVert = 0;
  int atEdgeHoriz = 0;

  homeTimeInbetweenCorrectionCounter ++;

  distToTarget = calcDist (thisUnit -> getPosX (),
			  thisUnit -> getPosY (),
			  targetUnit -> getPosX (), 
			  targetUnit -> getPosY ());

  //compensate for the target's size
  distToTarget -= (targetUnit -> getPicSize() + thisUnit -> getPicSize())/2;

  angleToTarget = calcAngle (thisUnit -> getPosX (), thisUnit -> getPosY (), targetUnit -> getPosX (), targetUnit -> getPosY (), thisUnit -> getDrawAngle ());

  diffAngle = angleDist (calcAngle (thisUnit -> getPosX (), thisUnit -> getPosY (), targetUnit -> getPosX (), targetUnit -> getPosY (), walkingDir), thisUnit -> getWalkAngle ());
  
  diffAngle2 = angleDist (calcAngle (thisUnit -> getPosX (), thisUnit -> getPosY (), targetUnit -> getPosX (), targetUnit -> getPosY (), walkingDir), thisUnit -> getDrawAngle ());

  //assuming the unit cares about going out of bounds in the arena, this checks.
  if (careAboutArena)
    {
      if (thisUnit -> getPosX () <= thisUnit -> getPicSize ()/2)
	atEdgeHoriz = -1;
      
      if (thisUnit -> getPosX () >= rnd -> getArenaSizeX () - (thisUnit -> getPicSize ()/2))
	atEdgeHoriz = 1;

      if (thisUnit -> getPosY () <= thisUnit -> getPicSize ()/2)
	atEdgeVert = -1;

      if (thisUnit -> getPosY () >= rnd -> getArenaSizeY () - (thisUnit -> getPicSize ()/2))
	atEdgeVert = 1;

      if (atEdgeVert != 0 || atEdgeHoriz != 0)
	atEdgeOfArena = true;
    }

  if (homeTimeInbetweenCorrectionCounter > homeTimeInbetweenCorrection)
    {
      //get the difference in angle between the direction the units are pointing and the direction it is facing  
      walkingDir = thisUnit -> getDrawAngle ();

      if (diffAngle > (-1 * homeAngleCorrection) && diffAngle < homeAngleCorrection)
	walkingDir += diffAngle;
      else if (diffAngle < (-1 * homeAngleCorrection))
	walkingDir -= homeAngleCorrection;
      else if (diffAngle > homeAngleCorrection)
	walkingDir += homeAngleCorrection;

      thisUnit -> faceAngle (walkingDir);

      homeTimeInbetweenCorrectionCounter = 0;
    }

  //if in range, stop moving...unless at the edge of an arena
  if (distToTarget >= attackDistMin && distToTarget <= attackDistMax && !atEdgeOfArena)
    shouldWalk = false;

  //if too close, back up...unless at the edge of an arena
  if (distToTarget < attackDistMin && !atEdgeOfArena)
    {
      thisUnit -> setWalkAngle (angleToTarget + itofix (128));
    }

  //if at the edge of an arena, head towards the middle!
  if (atEdgeOfArena)
    {
      angCorrect = normalizeAngle (thisUnit -> getWalkAngle ());

      if (atEdgeVert < 0) {
	if(angCorrect >= itofix (0) && angCorrect < itofix (64))
	  thisUnit -> setWalkAngle (itofix (65));

	if(angCorrect >= itofix (192) && angCorrect < itofix (256))
	  thisUnit -> setWalkAngle (itofix (191));
      }

      if (atEdgeVert > 0) {
	if(angCorrect >= itofix (64) && angCorrect < itofix (128))
	  thisUnit -> setWalkAngle (itofix (63));

	if(angCorrect >= itofix (128) && angCorrect < itofix (192))
	  thisUnit -> setWalkAngle (itofix (193));
      }

      if (atEdgeHoriz < 0) {
	if(angCorrect >= itofix (128) && angCorrect < itofix (192))
	  thisUnit -> setWalkAngle (itofix (127));

	if(angCorrect >= itofix (192) && angCorrect < itofix (256))
	  thisUnit -> setWalkAngle (itofix (1));
      }

      if (atEdgeHoriz > 0) {
	if(angCorrect >= itofix (0) && angCorrect < itofix (64))
	  thisUnit -> setWalkAngle (itofix (255));

	if(angCorrect >= itofix (64) && angCorrect < itofix (128))
	  thisUnit -> setWalkAngle (itofix (129));
      }

      if (atEdgeVert > 0 && atEdgeHoriz < 0)
	  thisUnit -> setWalkAngle (itofix (32));

      if (atEdgeVert < 0 && atEdgeHoriz < 0)
	  thisUnit -> setWalkAngle (itofix (32 + 64));

      if (atEdgeVert < 0 && atEdgeHoriz > 0)
	  thisUnit -> setWalkAngle (itofix (32 + 128));

      if (atEdgeVert > 0 && atEdgeHoriz > 0)
	  thisUnit -> setWalkAngle (itofix (32 + 192));
    }


  //decide if in attack range.
  if (fixabs (diffAngle2) <= attackAngleSpread && distToTarget >= attackDistMin && distToTarget <= attackDistMax)
    inAttackRange = true;
  else
    inAttackRange = false;

  return shouldWalk;
}

void UnitInput :: moveIt (Unit * thisUnit, bool shouldWalk)
{
  if (shouldWalk)
    thisUnit -> walk (true);
}

void UnitInput :: locateTarget (Unit * thisUnit, UnitList * units)
{
  vector <Unit *> * targetAffiliationUnits;
  vector <aiUnitSort *> * aiUnitSorter = NULL;
  int sizeOfTau;

  targetAffiliationUnits = units -> search (searchByAffiliation, targetAffiliation);

  sizeOfTau = targetAffiliationUnits -> size ();

  if (sizeOfTau > 0)
    {
      //now then... analyze all of them. Give them -+3 depending on criterion. Then sort the pile.

      aiUnitSorter = new vector <aiUnitSort *> ();

      targetUnit = prepAIUnitSort (thisUnit, targetAffiliationUnits, aiUnitSorter);
      targetLocated = true;

      //don't target yourself...
      if (targetUnit == NULL)
	targetLocated = false;
    }
}

void UnitInput :: attackP (Unit * thisUnit, int attackIndex)
{
  vector <AddUnitTemplate *> * auts = thisUnit -> getAddedUnits (attackIndex);

  if (auts -> size () > 0 && thisUnit -> canAddUnit (auts) )
    {
      thisUnit -> addUnit (auts, false, subUnitNone);
    }
}

void UnitInput :: switchTargets (Unit * thisUnit, UnitList * units)
{
  //just attacked. should you switch targets?
  //set targetLocated to false if you should switch.

  if (switchTarget == switchTargetNever)
    return;

  //analyze the situation.
  if (switchTarget & switchAfterAttack)
    { targetLocated = false; return; }

  if (switchTarget & switchFullHP)
    {
      if (units -> thisUnitExists (targetUnit) && targetUnit -> getHP () >= targetUnit -> getMaxHP ())
	{ targetLocated = false; return; }
    }
}

Unit * UnitInput :: prepAIUnitSort (Unit * thisUnit, vector <Unit *> * uns, vector <aiUnitSort *> * aius)
{
  int hpCheckHelper = 100;
  fixed distCheckHelper = 0;
  vector <aiUnitSort *> ::iterator iter;
  aiUnitSort * thisOne = NULL;

  bool doneLooking = false;

  //sets up the aius, copying stuff over.

  aius -> resize (uns -> size ());

  for (int i = 0; i < int(uns -> size ()); i ++)
    {
      aius -> at (i) = new aiUnitSort;
      aius -> at (i) -> unitToLookAt = uns -> at (i);
    }

  //then sets the criterions.
  //negative criterion makes the target MORE likely to be picked.

  iter = aius -> begin ();
  while(iter < aius -> end()) {

    if (targetPriorityHP != 0){
      hpCheckHelper = 100 * (*iter) -> unitToLookAt -> getHP () / (*iter) -> unitToLookAt -> getMaxHP ();

      if (hpCheckHelper > 95)
	(*iter) -> hpCriterion = -3 * targetPriorityHP;
      else if (hpCheckHelper > 80)
	(*iter) -> hpCriterion = -2 * targetPriorityHP;
      else if (hpCheckHelper > 65)
	(*iter) -> hpCriterion = -1 * targetPriorityHP;
      else if (hpCheckHelper > 50)
	(*iter) -> hpCriterion = 0 * targetPriorityHP;
      else if (hpCheckHelper > 35)
	(*iter) -> hpCriterion = 1 * targetPriorityHP;
      else if (hpCheckHelper > 20)
	(*iter) -> hpCriterion = 2 * targetPriorityHP;
      else
	(*iter) -> hpCriterion = 3 * targetPriorityHP;
    }

    if (targetPriorityDist != 0){
      //do distance stuff here
    }

    iter ++;
  }

  //now get everything with the lowest criterion

  //sort em, by criterion.

  random_shuffle (aius -> begin (), aius -> end ());
  sort (aius -> begin (), aius -> end (), &UnitInput::aiULess);

  thisOne = NULL;

  iter = aius -> begin ();
  while (iter < aius -> end() && !doneLooking){
    thisOne = (*iter);
    if (thisOne -> unitToLookAt == thisUnit)
      { thisOne = NULL;
      doneLooking = false; }
    else
      doneLooking = true; 
    iter++;
  }
  
  if (thisOne == NULL)
    return NULL;

  return thisOne -> unitToLookAt;
}

bool UnitInput :: aiULess (aiUnitSort * a1, aiUnitSort * a2)
{
  //returns true if a1 < a2.
  return a1 -> isLessThan (*a2);
}

bool UnitInput :: aiUEqual (aiUnitSort * a1, aiUnitSort * a2)
{
  //returns true if a1 = a2.
  return a1 -> isEqualTo (*a2);
}

UnitInput :: ~UnitInput ()
{
}
