#include "Ruleset.h"
#include "Player.h"
#include "MapUnit.h"
#include "../util/mathUtils.h"
#include <algorithm>
#include <iostream>
using namespace std;

Ruleset :: Ruleset ()
{
  p1.initialize(PLAYER_CLASS_NULL);
  mep.initialize();
  e1.initialize(0);

  battleFinished = false;
  playerWins = false;
  betweenLevels = false;

  enemyMapUnits.clear ();
  enemyMapUnitEngaged = NULL;
}

Ruleset :: ~Ruleset ()
{
  deleteBattle ();
  deleteStore ();
  deleteLevel ();
}

void Ruleset :: initialize ()
{

}

void Ruleset :: newBattle ()
{
  uint8_t enemyID = 0;

  deleteBattle ();

  battleFinished = false;
  playerWins = false;

  //make a new enemy
  e1.initialize (enemyID);

  //make a Player During Battle
  edb.initialize (e1);
  pdb.initialize (p1);

  //who goes first?
  playerTurn = true;

  //make battle abilities here. This should get ability information from the player.
  battleAbilities.resize(9);

  for (int i=0; i<9; i++)
    {
      battleAbilities[i].resetRechargeTime(16);
    }
}

void Ruleset :: deleteLevel ()
{
  mep.initialize();
  playerMapUnit.clear();
  enemyMapUnits.clear();
}

void Ruleset :: deleteBattle ()
{
  //clear resources associated with making the battle.
  e1.initialize(0);

  battleAbilities.clear();
}

void Ruleset :: newPlayer (uint8_t classID)
{
  p1.initialize (classID);
}

Player & Ruleset :: getPlayer ()
{
  return p1;
}

Enemy & Ruleset :: getEnemy ()
{
  return e1;
}

void Ruleset :: newGame ()
{
  //this is called when the user is starting a new game, from level 1.
  introBattle = true;
  setBetweenLevels (true);

  newLevel ();
}

void Ruleset :: addEnemyMapUnit (vector <uint8_t> :: iterator xPos, vector <uint8_t> :: iterator yPos, vector <string> :: iterator stringPos )
{
  MapUnit emu;
  emu.setMapPosition ((*xPos) * mep.getTileSize(),
                      (*yPos) * mep.getTileSize());
  emu.setResourceName ((*stringPos));
  enemyMapUnits.push_back(emu);
}

void Ruleset :: newLevel ()
{
  deleteLevel ();

  //make a new map
  mep.initialize();
  mep.loadMap ("Debug Map");

  //now put the player on the map.
  playerMapUnit.clear();
  playerMapUnit.setMapPosition (mep.getStartPointX()*mep.getTileSize(),
                                   mep.getStartPointY()*mep.getTileSize());
  playerMapUnit.setResourceName ("debugPlayerMapToon");

  //adding an enemy
  bool done=false;
  vector <uint8_t> :: iterator enemyPosXiter;
  vector <uint8_t> :: iterator enemyPosYiter;
  vector <string> :: iterator enemyPosMapUnitFrameIter;

  enemyPosXiter=mep.getEnemyStartingPositionsX().begin();
  enemyPosYiter=mep.getEnemyStartingPositionsY().begin();
  enemyPosMapUnitFrameIter=mep.getEnemyMapUnitPicNames().begin();

  while (!done)
    {
      if (enemyPosXiter==mep.getEnemyStartingPositionsX().end() ||
          enemyPosYiter==mep.getEnemyStartingPositionsY().end() ||
          enemyPosMapUnitFrameIter==mep.getEnemyMapUnitPicNames().end())
        done=true;
      else
        {
          addEnemyMapUnit (enemyPosXiter, enemyPosYiter, enemyPosMapUnitFrameIter);
          enemyPosXiter++;enemyPosYiter++;enemyPosMapUnitFrameIter++;
        }
    }

  mapWayPointX = 0;
  mapWayPointY = 0;
  mapWayPointActivate = false;
}

void Ruleset :: clearIntroBattle ()
{
  introBattle = false;
  setBetweenLevels (false);
}

