#include "stdafx.h"
#include "../Locations/Static/StaticLocation.h"
#include "../PerceptualState.h"
#include "UnitRecordType/TransportUnitRecordType.h"
#ifdef _DEBUG_COMPONENT
  #include "StaticUnitRecord.h"
#endif
#include "UnitRecord.h"
#include "StaticUnitRecord.h"
#include "UnitRecordManager.h"
#include "../../Utils/Logger.h"
#include <ctime>
#include <cstdlib>
#include <cmath>

size_t UnitRecord::s_id = 0;

UnitRecord::UnitRecord(BWAPI::Unit* unit)
  : m_unit(unit)
  , m_id(s_id++)
  , m_individualUnitRecordDestroyEvent(*this)
  , m_individualUnitRecordMorphEvent(*this)
  , m_individualUnitRecordRenegadeEvent(*this)
  , m_player(unit->getPlayer())
  , m_currentUnitRecordType(*this, NULL)
  , m_savedState(*this, NULL)
  , m_isDestroyed(false)
  , m_lastUnitEvadedFrame(-1)
  , m_firstSavedPositionInvalidatedFrame(-1)
  , m_transport(NULL)
  , m_lastPositions(3)
  , m_tilesLocationParametersCache(UnitRecord::TilesLocationParameters())
{
}

void UnitRecord::_output(std::ostream& out) const
{
  BWAPI::Player* player = getPlayer();
  if (player == BWAPI::Broodwar->self())
    out << "Own";
  else if (player == BWAPI::Broodwar->enemy())
    out << "Enemy";
  else
    out << "Neutral";

  if (isAccessible())
  {
    out << " unit (0x" << std::nouppercase << std::hex << m_unit << std::dec << ") " << *m_unit << ", hit points " << m_unit->getHitPoints();
  }
  else
  {
    out << " saved unit (0x" << std::nouppercase << std::hex << m_unit << std::dec << ") " << m_savedState->getType() << " " << m_unit->getID() << ", hit points " << m_savedState->getHitPoints();
  }

  const BWAPI::Position position = getPosition();
  if (position != BWAPI::Positions::None)
    out << " (" << position.x() << ", " << position.y() << ")";

  m_currentUnitRecordType->_outputUnitInfo(out);
}

BWAPI::UnitType UnitRecord::getType() const
{ 
  assert(!isAccessible() || m_unit->getType() == m_currentUnitRecordType->getType());
  return m_currentUnitRecordType->getType();
}

Region* UnitRecord::getRegion() const
{
  return Spar->getPerceptualState()->getMap()->getRegion(getPosition());
}

void UnitRecord::morph(BWAPI::UnitType newType)
{
  assert(m_currentUnitRecordType->getType() != newType);

#ifdef _DEBUG_COMPONENT
  if (dynamic_cast<StaticUnitRecord*>(this) != NULL)
  {
    SPAR_LOG(LogTypes.GENERAL_ERROR, "ERROR UnitRecord::morph(): Unit record \"%s\" should not morph!", toString(this).c_str());
    throw std::runtime_error("Invalid morph");
  }
#else
  assert(dynamic_cast<StaticUnitRecord*>(this) == NULL); // If this fails it means I forgot a possible morph! so the factory instance must be changed!
#endif

  BWAPI::UnitType oldType = m_currentUnitRecordType->getType();

  SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit record \"%s\" has morphed from %s to %s",
    toString(this).c_str(), oldType.getName().c_str(), newType.getName().c_str());

  m_currentUnitRecordType = UnitRecordManager::instantiate(this, newType);

  m_individualUnitRecordMorphEvent.raise(oldType);
}

void UnitRecord::morph(const std::set<BWAPI::UnitType>& possibleNewTypes)
{
  assert(!possibleNewTypes.empty());
  assert(!isAccessible());

  // So... this indicates the unit actually morphed into something else. If there's only one option,
  // we can deduce the unit has morphed into this particular type.
  if (possibleNewTypes.size() == 1)
    morph(*possibleNewTypes.begin());
}

void UnitRecord::renegade(BWAPI::Player* newPlayer)
{
  assert(isAccessible()); // So nothing to modify, the unit pointer holds the info
  assert(m_player != newPlayer);

  BWAPI::Player* oldPlayer = m_player;

  SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit record \"%s\" has renegaded from %s to %s",
    toString(this).c_str(), oldPlayer->getName().c_str(), newPlayer->getName().c_str());

  m_player = newPlayer;

  m_individualUnitRecordRenegadeEvent.raise(oldPlayer);
}

