//////////////////////////////////////////////////////////////////////////
//
// This file is part of Iron's source files.
// Iron is free software, licensed under the MIT/X11 License. 
// A copy of the license is provided with the library in the LICENSE file.
// Copyright (c) 2016, Igor Dimitrijevic
//
//////////////////////////////////////////////////////////////////////////


#include "fight.h"
#include "my.h"
#include "his.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }

namespace iron
{

const int death_delay = 3;

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class FightSim
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


FightSim::FightSim()
{

}


void FightSim::AddMyUnit(BWAPI::UnitType type, int life, int shields)
{
	m_MyUnits.push_back(UnitInfo(type, life, shields));
}


void FightSim::AddHisUnit(BWAPI::UnitType type, int life, int shields)
{
	m_HisUnits.push_back(UnitInfo(type, life, shields));
}


void FightSim::Compute_FaceOffInfoMatrix()
{
	for (const UnitInfo & myUnit : m_MyUnits)
	for (const UnitInfo & hisUnit : m_HisUnits)
	{
		// myUnit attacks hisUnit:
		auto inserted = m_FaceOffInfoMatrix.insert(make_pair(make_pair(myUnit.type, hisUnit.type), FaceOffInfo()));
		if (inserted.second)
		{
			inserted.first->second.pleinAttack = pleinAttack(myUnit.type, me().Player(), hisUnit.type.isFlyer());
			inserted.first->second.sizedAttack_times4 = sizedAttack_times4(myUnit.type, me().Player(), hisUnit.type, him().Player(), hisUnit.type.isFlyer());
		}

		// hisUnit attacks myUnit:
		inserted = m_FaceOffInfoMatrix.insert(make_pair(make_pair(hisUnit.type, myUnit.type), FaceOffInfo()));
		if (inserted.second)
		{
			inserted.first->second.pleinAttack = pleinAttack(hisUnit.type, him().Player(), myUnit.type.isFlyer());
			inserted.first->second.sizedAttack_times4 = sizedAttack_times4(hisUnit.type, him().Player(), myUnit.type, me().Player(), myUnit.type.isFlyer());
		}
	}
}


void FightSim::UnitInfo::UpdateShields(int v, frame_t time)
{
	assert_throw(death >= time);

	shields += v;
	shields = max(0, min(type.maxShields(), shields));
}


void FightSim::UnitInfo::UpdateLife4(int v4, frame_t time)
{
	assert_throw(death >= time);

	life_times4 += v4;
	life_times4 = max(0, min(type.maxHitPoints()*4, life_times4));

	if (life_times4 == 0) death = time + death_delay;
}


class FightSim::Event
{
public:
	enum type {myAttack, hisAttack, protossRegen, zergRegen};

					Event(type t) : m_type(t), m_pUnitInfo(nullptr)
					{
						assert_throw(t == protossRegen || t == zergRegen);

						m_freq = (t == protossRegen) ? 37 : 64;
						m_nextFrame = m_freq;
					}
					
					Event(type t, UnitInfo * pUnitInfo) : m_type(t), m_pUnitInfo(pUnitInfo)
					{
						assert_throw(t == myAttack || t == hisAttack);
						assert_throw(pUnitInfo);

						m_freq = avgCoolDown(pUnitInfo->type, t == myAttack ? me().Player() : him().Player());
						m_nextFrame = 0;
						if (pUnitInfo->type.isWorker()) m_nextFrame = 40;
					}

	type			Type() const			{ return m_type; }
	UnitInfo *		GetUnitInfo() const		{ return m_pUnitInfo; }
	frame_t			NextFrame() const		{ return m_nextFrame; }
	void			UpdateNextFrame()		{ m_nextFrame += m_freq; }


