#include "stdafx.h"
#include "HighTemplarUnitAttackBehavior.h"
#include "../../../../../Commands/FollowUnitCommand.h"
#include "../../../../../Commands/HighTemplarCommands/UsePsionicStormCommand.h"
#include <iostream>

// TODO: algorithm has to be refactored/improved

//#define PSIONIC_INFORMATION

// Sorting structure //////////////////////////////////////////////////////////////
struct SortByUnitDistance : public std::binary_function<BWAPI::Unit*, BWAPI::Unit*, bool>
{
  //Integration selon la frame rendu (estimation au frame futur)
  SortByUnitDistance(const UnitRecord* baseUnit)
    : m_baseUnit(baseUnit)
  {
  }

  bool operator() (const UnitRecord* u1, const UnitRecord* u2) const
  {
    const int distance1 = u1->getPosition().getApproxDistance(m_baseUnit->getPosition());
    const int distance2 = u2->getPosition().getApproxDistance(m_baseUnit->getPosition());
    return distance1 < distance2 || (distance1 == distance2 && u1 < u2);
  }

private:
  const UnitRecord* const m_baseUnit;
  SortByUnitDistance& operator=(const SortByUnitDistance&);
};

struct SortByHealth : public std::binary_function<BWAPI::Unit*, BWAPI::Unit*, bool>
{
  bool operator() (const UnitRecord* u1, const UnitRecord* u2) const
  {
    const int total1 = u1->getShields() + u1->getHitPoints();
    const int total2 = u2->getShields() + u2->getHitPoints();
    return total1 < total2 || (total1 == total2 && u1 < u2);
  }
};

// Behavior //////////////////////////////////////////////////////////////////////
HighTemplarUnitAttackBehavior::HighTemplarUnitAttackBehavior(const Process* parent, unsigned int initialPriority, BWAPI::Unit* unit, 
                                                             GetUnitToFollow getUnitToFollow)
  : UnitAttackBehaviorT(parent, initialPriority, unit)
  , UnitAttackBehavior(parent, initialPriority, unit)
  , UnitBehavior(parent, initialPriority, unit)
  , Behavior(parent, initialPriority)
  , FSM(parent)
  , Process(parent)
  , m_waitPsionicStorm(*this)
  , m_getUnitToFollow(getUnitToFollow)
  , m_states(m_waitPsionicStorm)
{
  assert(unit->getType() == BWAPI::UnitTypes::Protoss_High_Templar);
}

bool HighTemplarUnitAttackBehavior::selectTarget(const std::set<BWAPI::Unit*>& enemies)
{
  // Not used for high templars, as WaitPsionicStormOpportunity::onTimer() is called at a lower interval.

  unused(enemies);

  if (m_unit->getOrder() != BWAPI::Orders::CastPsionicStorm &&
      m_unit->getOrder() != BWAPI::Orders::ArchonWarp)
  {
    BWAPI::Unit* unitToFollow = m_getUnitToFollow();
    if (unitToFollow != NULL)
    {
      Layer1FSM::getInstance().getArbitrator().
        submitCommand(m_unit, FollowUnitCommand(*dynamic_cast<BehaviorState*>(getCurrentState()), unitToFollow), Arbitration::ActionImportance(*this));
    }
  }
  return m_unit->getOrder() == BWAPI::Orders::CastPsionicStorm;
}

// WaitPsionicStormOpportunity state //////////////////////////////////////////////////////////////
HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::WaitPsionicStormOpportunity(HighTemplarUnitAttackBehavior& behavior)
  : BehaviorStateT(behavior)
  , m_timerEvent(*this, Scheduler::FSM_SCHEDULING_PRIORITY, 10, 1)
  , m_events(m_timerEvent)
  , m_transitions()
{
}

void HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::onTimer()
{
  static const double MIN_VALUE = 100.0;
  static const double RADIUS_MULTIPLICATOR = 1.35;

  if(m_context.m_unit->getEnergy() < 75)
    return;

  const int radius = static_cast<int>(HIGH_TEMPLAR_PSIONIC_RANGE * RADIUS_MULTIPLICATOR);

  const BWAPI::Position pos = m_context.m_unit->getPosition();
  const int left = std::max(0, pos.x() - radius - PSIONIC_LEFT_MARKER_WIDTH);
  const int right = std::min(BWAPI::Broodwar->mapWidth() * TILE_SIZE - 1, pos.x() + radius + PSIONIC_RIGHT_MARKER_WIDTH);
  const int top = std::max(0, pos.y() - radius - PSIONIC_ABOVE_MARKER_HEIGHT);
  const int bottom = std::min(BWAPI::Broodwar->mapHeight() * TILE_SIZE - 1, pos.y() + radius + PSIONIC_BELOW_MARKER_HEIGHT);

  //Getting back all data
  std::set<const UnitRecord*> ownUnits;
  std::set<const UnitRecord*> enemyUnits;
  for (int x = left; x <= right; x += TILE_SIZE)
  {
    for (int y = top; y <= bottom; y += TILE_SIZE)
    {
      const Tile* tile = Spar->getPerceptualState()->getMap()->getTile(BWAPI::Position(x, y));
      {
        std::pair<Tile::unit_const_iterator, Tile::unit_const_iterator> units = tile->getOwnUnits();
        for (Tile::unit_const_iterator it = units.first; it != units.second; ++it)
        {
          const UnitRecord* unitRecord = *it;
          if (unitRecord->isAccessible() && !unitRecord->getUnit()->getType().isBuilding())
          {
            ownUnits.insert(unitRecord);
          }
        }
      }
      {
        std::pair<Tile::unit_const_iterator, Tile::unit_const_iterator> units = tile->getEnemyUnits();
        for (Tile::unit_const_iterator it = units.first; it != units.second; ++it)
        {
          const UnitRecord* unitRecord = *it;
          if (unitRecord->isAccessible() && !unitRecord->getUnit()->getType().isBuilding())
          {
            enemyUnits.insert(unitRecord);
          }
        }
      }
    }
  }

  if(!enemyUnits.empty())
  {
    std::pair<BWAPI::Position, double> storm = calculateHillClimbing(ownUnits, enemyUnits);
    if(storm.first != BWAPI::Positions::None && 
       storm.second >= MIN_VALUE &&
       m_context.m_unit->getOrder() != BWAPI::Orders::ArchonWarp)
    {
      Layer1FSM::getInstance().getArbitrator().
        submitCommand(m_context.m_unit, UsePsionicStormCommand(*this, storm.first), Arbitration::ActionImportance(m_context));
    }
  }
}

