#include "stdafx.h"
#include "GatherMineralsBehavior.h"
#include "../../../../../PerceptualState/Locations/Static/Autopopulated/BaseLocation.h"

const Process::TerminationMode GatherMineralsBehavior::NoMoreMinerals;

///////////////////////////////////////////////////
// Mining state
///////////////////////////////////////////////////

GatherMineralsBehavior::Mining::Mining(GatherMineralsBehavior& behavior)
  : BehaviorStateT(behavior)
  , m_crystalRemovedEventHandler(*this)
  , m_transitions()
  , m_event(*this, Scheduler::FSM_SCHEDULING_PRIORITY, 19, 5)
  , m_events(m_event)
  , m_defense("Defense")
{
  m_target=NULL;
  m_isAttacking=false;
}

void GatherMineralsBehavior::Mining::onEnter()
{
  std::pair<BaseLocation::crystal_iterator, BaseLocation::crystal_iterator> crystals = m_context.m_baseLocation->getLastKnownCrystalsLeft();

  m_crystalRemovedSubscriberId = m_context.m_baseLocation->getNeutralUnitRemovedEvent().subscribe(&m_crystalRemovedEventHandler, NULL);
  if(crystals.first != crystals.second)
  {
    BaseLocation::crystal_iterator mineralIt = crystals.first;
    // *mineralIt is type "const OwnUnitRecord*"
    // (*mineralIt)->isAccessible() is false if the mineral isn't in the field of view

    m_context.m_lastMineral = (*mineralIt)->getUnit();

    for(PersistentUnitGroup::const_iterator it = m_context.getGroup()->begin(); it != m_context.getGroup()->end(); ++it)
    {
      assert((*it)->getType() == BWAPI::UnitTypes::Protoss_Probe);
      assert((*it)->canIssueCommand(BWAPI::UnitCommand::gather((*it), (*mineralIt)->getUnit())));
      (*it)->gather((*mineralIt)->getUnit());
      m_context.m_lastMineral = (*mineralIt)->getUnit();
      ++mineralIt;
      if(mineralIt == crystals.second)
        mineralIt = crystals.first;
    }
  } else {
    m_context.terminate(NoMoreMinerals);
  }
}

void GatherMineralsBehavior::Mining::onNeutralBuildingRemoved(void* data, UnitRecord* crystal, BWAPI::UnitType, BWAPI::Player*)
{
  unused(data);
  if (crystal->getType().isMineralField())
  {
    std::pair<BaseLocation::crystal_iterator, BaseLocation::crystal_iterator> crystals = m_context.m_baseLocation->getLastKnownCrystalsLeft();
    if(crystals.first == crystals.second)
    {
      m_context.terminate(NoMoreMinerals);
    }
  }
}

void GatherMineralsBehavior::Mining::onUnitAddedToGroup(BWAPI::Unit* unit)
{
  std::pair<BaseLocation::crystal_iterator, BaseLocation::crystal_iterator> crystals = m_context.m_baseLocation->getLastKnownCrystalsLeft();

  assert(crystals.first != crystals.second);
  
  BaseLocation::crystal_iterator mineralIt = crystals.first;

  while(mineralIt != crystals.second && (*mineralIt)->getUnit() != m_context.m_lastMineral)
    ++mineralIt;

  if(mineralIt != crystals.second)
    ++mineralIt;
  if(mineralIt == crystals.second)
    mineralIt = crystals.first;
  m_context.m_lastMineral = (*mineralIt)->getUnit();

#ifdef _DEBUG
  if (!unit->canIssueCommand(BWAPI::UnitCommand::gather(unit, (*mineralIt)->getUnit())))
  {
    SPAR_LOG(LogTypes.GENERAL_WARNING, "Assertion failed GatherMineralsBehavior::Mining::onUnitAddedToGroup(): \"unit->canIssueCommand(BWAPI::UnitCommand::gather(unit, (*mineralIt)->getUnit()))\"");
  }
#endif
  //assert(unit->canIssueCommand(BWAPI::UnitCommand::gather(unit, (*mineralIt)->getUnit())));

  unit->gather((*mineralIt)->getUnit());
}