bool Ruleset :: getIntroBattle ()
{
  return introBattle;
}

bool Ruleset :: isItPlayerTurn ()
{
  return playerTurn;
}

void Ruleset :: nextTurn ()
{
  playerTurn = !playerTurn;
}

void Ruleset :: newStore ()
{
  //create a new store
  stre.initialize();
  //STUB: Make an actual inventory.
  stre.addItemToStock ("ConsumeThis", "Consumable");
  stre.addItemToStock ("Equipment", "Equipment");

  //STUB: Make an actual power listing.
  stre.addAbilityToStore ("AwesomeAbility");
}

Store & Ruleset :: getStore ()
{
  return stre;
}

void Ruleset :: deleteStore ()
{
  stre.initialize();
}

bool Ruleset :: getBetweenLevels ()
{
  return betweenLevels;
}

uint8_t Ruleset :: determineWinner ()
{
  //BATTLE_RESULT_NO_BATTLE = there is no battle.
  //BATTLE_RESULT_NOT_FINISHED = battle in progress, no winner determined.
  //BATTLE_RESULT_PLAYER_WON = battle is over, player won.
  //BATTLE_RESULT_PLAYER_LOST = battle is over, player lost.

  if (!battleFinished)
    return BATTLE_RESULT_NOT_FINISHED;

  if (playerWins)
    return BATTLE_RESULT_PLAYER_WON;

  return BATTLE_RESULT_PLAYER_LOST;
}

//STUB: ruleset should know who won and who lost
void Ruleset :: declarePlayerWon ()
{
  battleFinished = true;
  playerWins = true;

  //remove the enemy unit from the map.
  if (enemyMapUnitEngaged!=NULL)
    enemyMapUnits.erase( remove( enemyMapUnits.begin(), enemyMapUnits.end(), (*enemyMapUnitEngaged) ), enemyMapUnits.end() );

  enemyMapUnitEngaged = NULL;
}

//STUB: ruleset should know who won and who lost
void Ruleset :: declarePlayerLost ()
{
  battleFinished = true;
  playerWins = false;
}

void Ruleset :: setBetweenLevels (bool a)
{
  betweenLevels = a;
}

void Ruleset :: applyVictory ()
{
  uint8_t xpAward = 1;
  uint8_t monAward = getEnemy().getMoney ();

  if (getEnemy().isABoss ())
    {
      if (xpAward * 3 > 255)
        xpAward = 255;
      else
        xpAward *= 3;

      if (monAward * 3 > 255)
        monAward = 255;
      else
        monAward *= 3;
    }

  //player won, add experience
  getPlayer().addExperience (xpAward);

  //get gold?
  getPlayer().addMoney (monAward);
}

Map & Ruleset :: getMap ()
{
  return mep;
}

MapUnit & Ruleset :: getPlayerMapUnit ()
{
  return playerMapUnit;
}

void Ruleset :: setMapWayPoint (int32_t x, int32_t y)
{
  //x and y are relative to the map.
  mapWayPointActivate = true;

  //if it's off the map, don't go there.
  if ((x < 0 || x >= mep.getMapWidth() * mep.getTileSize()) ||
      (y < 0 || y >= mep.getMapHeight() * mep.getTileSize()))
    mapWayPointActivate = false;

  //plan A: set the waypoint to where the mouse clicked.
  if (mapWayPointActivate)
    {
      mapWayPointX = x;
      mapWayPointY = y;
    }
}

int32_t Ruleset :: getMapWayPointX ()
{
  return mapWayPointX;
}

int32_t Ruleset :: getMapWayPointY ()
{
  return mapWayPointY;
}

bool Ruleset :: getMapWayPointActivate ()
{
  return mapWayPointActivate;
}

void Ruleset :: setActionTime (uint32_t tim)
{
  //call this when there are huge gaps in time that elapse.
  timeStamp=tim;
}