	bool			operator<(const Event & Other) const { return m_nextFrame > Other.m_nextFrame; }

private:
	type			m_type;
	frame_t			m_freq;
	frame_t			m_nextFrame = 0;
	UnitInfo *		m_pUnitInfo;
};


bool FightSim::EvalAttack(const UnitInfo * pAttacker, vector<UnitInfo> & Targets, frame_t time)
{
	for (UnitInfo & target : Targets)
		if (target.death > time + death_delay)
		{
			auto iFound = m_FaceOffInfoMatrix.find(make_pair(pAttacker->type, target.type));
			assert_throw(iFound != m_FaceOffInfoMatrix.end());
			
			int pleinAttack = iFound->second.pleinAttack;					// damage to target's shields
			int sizedAttack_times4 = iFound->second.sizedAttack_times4;		// damage to target's life*4
			if (sizedAttack_times4 == 0) continue;

			if (target.shields > 0)
			{
				if (pleinAttack <= target.shields)
					sizedAttack_times4 = 0;
				else
					sizedAttack_times4 = sizedAttack_times4 * (pleinAttack - target.shields) / pleinAttack;

				target.UpdateShields(-pleinAttack, time);
			}

			target.UpdateLife4(-sizedAttack_times4, time);

			return true;
		}

	return false;
}


void FightSim::PrintUnitState(ostream & out, const UnitInfo & u) const
{
	if (u.type.getRace() == Races::Protoss) out << u.shields << "_";

	int n = u.life_times4 / 4;

//	out << n;
//	if (u.life_times4 % 4) out << "." << 25 * (u.life_times4 % 4);

	if ((n == 0) && (u.life_times4 % 4)) n = 1;
	out << n;

	out << "  ";
}


void FightSim::PrintState(ostream & out, char eventType, frame_t time) const
{
	out << eventType << " ";
	out << time << ") \t";
	for (const UnitInfo & u : m_MyUnits)
		PrintUnitState(out, u);

	out << "  vs   ";

	for (const UnitInfo & u : m_HisUnits)
		PrintUnitState(out, u);

	out << endl;
}


void FightSim::CalculateEfficiency()
{
	int myCost = 0;
	for (const UnitInfo & u : m_MyUnits)
		if (u.life_times4 == 0)
			myCost += Cost(u.type).Minerals() + Cost(u.type).Gas();

	int hisCost = 0;
	for (const UnitInfo & u : m_HisUnits)
		if (u.life_times4 == 0)
			hisCost += Cost(u.type).Minerals() + Cost(u.type).Gas();

	m_efficiency = hisCost - myCost;
}


void FightSim::Eval()
{
	assert_throw(!m_MyUnits.empty());
	assert_throw(!m_HisUnits.empty());

	Compute_FaceOffInfoMatrix();

	priority_queue<Event, vector<Event>> EventQueue;

	for (UnitInfo & u : m_MyUnits)	EventQueue.emplace(Event::myAttack, &u);
	for (UnitInfo & u : m_HisUnits)	EventQueue.emplace(Event::hisAttack, &u);

	if (him().Race() == Races::Protoss) EventQueue.emplace(Event::protossRegen);
	if (him().Race() == Races::Zerg) EventQueue.emplace(Event::zergRegen);

	while (!EventQueue.empty())
	{
		Event event = EventQueue.top();
		EventQueue.pop();

		frame_t time = event.NextFrame();

		if (event.Type() == Event::myAttack)
		{
			if (event.GetUnitInfo()->death < time) continue;

			if (!EvalAttack(event.GetUnitInfo(), m_HisUnits, time)) break;
		}
		else if (event.Type() == Event::hisAttack)
		{
			if (event.GetUnitInfo()->death < time) continue;

			if (!EvalAttack(event.GetUnitInfo(), m_MyUnits, time)) break;
		}
		else if (event.Type() == Event::protossRegen)
		{
			for (UnitInfo & u : m_HisUnits)
				if (u.death >= time)
					u.UpdateShields(+1, time);
		}
		else if (event.Type() == Event::zergRegen)
		{
			for (UnitInfo & u : m_HisUnits)
				if (u.death >= time)
					u.UpdateLife4(+4, time);
		}

		event.UpdateNextFrame();
		EventQueue.push(event);
/*
		PrintState(	Log, 
					event.Type() == Event::myAttack ? '+' : 
					event.Type() == Event::hisAttack ? '-' : 
					event.Type() == Event::protossRegen ? 'p' : 
					event.Type() == Event::zergRegen ? 'z' : '?',
					time);
*/
	}


	CalculateEfficiency();
	m_win = m_MyUnits.back().life_times4 > 0;

	ostringstream oss;
	PrintState(oss, ' ', 0);
	m_finalState = oss.str();


//	int myRemainingUnits = count_if(m_MyUnits.begin(), m_MyUnits.end(), [](const UnitInfo & u){ return u.life_times4 > 0; });
//	int hisRemainingUnits = count_if(m_HisUnits.begin(), m_HisUnits.end(), [](const UnitInfo & u){ return u.life_times4 > 0; });
//	bw << "--> " << myRemainingUnits << " vs " << hisRemainingUnits << endl;
}

	
} // namespace iron