void GatherMineralsBehavior::Mining::onExit()
{
  m_context.m_baseLocation->getNeutralUnitRemovedEvent().unsubscribe(m_crystalRemovedSubscriberId);
}

void GatherMineralsBehavior::Mining::onTimer()
{
  PersistentUnitGroup::const_iterator itUnit = m_context.getGroup()->begin();
  while(m_defense.size() < 3 && itUnit!=m_context.getGroup()->end())
  {
    m_defense.insert(*itUnit);
    ++itUnit;
  }
  bool doSomething = false;
  if(m_isAttacking){doSomething=true;}
  std::pair<BaseLocation::crystal_iterator, BaseLocation::crystal_iterator> crystals = m_context.m_baseLocation->getLastKnownCrystalsLeft();
  BaseLocation::crystal_iterator patch = crystals.first;

  if(crystals.first != crystals.second)
  {
    for(PersistentUnitGroup::const_iterator i = m_context.getGroup()->begin() ; i!=m_context.getGroup()->end() ; ++i)
    {
      if((*i)->isIdle())
      {
        if(patch != crystals.second)
        {
          (*i)->gather((*patch)->getUnit());
          patch++;
        }
        else
        {
          patch = crystals.first;
          (*i)->gather((*patch)->getUnit());
          patch++;
        }
      }
      if(!m_isAttacking && (*i)->isUnderAttack())
      {
        doSomething=true;
      }
      if(m_isAttacking && (*i)->isUnderAttack())
      {
        doSomething=false;
      }
    }
  }

  if(doSomething && m_isAttacking)
  {
    //stop attacking
    crystals = m_context.m_baseLocation->getLastKnownCrystalsLeft();
    patch = crystals.first;
    for(PersistentUnitGroup::const_iterator i = m_defense.begin() ; i!=m_defense.end() ; ++i)
    {
      if(patch != crystals.second)
      {
        (*i)->gather((*patch)->getUnit());
        patch++;
      }
      else
      {
        patch = crystals.first;
        (*i)->gather((*patch)->getUnit());
        patch++;
      }
    }
    m_isAttacking = false;
    m_target = NULL;
  }
  else if(doSomething && !m_isAttacking)
  {
    //start attacking
    m_isAttacking = true;
    //find a target
    std::set<BWAPI::Unit*> units = Spar->getPerceptualState()->getEnemyUnits(CircularLocation((*m_context.getGroup()->begin())->getPosition(), 7*TILE_SIZE));
    std::set<BWAPI::Unit*>::iterator itEnemy = units.begin();
    m_target = NULL;
    while(m_target == NULL && itEnemy!=units.end())
    {
      if((*itEnemy)->isDetected() == true)
      {
        m_target = (*itEnemy);
      }
      itEnemy++;
    }
    //everybody attack move it
    if(m_target!=NULL)
    {
      m_defense.attackMove(m_target->getPosition());
    }
    else
    {
      m_isAttacking = false;
    }
  }
  else if(!doSomething && m_isAttacking)
  {
    //if target is dead
    
    if(!m_target->exists() || !m_target->isDetected())
    {
      std::set<BWAPI::Unit*> units = Spar->getPerceptualState()->getEnemyUnits(CircularLocation((*m_context.getGroup()->begin())->getPosition(), 7*TILE_SIZE));
      std::set<BWAPI::Unit*>::iterator it = units.begin();
      m_target = NULL;
      while(m_target == NULL && it!=units.end())
      {
        if((*it)->isDetected() == true)
        {
          m_target = (*it);
        }
        it++;
      }
    }
    //change target
    //attack move target
    if(m_target != NULL)
    {
      m_defense.attackMove(m_target->getPosition());
    }
    else
    {
      m_isAttacking = false;
    }
  }
}

void GatherMineralsBehavior::_output(std::ostream& out) const
{
  out << "GatherMineralsBehavior(" << *m_baseLocation << ")";
}
