#pragma once

#include "Common.h"
#include "InformationManager.h"
#include "WorkerManager.h"
#include "BuildOrder.h"
#include "BuildOrderQueue.h"
#include "UnitUtil.h"

namespace UAlbertaBot
{
typedef std::pair<MacroAct, size_t> MetaPair;
typedef std::vector<MetaPair> MetaPairVector;

// Unit choices for main unit mix and tech target.
// This deliberately omits support units like queens and defilers.
enum class TechUnit : int
{
	None
	, Zerglings
	, Hydralisks
	, Lurkers
	, Mutalisks
	, Scourge
	, Guardians
	, Devourers
	, Ultralisks
	, Size
};

struct Strategy
{
	std::string _name;
	BWAPI::Race _race;
	int         _wins;
	int         _losses;
	std::string _openingGroup;
	BuildOrder  _buildOrder;

	Strategy()
		: _name("None")
		, _race(BWAPI::Races::None)
		, _wins(0)
		, _losses(0)
		, _openingGroup("")
	{
	}

	Strategy(const std::string & name, const BWAPI::Race & race, const std::string & openingGroup, const BuildOrder & buildOrder)
		: _name(name)
		, _race(race)
		, _wins(0)
		, _losses(0)
		, _openingGroup(openingGroup)
		, _buildOrder(buildOrder)
	{
	}
};


class StrategyManager 
{
	StrategyManager();

	// Values that cannot or should not be exceeded.
	const int						absoluteMaxSupply = 400;
	const int						absoluteMaxDrones = 80;

	BWAPI::Player					_self;
	BWAPI::Player					_enemy;
	BWAPI::Race					    _selfRace;
	BWAPI::Race					    _enemyRace;
	
	std::map<std::string, Strategy> _strategies;
	std::map<std::string, int> _strategiesMatchup;
    int                             _totalGamesPlayed;
    const BuildOrder                _emptyBuildOrder;
	int                             _lingScore; 
	int                             _hydraScore;
	int								_mutaScore;
	int								_lurkerScore;
	int								_ultraScore;
	int								_guardianScore;
	int								_devourerScore;
	std::string						_techTarget;
	std::string						_minUnit;
	std::string						_gasUnit;
	BuildOrder						_buildOrderZerg;
	double							_droneEco;
	int								_droneMax;
	bool							_hasIslandBases;

	// For choosing the tech target and the unit mix.
	std::array<int, int(TechUnit::Size)> techScores;

	        void	                writeResults();
	const	int					    getScore(BWAPI::Player player) const;
	const	double				    getUCBValue(const size_t & strategy) const;
	const	bool				    shouldExpandNow() const;
	const	MetaPairVector		    getProtossBuildOrderGoal() const;
	const	MetaPairVector		    getTerranBuildOrderGoal() const;

			bool					vProtossDenOverSpire();
			bool					vTerranDenOverSpire();
			bool					chooseDenOverSpire();
			bool					isBeingBuilt(BWAPI::UnitType unitType);

			int						getLingScore();
			int						getHydraScore();
			int						getMutaScore();
			int						getLurkerScore();
			int						getUltraScore();
			int						getGuardianScore();
			int						getDevourerScore();

			int						updateLingScore();
			int						updateHydraScore();
			int						updateMutaScore();
			int						updateLurkerScore();
			int						updateUltraScore();
			int						updateGuardianScore();

public:
    
	static	StrategyManager &	    Instance();

			void				    onEnd(const bool isWinner);
            void                    addStrategy(const std::string & name, Strategy & strategy);
			void                    addStrategyMatchup(const std::string & name, int weight);
			void	                setRandomStrategyOnce();
            void                    setLearnedStrategy();
            void	                readResults();
	const	MetaPairVector		    getBuildOrderGoal();
	const	BuildOrder &            getOpeningBookBuildOrder() const;

			void					getZergBuildOrder(BuildOrderQueue & queue);
			void					getTerranBuildOrder(BuildOrderQueue & queue);
			void					getProtossBuildOrder(BuildOrderQueue & queue);
			void					handleUrgentProductionIssues(BuildOrderQueue & queue);
			bool					detectSupplyBlock(BuildOrderQueue & queue);
			void					drawStrategyInformation(int x, int y);
			int						maxWorkers();
			void					chooseGasUnit();
			void					chooseMinUnit();
			void					chooseTechTarget();

			std::string techTargetToString(TechUnit target);
			TechUnit techTargetToTextUnit(std::string target);
			void setAvailableTechUnits(std::array<bool, int(TechUnit::Size)> & available);
			int techTier(TechUnit techUnit) const;
};

}