#include "Player.h"

Player :: Player ()
{
  initialize(PLAYER_CLASS_NULL);
}

Player :: ~Player ()
{
}

void Player::clear(int cID)
{
  initialize(cID);
}

void Player :: initialize (int cID)
{
  classID = cID;
  level = 1;
  experience = 0;
  experienceToLevel = 0;
  money = 0;
  name.assign ("Debug");

  inventory.clear();
  equippedItems.clear();
  baseStats.clear();
  baseStats.resize(PLAYER_STAT_NUMBER, 0);
  equipmentStats.clear();
  equipmentStats.resize(PLAYER_STAT_NUMBER, 0);

  playerAbilities.clear();
  readiedAbilities.clear();
  learnedAbilities.clear();

  switch (cID)
    {
    case (PLAYER_CLASS_WARRIOR):
      baseStats[PLAYER_STAT_HEALTH]=16;
      baseStats[PLAYER_STAT_ARMOR]=1;
      baseStats[PLAYER_STAT_ACCURACY]=2;
      baseStats[PLAYER_STAT_DAMAGE]=3;
      baseStats[PLAYER_STAT_PIERCE]=4;
      baseStats[PLAYER_STAT_EVADE]=5;
      baseStats[PLAYER_STAT_LUCK]=6;
      break;
    case (PLAYER_CLASS_ARCHER):
      baseStats[PLAYER_STAT_HEALTH]=14;
      baseStats[PLAYER_STAT_ARMOR]=1;
      baseStats[PLAYER_STAT_ACCURACY]=2;
      baseStats[PLAYER_STAT_DAMAGE]=3;
      baseStats[PLAYER_STAT_PIERCE]=4;
      baseStats[PLAYER_STAT_EVADE]=5;
      baseStats[PLAYER_STAT_LUCK]=6;
      break;
    case (PLAYER_CLASS_MAGE):
      baseStats[PLAYER_STAT_HEALTH]=12;
      baseStats[PLAYER_STAT_ARMOR]=1;
      baseStats[PLAYER_STAT_ACCURACY]=2;
      baseStats[PLAYER_STAT_DAMAGE]=3;
      baseStats[PLAYER_STAT_PIERCE]=4;
      baseStats[PLAYER_STAT_EVADE]=5;
      baseStats[PLAYER_STAT_LUCK]=6;
      break;
    case (PLAYER_CLASS_SWASHBUCKLER):
      baseStats[PLAYER_STAT_HEALTH]=10;
      baseStats[PLAYER_STAT_ARMOR]=1;
      baseStats[PLAYER_STAT_ACCURACY]=2;
      baseStats[PLAYER_STAT_DAMAGE]=3;
      baseStats[PLAYER_STAT_PIERCE]=4;
      baseStats[PLAYER_STAT_EVADE]=5;
      baseStats[PLAYER_STAT_LUCK]=6;
      break;
    default:
      baseStats[PLAYER_STAT_HEALTH]=8;
      baseStats[PLAYER_STAT_ARMOR]=1;
      baseStats[PLAYER_STAT_ACCURACY]=2;
      baseStats[PLAYER_STAT_DAMAGE]=3;
      baseStats[PLAYER_STAT_PIERCE]=4;
      baseStats[PLAYER_STAT_EVADE]=5;
      baseStats[PLAYER_STAT_LUCK]=6;
      classID = PLAYER_CLASS_NULL;
      break;
    };
}

int Player :: getBaseHealth ()
{ return baseStats[PLAYER_STAT_HEALTH]; }

int Player :: getBaseAccuracy ()
{ return baseStats[PLAYER_STAT_ACCURACY]; }

int Player :: getBaseDamage ()
{ return baseStats[PLAYER_STAT_DAMAGE];}

int Player :: getBaseEvade ()
{ return baseStats[PLAYER_STAT_EVADE];}

int Player :: getBasePierce ()
{ return baseStats[PLAYER_STAT_PIERCE];}

int Player :: getBaseArmor ()
{return baseStats[PLAYER_STAT_ARMOR];}

int Player :: getBaseLuck ()
{return baseStats[PLAYER_STAT_LUCK];}

int Player :: getHealth ()
{ return getBaseHealth()+equipmentStats[PLAYER_STAT_HEALTH];}

int Player :: getArmor ()
{ return getBaseArmor()+equipmentStats[PLAYER_STAT_ARMOR];}