std::pair<BWAPI::Position, double> HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::calculateHillClimbing(const std::set<const UnitRecord*>& ownUnits, const std::set<const UnitRecord*>& enemyUnits) const
{
  //Integration du ratio, float de borne ]0, 1] determinant le pourcentage d'unite que nous prenons comme centre
  static const double RATIO_TO_CALCULATE = 0.5;

  UnitIterator unitsBegin = UnitIterator(boost::make_tuple(std::make_pair(ownUnits.begin(), ownUnits.end()), std::make_pair(enemyUnits.begin(), enemyUnits.end())));
  UnitIterator unitsEnd = UnitIterator(boost::make_tuple(std::make_pair(ownUnits.end(), ownUnits.end()), std::make_pair(enemyUnits.end(), enemyUnits.end())));

  std::vector<const UnitRecord*> rootEnemyUnits;
  if(RATIO_TO_CALCULATE == 1.0)
  {
    rootEnemyUnits.assign(enemyUnits.begin(), enemyUnits.end());
  }
  else
  {
    const int nodeToCalculate = std::max(1, (int)(enemyUnits.size() * RATIO_TO_CALCULATE));

    //Possibilite de pogner plusieurs fois la meme unite
    for(int jump = 0; jump < nodeToCalculate; jump++)
    {
      std::set<const UnitRecord*>::const_iterator addIter = enemyUnits.begin();
      std::advance(addIter, rand() % enemyUnits.size());
      rootEnemyUnits.push_back(*addIter);
    }
  }

  std::sort(rootEnemyUnits.begin(), rootEnemyUnits.end(), SortByHealth());
  // Erase duplicates if necessary
  rootEnemyUnits.erase(std::unique(rootEnemyUnits.begin(), rootEnemyUnits.end()), rootEnemyUnits.end());

  BWAPI::Position bestStormPosition = BWAPI::Positions::None;
  double bestValue = boost::numeric::bounds<double>::lowest();

#ifdef PSIONIC_INFORMATION
  std::ofstream stream;
  stream.open("valuesOfPsionic.txt", std::fstream::app);
  stream << "BEGIN\n\n";
#endif

  for(std::vector<const UnitRecord*>::iterator rootIt = rootEnemyUnits.begin(); rootIt != rootEnemyUnits.end(); rootIt++)
  {
    //Starting hill-climbing iteration
    const UnitRecord* rootEnemyUnit = *rootIt;

    std::vector<const UnitRecord*> otherEnemyUnits;
    for(std::set<const UnitRecord*>::const_iterator tmpIt = enemyUnits.begin(); tmpIt != enemyUnits.end(); tmpIt++)
      if(rootEnemyUnit != *tmpIt)
        otherEnemyUnits.push_back(*tmpIt);
    std::sort(otherEnemyUnits.begin(), otherEnemyUnits.end(), SortByUnitDistance(rootEnemyUnit));

    const BWAPI::Position rootUnitPosition = rootEnemyUnit->getPredictedPaths().empty()
      ? rootEnemyUnit->getPosition()
      : rootEnemyUnit->getPredictedPaths()[0].second.getPositionAtFrame(BWAPI::Broodwar->getFrameCount() + CASTING_FRAME_COUNT);

    {
      const double value = calculateValueAtPosition(rootUnitPosition, unitsBegin, unitsEnd);
      if(value > bestValue)
      {
#ifdef PSIONIC_INFORMATION
        std::ofstream stream;
        stream.open("valuesOfPsionic.txt", std::fstream::app);
        stream << "NEW SOLUTION\n";
#endif
        bestValue = value;
        bestStormPosition = rootUnitPosition;
      }
    }

    int leftEdge, rightEdge, topEdge, bottomEdge, sumX, sumY;
    sumX = leftEdge = rightEdge = rootUnitPosition.x();
    sumY = topEdge = bottomEdge = rootUnitPosition.y();
    int nbUnits = 1;
    for(std::vector<const UnitRecord*>::iterator unitIter = otherEnemyUnits.begin(); unitIter != otherEnemyUnits.end(); unitIter++)
    {
      const UnitRecord* newUnit = (*unitIter);

      const BWAPI::Position newUnitPos = newUnit->getPredictedPaths().empty()
        ? newUnit->getPosition()
        : newUnit->getPredictedPaths()[0].second.getPositionAtFrame(BWAPI::Broodwar->getFrameCount() + CASTING_FRAME_COUNT);

      leftEdge = std::min(leftEdge, newUnitPos.x());
      rightEdge = std::max(rightEdge, newUnitPos.x());
      topEdge = std::min(topEdge, newUnitPos.y());
      bottomEdge = std::max(bottomEdge, newUnitPos.y());

      if(rightEdge - leftEdge > PSIONIC_LEFT_MARKER_WIDTH + PSIONIC_RIGHT_MARKER_WIDTH || bottomEdge - topEdge > PSIONIC_BELOW_MARKER_HEIGHT + PSIONIC_ABOVE_MARKER_HEIGHT)
        break;

      sumX += newUnitPos.x();
      sumY += newUnitPos.y();
      ++nbUnits;
      const BWAPI::Position stormPosition = BWAPI::Position(sumX / nbUnits, sumY / nbUnits);     
      const double value = calculateValueAtPosition(stormPosition, unitsBegin, unitsEnd);
      if(value > bestValue)
      {
#ifdef PSIONIC_INFORMATION
        std::ofstream stream;
        stream.open("valuesOfPsionic.txt", std::fstream::app);
        stream << "NEW SOLUTION\n";
#endif
        bestValue = value;
        bestStormPosition = stormPosition;
      }
    }
  }
  return std::make_pair(bestStormPosition, bestValue);
}

