#include "stdafx.h"
#include "Vulnerability.h"
#include "InfluenceMap.h"
#include "../UnitRecord/UnitRecord.h"
#include "../../SparAIContainer.h"
#include "../../Utils/BresenhamCircle.h"
#include "../../PerceptualState/PerceptualState.h"

void Vulnerability::display(int x, int y, int w, int h, BWAPI::Player* player) const
{
  unused(player);
  if (!isNull())
  {
    BWAPI::Color color;
    if((_airToAir + _airToGround) >= (_groundToAir + _groundToGround))
    {
      int val = std::min(255, static_cast<int>((_airToAir + _airToGround) * 10) + 50);
      color = BWAPI::Color(255, val, val);
    }
    else
    {
      int val = std::min(255, static_cast<int>((_groundToAir + _groundToGround) * 10) + 50);
      color = BWAPI::Color(val, val, 255);
    }

    BWAPI::Broodwar->drawBoxMap( x, y, x+w, y+h, color, false);

  }
}

/**
 * @brief Functor for Bresenham's circle drawing algorithm, used to update
 * the influence map at certain positions. Won't work with units that are not available
 */

struct VulnFunctor : std::binary_function<int,int,void>
{
  /**
   * @brief Builds a functor object.
   * @param map A pointer to the influence map to update.
   * @param unit The unit for which to compute the vulnerability.
   */
  VulnFunctor (InfluenceMap<Vulnerability>& map, const BWAPI::Unit * unit)
    : _map(map)
    , _unit(unit)
  {
    // FIXME
    assert(map.getWidth()  == static_cast<size_t>(BWAPI::Broodwar->mapWidth())
        && map.getHeight() == static_cast<size_t>(BWAPI::Broodwar->mapHeight()));
  }

  /**
   * @brief Updates the influence map at the given position.
   * @param x the x coordinate of the tile (relative to the center of the
   * range circle) whose influence will be updated.
   * @param y the y coordinate of the tile (relative to the center of the
   * range circle) whose influence will be updated.
   */
  result_type operator()(first_argument_type x, second_argument_type y)
  {
    const BWAPI::TilePosition current = _unit->getTilePosition();
    x += current.x();
    y += current.y();

    if (_map.isValid(x, y))
    {
      const int distance = static_cast<int>(current.getDistance(BWAPI::TilePosition(x, y)));
      const double mod = 10.0 / (distance + 1);
      Vulnerability influence = _map.getData(x, y);
      influence._airToAir += Vulnerability::getAirToAir(_unit) * mod;
      influence._airToGround += Vulnerability::getAirToGround(_unit) * mod;
      influence._groundToAir += Vulnerability::getGroundToAir(_unit) * mod;
      influence._groundToGround += Vulnerability::getGroundToGround(_unit) * mod;
      _map.setInfluence(x, y, influence);
    }
  }
private:
  InfluenceMap<Vulnerability>& _map;
  const BWAPI::Unit * _unit;

  //Prevents using the assignment operator
  VulnFunctor& operator=(const VulnFunctor&);
};

void Vulnerability::update(InfluenceMap<Vulnerability>& map)
{
  map.reset();

  if (map.getPlayer() == BWAPI::Broodwar->enemy())
  {
    std::pair<Map::unit_const_iterator, Map::unit_const_iterator> enemyUnits = Spar->getPerceptualState()->getMap()->getEnemyUnits();
    update(map, enemyUnits.first, enemyUnits.second);
  }
}

void doUpdate(InfluenceMap<Vulnerability> &map, BWAPI::Unit * unit)
{
  VulnFunctor func(map, unit);
  BresenhamCircle(func, 4, true);
}

void Vulnerability::update(InfluenceMap<Vulnerability>& map, BWAPI::Unit* unit)
{
  doUpdate(map, unit);
}

void Vulnerability::update(InfluenceMap<Vulnerability>& map, const UnitRecord * unit)
{
  if (unit->isAccessible())
    update(map, unit->getUnit());
}

Vulnerability::value_type Vulnerability::getGeneralTemporary(const BWAPI::Unit * unit)
{
  value_type vuln = 0;
  //Sort pouvant ajouter à la vulnérabilité
  //Queen's ensnare
  //Science Vessel's irradiate
  if(unit->isEnsnared() || unit->isIrradiated() || unit->isMaelstrommed() || unit->isPlagued())
    vuln += 1.0;

  return vuln;
}

Vulnerability::value_type Vulnerability::getHitPoint(const BWAPI::Unit * unit)
{
  const BWAPI::UnitType& type = unit->getType();
  value_type vuln = 0;
   
  //plus l'unité est blessé, plus c'est intéressant (tant qu'il n'y a pas de defenseMatrix)
  if(!unit->isDefenseMatrixed() || !unit->isUnderDarkSwarm() || !unit->isStasised())
    vuln += 1.0 - (unit->getHitPoints() / type.maxHitPoints());
  return vuln;
}

Vulnerability::value_type Vulnerability::getAirToAir(const BWAPI::Unit * unit)
{
  const BWAPI::UnitType& type = unit->getType();
  value_type vuln = 0;
  if(type.isFlyer())
  {
    //S'il ne vise pas dans les airs, il est vulnérable au attaque aérienne
    if(!type.airWeapon().targetsAir())
      vuln += 1;

    vuln += getHitPoint(unit);   

    vuln += getGeneralTemporary(unit);
  }
  return vuln;
}

Vulnerability::value_type Vulnerability::getAirToGround(const BWAPI::Unit * unit)
{
  const BWAPI::UnitType& type = unit->getType();
  value_type vuln = 0;
  if(type.isFlyer())
  {
    //S'il ne vise pas dans les airs, c'est un avantage pour nous
    if(!type.airWeapon().targetsGround())
      vuln += 1.0;

    vuln += getHitPoint(unit);   

    vuln += getGeneralTemporary(unit);
  }
  return vuln;
}

Vulnerability::value_type Vulnerability::getGroundToAir(const BWAPI::Unit * unit)
{
  const BWAPI::UnitType& type = unit->getType();
  value_type vuln = 0;
  if(!type.isFlyer()) 
  {
    //S'il ne vise pas dans les airs, c'est un avantage pour nous
    if(!type.groundWeapon().targetsAir())
      vuln += 1.0;

    vuln += getHitPoint(unit);   

    vuln += getGeneralTemporary(unit);
  } 
  return vuln;
}

Vulnerability::value_type Vulnerability::getGroundToGround(const BWAPI::Unit * unit)
{
  const BWAPI::UnitType& type = unit->getType();
  value_type vuln = 0;
  if(!type.isFlyer())  
  {
    //S'il ne vise pas au sol, c'est un avantage pour nous
    if(!type.groundWeapon().targetsGround())
      vuln += 1.0;

    vuln += getHitPoint(unit);   

    vuln += getGeneralTemporary(unit);
  }
  return vuln;
}