int Player :: getAccuracy ()
{ return getBaseAccuracy()+equipmentStats[PLAYER_STAT_ACCURACY];}

int Player :: getDamage ()
{ return getBaseDamage()+equipmentStats[PLAYER_STAT_DAMAGE];}

int Player :: getPierce ()
{ return getBasePierce()+equipmentStats[PLAYER_STAT_PIERCE];}

int Player :: getEvade ()
{  return getBaseEvade()+equipmentStats[PLAYER_STAT_EVADE];}

int Player :: getLuck ()
{  return getBaseLuck()+equipmentStats[PLAYER_STAT_LUCK];}

int Player :: getMoney ()
{ return money;}

int Player :: getLevel ()
{ return level;}

int Player :: getExperience ()
{ return experience;}

void Player :: setHealth (int x)
{ baseStats[PLAYER_STAT_HEALTH] = x; }

void Player :: setAccuracy (int x)
{ baseStats[PLAYER_STAT_ACCURACY] = x; }

void Player :: setDamage (int x)
{ baseStats[PLAYER_STAT_DAMAGE] = x; }

void Player :: setEvade (int x)
{ baseStats[PLAYER_STAT_EVADE] = x; }

void Player :: setPierce (int x)
{ baseStats[PLAYER_STAT_PIERCE] = x; }

void Player :: setArmor (int x)
{ baseStats[PLAYER_STAT_ARMOR] = x; }

void Player :: setLuck (int x)
{ baseStats[PLAYER_STAT_LUCK] = x; }

void Player :: addMoney (int x)
{ money += x; }

void Player :: setMoney (int x)
{ money = x; }

void Player :: setLevel (int x)
{ level = x; }

void Player :: setExperience (int x)
{ experience = x; }

const string & Player :: getName ()
{ return name; }

void Player :: setName (const string & x)
{ name.assign (x); }

int Player :: getClassID ()
{return classID;}

void Player :: setClassID (int x)
{ classID = x; }

int Player :: numAbilitiesAtCurrentLevel ()
{
  //returns the number of abilities the user can buy at this level.
  int lev = getLevel ();
  if (lev < 2)
    return 3;
  if (lev < 4)
    return 4;
  if (lev < 6)
    return 5;
  if (lev < 8)
    return 6;
  if (lev < 9)
    return 7;
  if (lev < 10)
    return 8;
  return 9;
}

void Player :: addExperience (int x)
{
  setExperience (getExperience () + x);
}

bool Player :: checkForLevelUp ()
{
  return (getExperience () > getExperienceToLevel ());
}

int Player :: getExperienceToLevel ()
{
  return experienceToLevel;
}

void Player :: setExperienceToLevel (int x)
{
  experienceToLevel = x;
}

void Player :: levelUp ()
{
  //read file to tell what the player gains from leveling up.

  //recursively call levelUpHelper until experienceToLevel is too high.

  //open file here.
  xmlDocPtr doc;
  xmlXPathContextPtr xpathCtx;
  xmlXPathObjectPtr xpathObj;
  string xpathStart="/PlayerManifest/Player[@ID='";
  xpathStart+="Debug"; //STUB: Player's name
  xpathStart+="']";

  if (!prepareXMLFile (doc, xpathCtx, "resources/game/PlayerManifest.xml", xpathStart))
      return;

  xpathObj=getElementsUsingXPath (doc, xpathCtx, xpathStart, "", "");

  if (xpathObj==NULL)
    {
      //Failed to load shape. Cleanup and exit.
      xmlXPathFreeContext(xpathCtx);
      xmlFreeDoc (doc);
      writeToErrorLog ("Failed to load levelUp", 0);
      return;
    }

  xpathStart+="/LevelUp/Level";

  while (getLevel () < 255 && getExperience () > getExperienceToLevel ())
    {
      levelUpHelper (doc, xpathCtx, xpathStart);
    }

  xmlXPathFreeContext(xpathCtx);
  xmlXPathFreeObject(xpathObj);
  xmlFreeDoc(doc);
}