void Ruleset :: mapAction (uint32_t tim)
{
  //pass the current time. Action moves accordingly.
  playerMapUnit.mapAction(tim);

  //if a waypoint was set, tell the user to move there.
  if (mapWayPointActivate)
    {
      playerMapUnit.moveToDestination (getMapWayPointX(), getMapWayPointY(), tim);
      mapWayPointActivate=false;
    }

  vector <vector <uint8_t> > mapLayout;

  mep.getMapTerrain(mapLayout);

  //check all map units to see if they have intersected with any boxes.

  if (mapUnitMapIntersectionCheck (mapLayout, playerMapUnit, tim) == 0)
    {
      //reset player's position
      playerMapUnit.setMapPosition (mep.getStartPointX()*mep.getTileSize(),
                                    mep.getStartPointY()*mep.getTileSize());

      //tell it to stop moving
      playerMapUnit.stopMoving (tim);
    }
}

uint8_t Ruleset::mapUnitMapIntersectionCheck (vector <vector <uint8_t> > & mapLayout, MapUnit & mu, uint32_t tim)
{
  //checks mapLayout for tiles the unit shouldn't cross, then tells mu not to enter those sections.
  //if mu is unable to retreat to somewhere that doesn't violate, it returns 0.
  //if mu is able to retreat, this returns 1.
  //if mu is off the board, returns 2.

  uint8_t numRetries = 5; //failsafe for number of times to loop
  uint8_t result;
  bool lockAngle = false;

  if (playerMapUnit.getMapPositionX()>=mep.getMapWidth()*mep.getTileSize() ||
      playerMapUnit.getMapPositionY()>=mep.getMapHeight()*mep.getTileSize())
    return 2; //you're off the board.

  while (numRetries > 0)
    {
      result = mapUnitMapIntersectionCheckHelper (mapLayout, mu, tim, lockAngle);
      if (result==2) //if this MapUnit does not intersect with any tile, then leave
        return 1;
      else if (result==0) //it retreated somwhere unsafe. Call for help.
        return 0;
      else
        {
          //mu intersected but moved out of the way. Check to see if it intersected with something else.
          //But do not reverse direction when retreating. This prevents pingponging between two hit boxes for all eternity.
          numRetries--;
          lockAngle = true;
        }
    }

  return 0; //To prevent infinite loops I'm going to bail out right now.
}

uint8_t Ruleset::mapUnitMapIntersectionCheckHelper (vector <vector <uint8_t> > & mapLayout, MapUnit & mu, uint32_t tim, bool lockAngle)
{
  //Checks mu against all the mapLayout tiles.
  //Returns:
  //0 if it intersects with any tile and cannot retreat somewhere safe.
  //1 if it intersects with any tile and retreats somewhere safe.
  //2 if it does not intersect.

  bool done = false;

  uint8_t i, j, intersectReturnValue;

  i = 0; j = 0;

  while (!done)
    {
      if (mapLayout[i][j] == MAP_TERRAIN_NULL ||
          mapLayout[i][j] == MAP_TERRAIN_UNPASSABLE ||
          mapLayout[i][j] == MAP_TERRAIN_LOCKED)
        {
          intersectReturnValue = mu.backpedalHitRectangle (j*mep.getTileSize(),
                                                           ((j+1)*mep.getTileSize())-1,
                                                           i*mep.getTileSize(),
                                                           ((i+1)*mep.getTileSize())-1,
                                                           tim, lockAngle);
          if (intersectReturnValue==0)
            {
              //stuck, call for help.
              return intersectReturnValue;
            }
          else if (intersectReturnValue!=2)
            return intersectReturnValue;
        }

      if (j >= mapLayout[i].size ()-1)
        {
          j = 0;
          if (i >= mapLayout.size()-1) //iterated through map, we're done.
            return 2;
          else
            i++; //next row
        }
      else
        j++;
    }

  return 2;
}