void UnitRecord::destroy(const char* reason)
{
  AutoPtr<UnitRecord> scope(*this, this); // Suicide if there's no reference left after the function

  assert(!m_isDestroyed);
  m_isDestroyed = true;

  if (getPlayer() == BWAPI::Broodwar->enemy())
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Enemy unit record: Remove \"%s\" due to \"%s\"", 
      toString(this).c_str(), reason);
  }

  m_currentUnitRecordType->onDestroyed();

  m_individualUnitRecordDestroyEvent.raise();

  if (m_transport != NULL)
  {
    dynamic_cast<TransportUnitRecordType*>(m_transport->getCurrentUnitRecordType())->removeUnit(this);
    m_transport = NULL;
  }
}

void UnitRecord::onSavedPositionScouted()
{
  m_firstSavedPositionInvalidatedFrame = BWAPI::Broodwar->getFrameCount();
}

void UnitRecord::setTransport(UnitRecord* transport)
{
  if (transport != NULL)
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit record \"%s\" is hypothesized to be loaded into transport \"%s\"",
      toString(this).c_str(), toString(transport).c_str());
  }
  else
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit record \"%s\" is hypothesized to be unloaded from any transport",
      toString(this).c_str());
  }

  m_transport = transport;
  dynamic_cast<TransportUnitRecordType*>(m_transport->getCurrentUnitRecordType())->addUnit(this);
}

void UnitRecord::onTransportDestroyed(UnitRecord* transport)
{
  if (m_transport == transport)
  {
    if (BWAPI::troopsDestroyedInTransport(m_transport->getType()))
    {
      destroy("Unit destroyed in transport");
    }
    else
    {
      m_transport = NULL;
    }
  }
}

void UnitRecord::onUnitDiscover()
{
  assert(m_unit->getType() != BWAPI::UnitTypes::Unknown);
  assert(!m_isDestroyed);

  if (!isAccessible()) // If the unit is transitioning from inaccessibility
  {
    m_lastUnitEvadedFrame = -1;

    if (m_currentUnitRecordType->getType() != m_unit->getType())
    {
      // Unit has morphed while in the fog of war

      morph(m_unit->getType()); 
    }
    if (m_player != m_unit->getPlayer())
    {
       // Unit has renegaded while in the fog of war

      renegade(m_unit->getPlayer());
    }
  }
  m_firstSavedPositionInvalidatedFrame = -1;

  if (m_transport != NULL)
  {
    dynamic_cast<TransportUnitRecordType*>(m_transport->getCurrentUnitRecordType())->removeUnit(this);
    m_transport = NULL;
  }
  
  m_currentUnitRecordType->onUnitDiscover();
}

UnitRecord::SavedState* UnitRecord::saveState()
{
  return m_currentUnitRecordType->saveState();
}

void UnitRecord::onUnitEvade()
{
  m_savedState = saveState();

  m_lastUnitEvadedFrame = BWAPI::Broodwar->getFrameCount();

  m_currentUnitRecordType->onUnitEvade();
}

BWAPI::Position UnitRecord::PredictedPath::getPositionAtFrame(int frameNumber) const
{
  // TODO: optimize: use a map or binary search

  const int currentFrame = BWAPI::Broodwar->getFrameCount();
  assert(frameNumber >= currentFrame);

  if (frameNumber <= currentFrame)
    return m_unitRecord->getPosition();

  std::pair<int, BWAPI::Position> lastMilestone = std::make_pair(currentFrame, m_unitRecord->getPosition());

  for (std::vector<std::pair<int, BWAPI::Position>>::const_iterator it = m_positions.begin(); it != m_positions.end(); ++it)
  {
    if (it->first >= frameNumber)
    {
      const double factor = double(frameNumber - lastMilestone.first) / double(it->first - lastMilestone.first);
      const BWAPI::Position difference = it->second - lastMilestone.second;
      BWAPI::Position translation = BWAPI::Position(static_cast<int>(factor * difference.x()), static_cast<int>(factor * difference.y()));
      return lastMilestone.second + translation;
    }
    else if (it->first > lastMilestone.first)
    {
      lastMilestone.first = it->first;
      lastMilestone.second = it->second;
    }
  }
  return lastMilestone.second;
}
