#include "stdafx.h"
#include "SplitGroupCarefulAttackBehavior.h"
#include "GroupCarefulAttackBehavior.h"
#include "../MeleeEngageBehavior.h"
#include "../../../../../../Utils/Vector2.h"
#include <BWAPI.h>
#include <algorithm>
#include <vector>

static const int MELEE_RANGE = 25; //Zealot's range has been verified to be 15.

/**
* @brief Divides a std::set<BWAPI::Unit*> into a std::vector of std::vector of units. All units of a type are in a
* same std::vector.
* @param group The unit group to split.
* @return The result of the division in groups.
*/
std::vector<std::vector<BWAPI::Unit *>> splitInTypes(const std::set<BWAPI::Unit*> &group) {
  std::vector<std::vector<BWAPI::Unit *>> groupedUnits;
	for (std::set<BWAPI::Unit*>::const_iterator i = group.begin(); i != group.end(); i++) {
		bool found = false;
		for (std::vector<std::vector<BWAPI::Unit *>>::iterator j = groupedUnits.begin(); j != groupedUnits.end(); ++j) {
			if ((*i)->getType() == (*j->begin())->getType()) {
				found = true;
				j->push_back(*i);
				break;
			}
		}
		
		if (!found) {
			groupedUnits.push_back(std::vector<BWAPI::Unit *>());
			groupedUnits[groupedUnits.size()-1].push_back(*i);
		}
	}
  return groupedUnits;
}

struct Functor_SortByDistanceToPos {
	BWAPI::Position m_pos;
	Functor_SortByDistanceToPos(BWAPI::Position pos): m_pos(pos) {}
	static int squaredDistance(BWAPI::Position pos1, BWAPI::Position pos2) {
		return (int)(pow((float)pos1.x()-pos2.x(), 2) + pow((float)pos1.y()-pos2.y(), 2));
	}

	bool operator() (BWAPI::Unit *u1, BWAPI::Unit *u2) {
		return squaredDistance(m_pos, u1->getPosition()) < squaredDistance(m_pos, u2->getPosition());
	}
};

/**
@brief Divide a group of unit to face enemies. Makes groups of same type units
		that are in a perpendicular line to the position from the middle of the group.
@param myUnits The unit group to split.
@param enemyPos The estimated enemy position.
@return The result of the division in groups.
*/
std::vector<std::set<BWAPI::Unit*>> divideInGroups(const std::set<BWAPI::Unit*>& myUnits, BWAPI::Position enemyPos) {
  std::vector<std::set<BWAPI::Unit*>> result;
	std::vector<std::vector<BWAPI::Unit *>> myUnitsByType = splitInTypes(myUnits);
	
  BWAPI::Position myPos = GroupHelper::getCenter(myUnits);
	
	Vector2i myPosToEnemies = Vector2i(enemyPos.x() - myPos.x(), enemyPos.y() - myPos.y());
		
	for (std::vector<std::vector<BWAPI::Unit *>>::iterator i = myUnitsByType.begin(); i != myUnitsByType.end(); ++i) {
		std::vector<BWAPI::Unit *> &units = *i;
		BWAPI::UnitType unitType = units[0]->getType();
		size_t nbPerGroup;
		if (unitType.size() == BWAPI::UnitSizeTypes::Large) {
			nbPerGroup = 3;
		} else if (unitType.size() == BWAPI::UnitSizeTypes::Medium) {
			nbPerGroup = 5;
		} else {
			nbPerGroup = 10;
		}

		if (units.size() > nbPerGroup) {
			sort(units.begin(), units.end(), Functor_SortByDistanceToPos(enemyPos));
			size_t curBeginIndex = 0;
			size_t curEndIndex = nbPerGroup;
			for(;;) {
				if (curBeginIndex >= units.size()) {
					break;
				}
				if (curEndIndex > units.size()) {
					curEndIndex = units.size();
				}
				result.push_back(std::set<BWAPI::Unit*>());
				result[result.size()-1].insert(units.begin()+curBeginIndex, units.begin()+curEndIndex);
				curBeginIndex += nbPerGroup;
				curEndIndex += nbPerGroup;
			}			
		} else {
			result.push_back(std::set<BWAPI::Unit*>());
			result[result.size()-1].insert(units.begin(), units.end());
		}
	}
  return result;
}

/**
@brief Divide a group of unit to face enemies. Makes groups of same type units
		that are in a perpendicular line to the enemies.
@param myUnits The unit group to split.
@param enemyUnits The enemy units against which the groups should be fighting.
@return The result of the division in groups.
*/
std::vector<std::set<BWAPI::Unit*>> divideInGroups(const std::set<BWAPI::Unit*> &myUnits, const std::set<BWAPI::Unit*> &enemyUnits) {
	//int sumx = 0;
	//int sumy = 0;

	// Use the middle position of the enemy group. Thats all we do with them for now.
  return divideInGroups(myUnits, GroupHelper::getCenter(enemyUnits));
}


///////////////////////////////////////////////////
// Engaging state
///////////////////////////////////////////////////

SplitGroupCarefulAttackBehavior::Engaging::Engaging(SplitGroupCarefulAttackBehavior& behavior, const std::set<BWAPI::Unit*>& enemyUnits)
  : BehaviorStateT(behavior)
  , m_enemyCenter(GroupHelper::getCenter(enemyUnits))
  , m_groupBehavior(*this)
{
}

void SplitGroupCarefulAttackBehavior::Engaging::onEnter()
{
  std::set<BWAPI::Unit*> unitGroup = std::set<BWAPI::Unit*>();
  unitGroup.insert(m_context.getGroup()->begin(), m_context.getGroup()->end());

  std::vector<std::set<BWAPI::Unit*>> groups = divideInGroups(unitGroup, m_enemyCenter);

  for (std::vector<std::set<BWAPI::Unit*>>::iterator curGroup = groups.begin(); curGroup != groups.end(); ++curGroup)
  {
	  BWAPI::UnitType unitType = (*curGroup->begin())->getType();

    PersistentUnitGroup newGroup(std::string("Sub-group type '" + unitType.getName() + "'"), curGroup->begin(), curGroup->end());
		
	  if (unitType.groundWeapon().maxRange() <= MELEE_RANGE) {
      m_groupBehavior = new MeleeEngageBehavior(&m_context, m_context.getCurrentPriority(), &newGroup);
	  } else {
      m_groupBehavior = new GroupCarefulAttackBehavior(&m_context, m_context.getCurrentPriority() * 2 + 1, &newGroup);
	  }
    m_groupBehavior->start();
  }
}

void SplitGroupCarefulAttackBehavior::Engaging::onExit()
{
}