bool Ruleset::isPlayerOnEndOfMap ()
{
  //returns true if the player is on the end of a map.
  return pointRectangleCollision (playerMapUnit.getMapPositionX(),
                           playerMapUnit.getMapPositionY(),
                           mep.getEndPointX()*mep.getTileSize(),
                           (mep.getEndPointX()+1)*mep.getTileSize()-1,
                           mep.getEndPointY()*mep.getTileSize(),
                           (mep.getEndPointY()+1)*mep.getTileSize()-1);
}

vector <MapUnit> & Ruleset::getEnemyMapUnits ()
{
  return enemyMapUnits;
}

bool Ruleset :: isPlayerAboutToEngageInBattle ()
{
  //returns true if the player is close to an enemy unit.
  if (enemyMapUnits.empty())
    return false;

  //iterate through, compare distance.
  vector <MapUnit> :: iterator enemyMapUnitIter;
  enemyMapUnitIter = enemyMapUnits.begin();

  int distX, distY;

  while (enemyMapUnitIter != enemyMapUnits.end())
    {
      //compare distances here
      distX = int((*enemyMapUnitIter).getMapPositionX()) - int(playerMapUnit.getMapPositionX());
      distY = int((*enemyMapUnitIter).getMapPositionY()) - int(playerMapUnit.getMapPositionY());

      if ((distX * distX) + (distY * distY) <= mep.getTileSize() * mep.getTileSize())
        {
          enemyMapUnitEngaged = &(*enemyMapUnitIter);
          //stop moving player
          playerMapUnit.stopMoving(SDL_GetTicks());
          return true;
        }

      enemyMapUnitIter++;
    }

  return false;
}

vector<BattleAbility> & Ruleset::getPlayerBattleAbilities ()
{
  return battleAbilities;
}

//fills given arguments comparing a selected ability, given the slot the attack appears in.
//abilityName is the name of the given ability.
//abilityType matches ability types found in Player.h . Wait is considered ABILITY_TYPE_WAIT.
//desc is the description of the ability.
//quantityOrRecharge is the number of items or the time to recharge, depending on the type.
//rechargeTimeRemaining is the amount of time left to recharge.
//pattern is the type of pattern the player needs to draw.
//allowedMisses is the number of misses the user is allowed to make before the attack fails. Negative counts are allowed.
//bonusDamage is the amount of damage added if the attack hits.
//minimalDamage warning is set if the user draws perfectly and still cannot overcome enemy armor.
//drawingDifficulty compares player's accuracy versus enemy evasion to estimate how easy it should be to draw.
void Ruleset::compareAbilityToEnemy (int16_t slot, int & abilityType,
                                     string & abilityName, string & desc,
                                     int & quantityOrRecharge, uint8_t & rechargeTimeRemaining, uint8_t & pattern,
                                     int & allowedMisses, uint8_t & bonusDamage, bool & minimalDamageWarning,
                                     int & drawingDifficulty)
{
  //first get the ability.
  vector<PlayerAbility>::iterator abilityIter;

  abilityType=getPlayer().retrieveEquippedAbilityBySlot(slot, abilityIter);

  if (abilityType==ABILITY_TYPE_NONE)
    {
      abilityName="No ability chosen";
      desc="";
      return;
    }

  //is this the wait command?
  if (abilityType==ABILITY_TYPE_WAIT)
    {
      abilityName="Wait";
      desc="Skip this turn.";
      return;
    }

  //set stuff specific to each type of ability.
  if (abilityType==ABILITY_TYPE_NATURAL)
    {
      quantityOrRecharge=int(abilityIter->getRechargeTime());
      rechargeTimeRemaining=battleAbilities[slot].getRechargeTimeRemaining();
    }
  else if (abilityType==ABILITY_TYPE_ITEM)
    {
      quantityOrRecharge=abilityIter->getQuantity();
    }

  //set stuff that is common to all types of abilities using abilityIter
  abilityName=abilityIter->getAbilityName();
  desc=abilityIter->getDescription();
  pattern=0; //STUB!

  //calculate the allowed misses
  //for now, STUB data
  allowedMisses=0;
  bonusDamage=0;
  minimalDamageWarning=false;

  //calculate the difficulty to draw the attack
  //STUB
  drawingDifficulty=0;
}