inline int getPlayerMultiplicator(BWAPI::Player* player)
{
  if (BWAPI::Broodwar->enemy() == player)
    return 1;
  else if (BWAPI::Broodwar->self() == player)
    return -1;
  else
    return 0;
}

double HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::calculateValueAtPosition(
  BWAPI::Position stormPosition, 
  HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::UnitIterator unitsBegin, 
  HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::UnitIterator unitsEnd) const
{
  const int topEdge = std::max(0, stormPosition.y() - PSIONIC_ABOVE_MARKER_HEIGHT);
  const int bottomEdge = std::min(BWAPI::Broodwar->mapHeight() * TILE_SIZE - 1, stormPosition.y() + PSIONIC_BELOW_MARKER_HEIGHT);
  const int leftEdge = std::max(0, stormPosition.x() - PSIONIC_LEFT_MARKER_WIDTH);
  const int rightEdge = std::min(BWAPI::Broodwar->mapWidth() * TILE_SIZE - 1, stormPosition.x() + PSIONIC_RIGHT_MARKER_WIDTH);
  const is_between<int, true> horizontal(leftEdge, rightEdge - 1);
  const is_between<int, true> vertical(bottomEdge, topEdge - 1);

  //Facteur pour la mesure final, voir aussi les deux fonctions de calculs pour determiner les champs
  //sur lequel on veut travailler afin de connaitre la valeur d'une unite morte et la valeur d'attaque d'une unite
  static const double DAMAGE_FACTOR = 1.0;
  static const double COST_FACTOR = 0.3;
  static const double DISTANCE_FACTOR = 0.25;
  //static const double DANGEROSITY_FACTOR = 0.1;
  static const double ALLY_FACTOR = -0.5;

  const int distance = m_context.m_unit->getPosition().getApproxDistance(stormPosition);
  double value = (distance <= HIGH_TEMPLAR_PSIONIC_RANGE) ? 0 : (HIGH_TEMPLAR_PSIONIC_RANGE - distance) * DISTANCE_FACTOR;
  for(UnitIterator it = unitsBegin; it != unitsEnd; it++)
  {
    const UnitRecord* unitRecord = (*it);
    const int playerMultiplicator = getPlayerMultiplicator(unitRecord->getPlayer());
    const BWAPI::UnitType unitType = unitRecord->getType();
    const BWAPI::Position unitPosition = unitRecord->getPredictedPaths().empty()
      ? unitRecord->getPosition()
      : unitRecord->getPredictedPaths()[0].second.getPositionAtFrame(BWAPI::Broodwar->getFrameCount() + CASTING_FRAME_COUNT);
    if (horizontal(unitPosition.x()) && vertical(unitPosition.y()))
    {
      const int unitLife = unitRecord->getShields() + unitRecord->getHitPoints();
      const int unitCost = unitRecord->getType().mineralPrice() + unitRecord->getType().gasPrice();

      //Calculer la meilleur sortie possible (la ligne droite la plus rapide pour la sortie)
      //Se sera considerer comme les pire degat que nous puissions faire, car elle sortira rapidement de notre attaque
      const int distanceFromExit = std::min(
        std::min(unitPosition.y() - topEdge, bottomEdge - unitPosition.y()),
        std::min(unitPosition.x() - leftEdge, rightEdge - unitPosition.x()));
      const int nbFrameInPsionic = static_cast<int>(std::ceil(distanceFromExit / unitType.topSpeed()));
      const int bestCaseDamage = std::min(unitLife, PSIONIC_TOTAL_DMG);
      const int worstCaseDamage = std::min(unitLife, nbFrameInPsionic * PSIONIC_DMG_PER_FRAME);

      //On considere les pire degat que nous puissions faire a l'unite.
      const double valueDamage = ((bestCaseDamage + worstCaseDamage) * DAMAGE_FACTOR / 2) * playerMultiplicator;
      //Si on tue l'unite on gagne quoi
      const double valueKill = (unitLife <= PSIONIC_TOTAL_DMG ? (unitCost * COST_FACTOR) : 0) * playerMultiplicator;
      //Calculer selon les caracteristique de l'unite, si elle est dengeureuse pour nous
      //double valueDangerosity = (calculateUnitDangerosity(unitType) * DANGEROSITY_FACTOR);
      //Calcul de l'influence alliee, nos alliee aussi peuvent tuer
      const double valueAlly = (calculateAllyInfluence(stormPosition) * ALLY_FACTOR);

  #ifdef PSIONIC_INFORMATION
      std::ofstream stream;
      stream.open("valuesOfPsionic.txt", std::fstream::app);
      stream << "Damage => " << valueDamage << "\n";
      stream << "Kill => " << valueKill << "\n";
      //stream << "Dangerosity => " << valueDangerosity << "\n";
      stream << "Ally => " << valueAlly << "\n\n";
  #endif

      double valueUnit = valueDamage +
                         valueKill +
                         //valueDangerosity +
                         valueAlly;
      if (unitRecord->getPlayer() == BWAPI::Broodwar->enemy() && 
          (unitRecord->getType() == BWAPI::UnitTypes::Terran_Siege_Tank_Tank_Mode || unitRecord->getType() == BWAPI::UnitTypes::Terran_Siege_Tank_Siege_Mode))
      {
        value += 50; // bonus for annoying siege tanks
      }
      value += valueUnit;
    }
  }

#ifdef PSIONIC_INFORMATION
  std::ofstream stream;
  stream.open("valuesOfPsionic.txt", std::fstream::app);
  stream << "Value => " << value << "\n\n\n";
#endif

  return value;
}

double HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::calculateUnitDangerosity(BWAPI::UnitType unitType) const
{
  //Possibilité de modifier les valeurs selon le type d'unité que nous considérons important d'attaque en premier
  //Par exemple focuser nos attaque sur les High Templar ennemie

  double value = 0.0;

  value += unitType.isFlyer() ? 10.0 : 0.0;
  //value += unitType.isDetector() ? 8.0 : 0.0;

  return value;
}

double HighTemplarUnitAttackBehavior::WaitPsionicStormOpportunity::calculateAllyInfluence(BWAPI::Position center) const
{
  const int top = std::max(0, center.y() - PSIONIC_ABOVE_MARKER_HEIGHT);
  const int bottom = std::min(BWAPI::Broodwar->mapHeight() * TILE_SIZE - 1, center.y() + PSIONIC_BELOW_MARKER_HEIGHT);
  const int left = std::max(0, center.x() - PSIONIC_LEFT_MARKER_WIDTH);
  const int right = std::min(BWAPI::Broodwar->mapWidth() * TILE_SIZE - 1, center.x() + PSIONIC_RIGHT_MARKER_WIDTH);

  int maxIter = ((right - left) / TILE_SIZE) * ((bottom - top) / TILE_SIZE);
  double currentThreat = 0.0f;

  const InfluenceMapRef<Threat>::map_ptr th = Spar->getPerceptualState()->getSelfThreat();
  for(int x = left; x <= right; x += TILE_SIZE)
  {
    for(int y = top; y <= bottom; y += TILE_SIZE)
    {
      const Threat& selfThreat = th->getData(x / TILE_SIZE, y / TILE_SIZE);
      currentThreat += (selfThreat.ground + selfThreat.air) / 2;
    }
  }

  return currentThreat / maxIter;
}