void Player :: levelUpHelper (xmlDocPtr & doc, xmlXPathContextPtr & xpathCtx, string & xpath)
{
  //this levels up exactly 1 level.

  int change;
  xmlChar *element;

  //All the "change = 1" lines are STUBS.

  //read the file at level (new level)
  setLevel (getLevel () + 1);

  char searchString [255];
  sprintf (searchString, "%u", getLevel());
  xpath+=searchString;

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Health", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseHealth () + change > 255)
    setHealth (255);
  else
    setHealth (getBaseHealth () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Armor", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseArmor () + change > 255)
    setArmor (255);
  else
    setArmor (getBaseArmor () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Accuracy", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseAccuracy () + change > 255)
    setAccuracy (255);
  else
    setAccuracy (getBaseAccuracy () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Damage", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseDamage () + change > 255)
    setDamage (255);
  else
    setDamage (getBaseDamage () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Pierce", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBasePierce () + change > 255)
    setPierce (255);
  else
    setPierce (getBasePierce () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Evade", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseEvade () + change > 255)
    setEvade (255);
  else
    setEvade (getBaseEvade () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Luck", "");
  if (element == NULL)
    change=0;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  if (getBaseLuck () + change > 255)
    setLuck (255);
  else
    setLuck (getBaseLuck () + change);

  element=getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Experience", "");
  if (element == NULL)
    change=2;
  else
    {
      change=int(xmlXPathCastStringToNumber(element));
      xmlFree(element);
    }
  setExperienceToLevel (getExperienceToLevel () + change);
}

void Player::clearDuringBattleData()
{
  currentHealth=0;
  status.clear();
}

void Player::startDuringBattleData()
{
  clearDuringBattleData();
  currentHealth=getHealth();
  status.resize(AILMENT_TOTAL, 0);
}

int Player::getCurrentHealth ()
{
  return currentHealth;
}

void Player::setCurrentHealth (int x)
{
  //cap current health at maximum health.
  currentHealth = x;
  if (currentHealth > getHealth())
    currentHealth=getHealth();
}

void Player::loseCurrentHealth (int x)
{
  setCurrentHealth(getCurrentHealth()-x);
}

bool Player::isDead ()
{
  return (currentHealth<=0);
}

//Inventory Management
//This will try to add the item to the inventory.
//If this item already exists it will increase the quantitiy.
//(NOT IMPLEMENTED YET)If this item has an ability, it will try to add the ability to the player's repetiore
//Returns false if the item was not added.
bool Player::addItem(const string & itemName)
{
  //Do you have room in inventory?
  if (getInventorySize()>=PLAYER_ITEM_MAX)
    return false;

  map<string, Item>::iterator itemIter;
  Item newItem;

  if (getItem(itemName, itemIter))
    {
      //if the item is already in inventory, increment the quantity.
      itemIter->second.addQuantity(1);
      return true;
    }
  else
    {
      //item does not exist yet. Create it.
      //Reject if item is invalid.
      if (!newItem.loadItem(itemName))
	return false;

      //Now add the item to inventory.
      inventory[itemName]=newItem;

      //(NOT IMPLEMENTED YET)If the item has an ability, add that to the player's repetiore.
    }
  return true;
}

//returns the number of items in inventory.
int Player::getInventorySize ()
{
  map<string, Item>::iterator itemIter;
  itemIter=inventory.begin();
  int size=0;

  while (itemIter!=inventory.end())
    size+=itemIter->second.getQuantity();
  return size;
}

//finds the item with the name itemName, sets itemIter to that item and returns true.
//returns false if it cannot find the item.
bool Player::getItem (const string & itemName, map<string, Item>::iterator & itemIter)
{
  itemIter=inventory.find(itemName);
  return (itemIter!=inventory.end());
}

//refunds the player for the cost of the item, then decrements the quantity.
bool Player::sellItem (const string & itemName)
{
  map <string, Item>::iterator itemToSell;
  //bail if you couldn't find the item.
  if (!getItem(itemName, itemToSell))
    return false;

  int amountToRefund = itemToSell->second.getPrice()*PLAYER_SELL_RATIO/16;
  addMoney(amountToRefund);
  decrementItem (itemName);
  return true;
}
//This will remove (amount) uses from the item. If the quantity of the item is now negative, the item will be removed from inventory.
//Default value is 1. Any negative value will mean the item should be removed regardless of quantity.
//(NOT IMPLEMENTED YET) If the item had a PlayerAbility, this will try to remove the ability.
//(NOT IMPLEMENTED YET) If the item is equippable, this will try to unequip it.
//returns false if the item does not exist.
bool Player::decrementItem (const string & itemName, int amount)
{
  map<string, Item>::iterator itemToDecrement;

  //bail if the item does not exist.
  if (!getItem(itemName,itemToDecrement))
    return false;

  //If amount is positive, remove quantity.
  if (amount > 0)
    itemToDecrement->second.addQuantity(-1*amount);

  //See if you should remove the item based on its quantity.
  if (itemToDecrement->second.getQuantity()<=0)
    {
      //(NOT IMPLEMENTED YET) Try to remove the ability
      //(NOT IMPLEMENTED YET) Try to unequip this item.

      //remove the item
      inventory.erase(itemToDecrement);
    }
  return true;
}

//Player attempts to equip the item. Return true if sucessful and add its name to the list of equipped Items.
//The player's final stats are also updated.
//Possible reasons for failure:
//-The Player does not have this item.
//-The Item is not equippable
//-The player cannot equip more than PLAYER_EQUIPPED_ITEM_MAX items at a time.
//-All copies of the item have already been equipped
bool Player::equipItem(const string & itemName)
{
  map <string, Item> :: iterator itemToEquip;
  itemToEquip=inventory.find(itemName);

  //Return false if the player doesn't own this item.
  if (itemToEquip==inventory.end())
    return false;

  //Return false if the item is not equippable
  if (itemToEquip->second.getItemType()!=ITEM_TYPE_EQUIPMENT)
    return false;

  //Return false if the player has already equipped PLAYER_EQUIPPED_ITEM_MAX items.
  if (getNumberOfEquippedItems()>=PLAYER_EQUIPPED_ITEM_MAX)
    return false;

  //Return false if all of the copies have been equipped
  if (itemToEquip->second.getNumberEquipped() == itemToEquip->second.getQuantity())
    return false;

  //Equip the item.
  itemToEquip->second.equip();

  //Redo the Player's stats.
  rebuildEquipmentStats();
  return true;
}

//Returns the number of currently equipped items.
int Player::getNumberOfEquippedItems()
{
  vector <string> :: iterator nameOfEquippedItem;
  map <string, Item> :: iterator itemToEquip;
  int count=0;

  for (nameOfEquippedItem=equippedItems.begin(); 
       nameOfEquippedItem!=equippedItems.end(); 
       nameOfEquippedItem++)
    {
      if (getItem(*nameOfEquippedItem, itemToEquip))
	count+=itemToEquip->second.getQuantity();
    }
  return count;
}

//Rebuilds equipmentStats based on the current equipment.
void Player::rebuildEquipmentStats()
{
  vector <string> :: iterator nameOfEquippedItem;
  map <string, Item> :: iterator itemToEquip;
  equipmentStats.clear();
  equipmentStats.resize(PLAYER_STAT_NUMBER, 0);

  for (nameOfEquippedItem=equippedItems.begin(); 
       nameOfEquippedItem!=equippedItems.end(); 
       nameOfEquippedItem++)
    {
      if (getItem(*nameOfEquippedItem, itemToEquip))
	{
	  equipmentStats[PLAYER_STAT_HEALTH]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getHealthBoost();
	  equipmentStats[PLAYER_STAT_ARMOR]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getArmorBoost();
	  equipmentStats[PLAYER_STAT_ACCURACY]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getAccuracyBoost();
	  equipmentStats[PLAYER_STAT_DAMAGE]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getDamageBoost();
	  equipmentStats[PLAYER_STAT_PIERCE]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getPierceBoost();
	  equipmentStats[PLAYER_STAT_LUCK]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getLuckBoost();
	  equipmentStats[PLAYER_STAT_EVADE]+=itemToEquip->second.getNumberEquipped()*itemToEquip->second.getEvadeBoost();
	}
    }
  return;
}

//Try to unequip the item. Return true if successful. Recalculate the player's stats afterward.
//Reasons to return false:
//-The item is not equipped.
//-The item is not equippable.
//-The player doesn't own the item.
//-The item is not equipped.
bool Player::unequipItem(const string & itemName)
{
  vector <string> :: iterator listingToDelete;
  listingToDelete=find(equippedItems.begin(), equippedItems.end(), itemName);

  //Return false if the item isn't equipped
  if (listingToDelete==equippedItems.end())
    return false;

  map <string, Item> :: iterator itemToEquip;
  //Return false if the player doesn't own this item.
  if (!getItem(itemName, itemToEquip))
    return false;

  //Return false if the item is not equippable
  if (itemToEquip->second.getItemType()!=ITEM_TYPE_EQUIPMENT)
    return false;

  //Return false if none of the copies have been equipped
  if (itemToEquip->second.getNumberEquipped() <= 0)
    return false;

  //Unequip the item.
  itemToEquip->second.unequip();

  //If there are no more equipped copies, remove it from the listing.
  if (itemToEquip->second.getNumberEquipped() <= 0)
    equippedItems.erase(listingToDelete);

  //Redo the Player's stats.
  rebuildEquipmentStats();
  return true; 
}

//This will try to retrieve the power found at the given slot and set ans to it.
//Return false if:
//-The slot is unassigned or is NULL_INDEX
//-The slot is assigned but the Player does not have access to this ability.
bool Player::getPlayerAbility (map<string,PlayerAbility>::iterator & ans, int slot)
{
  map <string, string>::iterator nameOfPlayerAbility;
  //Return false if the slot is unassigned.
  if (!readiedAbilities.get(slot, nameOfPlayerAbility))
    return false;
  //Return false if the ability is NULL_INDEX
  if (nameOfPlayerAbility->second=="NULL_INDEX")
    return false;

  //Name of PlayerAbility is nameOfPlayerAbility->second.
  ans=playerAbilities.find(nameOfPlayerAbility->second);
  return (ans!=playerAbilities.end());
}

//Try to learn an ability and add it to the list of learned abilities. Return true if successful.
//If the player already knows this ability, do nothing and return true.
//Return false if:
//-There is no ability with that name.
//-The player does not meet the class and level requirements for this ability.
bool Player::learnPlayerAbility (const string & abilityName)
{
  PlayerAbility tempPA;

  //return false if it fails to load.
  if (!tempPA.loadPlayerAbility(abilityName))
    return false;

  //check class requirements. Return false if the minimum level is too high.
  if (tempPA.getMinLevel(getClassID()) > getLevel())
    return false;

  //Make sure the player does not already know this ability. If the player does, return true but do nothing.
  vector <string> :: iterator learnedIter;
  learnedIter=find(learnedAbilities.begin(), learnedAbilities.end(), tempPA.getAbilityName());
  if (learnedIter != learnedAbilities.end())
    return true;

  //The player should learn this. Try to add the ability.
  if (!addPlayerAbility(tempPA))
    return false;
  
  //Add this ability to the list of known abilities.
  learnedAbilities.push_back(tempPA.getAbilityName());
  return true;
}

//add an ability to the player's list of usable abilities. Return true upon success.
//If the player already knows this ability, it will return true and do nothing.
bool Player::addPlayerAbility (PlayerAbility & paToAdd)
{
  map <string, PlayerAbility> :: iterator abilityIter;

  abilityIter=playerAbilities.find(paToAdd.getAbilityName());
  //if it already exists, return true
  if (abilityIter!=playerAbilities.end())
    return true;

  playerAbilities[paToAdd.getAbilityName()]=paToAdd;
  return true;
}

//Assign one of the player's usable abilities to the given slot. Return true upon success.
//if slot == -1, assign to the first empty slot.
//Return false if:
//-There are no more empty slots.
//-Ability does not exist in the player's usable abilities.
bool Player::readyAbility (const string & abilityName, int slot)
{
  map <string, PlayerAbility> :: iterator abilityFound;

  abilityFound=playerAbilities.find(abilityName);

  //if this ability isn't in the usable abilities, bail
  if (abilityFound==playerAbilities.end())
    return false;

  Param options;
  options.setInt("Max Size", 10);
  options.setBool("Overwrite Slot", false);
  string dataToAdd=abilityName;
  return (readiedAbilities.add(abilityName, slot, options, dataToAdd));
}

//Generate a list of references of attack paths for the readied abilities. Returns the number of attack paths.
int Player::getAttackPathNames (vector <string> & attackPathNames)
{
  attackPathNames.clear();
  int slot;
  map <string, PlayerAbility> :: iterator playerAbility;
  bool validAbility;

  for (slot=0; slot<10; slot++)
    {
      validAbility=getPlayerAbility(playerAbility, slot);
      if (validAbility)
	{
	  attackPathNames.push_back(playerAbility->second.getAttackPath());
	}
    }
  return int(attackPathNames.size());
}
