// This file is part of ZetaGlest <https://github.com/ZetaGlest>
//
// Copyright (C) 2018  The ZetaGlest team
//
// ZetaGlest is a fork of MegaGlest <https://megaglest.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>

#ifndef _FACTION_H_
#define _FACTION_H_

#ifdef WIN32
#   include <winsock2.h>
#   include <winsock.h>
#endif

#include <vector>
#include <map>
#include "upgrade.h"
#include "texture.h"
#include "resource.h"
#include "game_constants.h"
#include "command_type.h"
#include "base_thread.h"
#include <set>
#include "faction_type.h"
#include "leak_dumper.h"

using std::map;
using std::vector;
using std::set;

using Shared::Graphics::Texture2D;
using namespace Shared::PlatformCommon;

namespace Game {
	class Unit;
	class TechTree;
	class FactionType;
	class ProducibleType;
	class RequirableType;
	class CommandType;
	class UnitType;
	class Game;
	class ScriptManager;
	class World;
	class Faction;
	class GameSettings;
	class SurfaceCell;

	class FowAlphaCellsLookupItem {
	public:

		std::map < Vec2i, float >surfPosAlphaList;
	};

	class ExploredCellsLookupItem {
	public:

		ExploredCellsLookupItem() {
			ExploredCellsLookupItemCacheTimerCountIndex = 0;
		}
		int ExploredCellsLookupItemCacheTimerCountIndex;
		std::vector < SurfaceCell * >exploredCellList;
		std::vector < SurfaceCell * >visibleCellList;

		static time_t lastDebug;
	};

	// =====================================================
	//      class Faction
	//
	///     Each of the game players
	// =====================================================

	struct CommandGroupUnitSorter {
		bool operator () (const Unit * l, const Unit * r);
		bool compare(const Unit * l, const Unit * r);
	};

	struct CommandGroupUnitSorterId {
		Faction *faction;
		bool operator () (const int l, const int r);
	};

	class FactionThread :public BaseThread,
		public SlaveThreadControllerInterface {
	protected:

		Faction * faction;
		Semaphore semTaskSignalled;
		Mutex *triggerIdMutex;
		std::pair < int, bool > frameIndex;
		MasterSlaveThreadController *masterController;

		virtual void setQuitStatus(bool value);
		virtual void setTaskCompleted(int frameIndex);
		virtual bool canShutdown(bool deleteSelfIfShutdownDelayed = false);

	public:
		explicit FactionThread(Faction * faction);
		virtual ~FactionThread();
		virtual void execute();

		virtual void setMasterController(MasterSlaveThreadController * master) {
			masterController = master;
		}
		virtual void signalSlave(void *userdata) {
			signalPathfinder(*((int *) (userdata)));
		}

		void signalPathfinder(int frameIndex);
		bool isSignalPathfinderCompleted(int frameIndex);
	};

	class SwitchTeamVote {
	public:

		int factionIndex;
		int oldTeam;
		int newTeam;
		bool voted;
		bool allowSwitchTeam;
	};

	class Faction {
	private:
		typedef vector < Resource > Resources;
		typedef vector < Resource > Store;
		typedef vector < Faction * >Allies;
		typedef vector < Unit * >Units;
		typedef map < int, Unit * >UnitMap;

	private:
		UpgradeManager upgradeManager;

		Resources resources;
		Store store;
		Allies allies;

		Mutex *unitsMutex;
		Units units;
		UnitMap unitMap;
		World *world;
		ScriptManager *scriptManager;

		FactionPersonalityType overridePersonalityType;
		ControlType control;

		Texture2D *texture;
		FactionType *factionType;

		int index;
		int teamIndex;
		int startLocationIndex;

		bool thisFaction;

		bool factionDisconnectHandled;

		bool cachingDisabled;
		std::map < Vec2i, int >cacheResourceTargetList;
		std::map < Vec2i, bool > cachedCloseResourceTargetLookupList;

		RandomGen random;
		FactionThread *workerThread;

		std::map < int, SwitchTeamVote > switchTeamVotes;
		int currentSwitchTeamVoteFactionIndex;

		bool allowSharedTeamUnits;
		set < int >livingUnits;
		set < Unit * >livingUnitsp;

		std::map < int, int >unitsMovingList;
		std::map < int, int >unitsPathfindingList;

		std::set < const UnitType *>lockedUnits;

		TechTree *techTree;
		const XmlNode *loadWorldNode;

		std::vector < string > worldSynchThreadedLogList;

		std::map < int, string > crcWorldFrameDetails;

		std::map < int, const Unit *>aliveUnitListCache;
		std::map < int, const Unit *>mobileUnitListCache;
		std::map < int, const Unit *>beingBuiltUnitListCache;

		std::map < std::string, bool > resourceTypeCostCache;

	public:
		Faction();
		~Faction();

		Faction(const Faction & obj) {
			init();
			throw game_runtime_error("class Faction is NOT safe to copy!");
		}
		Faction & operator= (const Faction & obj) {
			init();
			throw
				game_runtime_error("class Faction is NOT safe to assign!");
		}

		void notifyUnitAliveStatusChange(const Unit * unit);
		void notifyUnitTypeChange(const Unit * unit, const UnitType * newType);
		void notifyUnitSkillTypeChange(const Unit * unit,
			const SkillType * newType);
		bool hasAliveUnits(bool filterMobileUnits,
			bool filterBuiltUnits) const;

		inline void addWorldSynchThreadedLogList(const string & data) {
			if (SystemFlags::getSystemSettingType(SystemFlags::debugWorldSynch).
				enabled == true) {
				worldSynchThreadedLogList.push_back(data);
			}
		}
		inline void clearWorldSynchThreadedLogList() {
			if (SystemFlags::getSystemSettingType(SystemFlags::debugWorldSynch).
				enabled == true) {
				worldSynchThreadedLogList.clear();
			}
		}
		inline void dumpWorldSynchThreadedLogList() {
			if (SystemFlags::getSystemSettingType(SystemFlags::debugWorldSynch).
				enabled == true) {
				if (worldSynchThreadedLogList.empty() == false) {
					for (unsigned int index = 0;
						index < worldSynchThreadedLogList.size(); ++index) {
						SystemFlags::OutputDebug(SystemFlags::debugWorldSynch,
							worldSynchThreadedLogList[index].
							c_str());
					}
					worldSynchThreadedLogList.clear();
				}
			}
		}

		inline void addLivingUnits(int id) {
			livingUnits.insert(id);
		}
		inline void addLivingUnitsp(Unit * unit) {
			livingUnitsp.insert(unit);
		}

		inline bool isUnitInLivingUnitsp(Unit * unit) {
			return (livingUnitsp.find(unit) != livingUnitsp.end());
		}
		inline void deleteLivingUnits(int id) {
			livingUnits.erase(id);
		}
		inline void deleteLivingUnitsp(Unit * unit) {
			livingUnitsp.erase(unit);
		}

		//std::map<int,int> unitsMovingList;
		void addUnitToMovingList(int unitId);
		void removeUnitFromMovingList(int unitId);
		//int getUnitMovingListCount();

		void addUnitToPathfindingList(int unitId);
		//void removeUnitFromPathfindingList(int unitId);
		int getUnitPathfindingListCount();
		void clearUnitsPathfinding();
		bool canUnitsPathfind();

		void setLockedUnitForFaction(const UnitType * ut, bool lock);
		bool isUnitLocked(const UnitType * ut) const {
			return lockedUnits.find(ut) != lockedUnits.end();
		}

		void init(FactionType * factionType, ControlType control,
			TechTree * techTree, Game * game, int factionIndex,
			int teamIndex, int startLocationIndex, bool thisFaction,
			bool giveResources, const XmlNode * loadWorldNode = NULL);
		void end();

		inline bool getFactionDisconnectHandled() const {
			return factionDisconnectHandled;
		}
		void setFactionDisconnectHandled(bool value) {
			factionDisconnectHandled = value;
		}

		//get
		const Resource *getResource(const ResourceType * rt,
			bool localFactionOnly = false) const;
		inline const Resource *getResource(int i) const {
			return &resources[i];
		}
		int getStoreAmount(const ResourceType * rt, bool localFactionOnly =
			false) const;
		inline const FactionType *getType() const {
			return factionType;
		}
		inline int getIndex() const {
			return index;
		}

		inline int getTeam() const {
			return teamIndex;
		}
		void setTeam(int team) {
			teamIndex = team;
		}

		inline TechTree *getTechTree() const {
			return techTree;
		}
		const SwitchTeamVote *getFirstSwitchTeamVote() const;
		SwitchTeamVote *getSwitchTeamVote(int factionIndex);
		void setSwitchTeamVote(SwitchTeamVote & vote);
		inline int getCurrentSwitchTeamVoteFactionIndex() const {
			return currentSwitchTeamVoteFactionIndex;
		}
		void setCurrentSwitchTeamVoteFactionIndex(int index) {
			currentSwitchTeamVoteFactionIndex = index;
		}

		bool getCpuControl(bool enableServerControlledAI, bool isNetworkGame,
			NetworkRole role) const;
		bool getCpuControl() const;
		inline bool getCpuEasyControl() const {
			return control == ctCpuEasy;
		}
		inline bool getCpuUltraControl() const {
			return control == ctCpuUltra;
		}
		inline bool getCpuZetaControl() const {
			return control == ctCpuZeta;
		}
		inline ControlType getControlType() const {
			return control;
		}

		FactionPersonalityType getPersonalityType() const;
		void setPersonalityType(FactionPersonalityType pType) {
			overridePersonalityType = pType;
		}
		int getAIBehaviorStaticOverideValue(AIBehaviorStaticValueCategory type)
			const;

		inline Unit *getUnit(int i) const {
			Unit *result = units[i];
			return result;
		}
		inline int getUnitCount() const {
			int result = (int) units.size();
			return result;
		}
		inline Mutex *getUnitMutex() {
			return unitsMutex;
		}

		inline const UpgradeManager *getUpgradeManager() const {
			return &upgradeManager;
		}
		inline const Texture2D *getTexture() const {
			return texture;
		}
		inline int getStartLocationIndex() const {
			return startLocationIndex;
		}
		inline bool getThisFaction() const {
			return thisFaction;
		}

		//upgrades
		void startUpgrade(const UpgradeType * ut);
		void cancelUpgrade(const UpgradeType * ut);
		void finishUpgrade(const UpgradeType * ut);

		//cost application
		bool applyCosts(const ProducibleType * p, const CommandType * ct);
		void applyDiscount(const ProducibleType * p, int discount);
		void applyStaticCosts(const ProducibleType * p,
			const CommandType * ct);
		void applyStaticProduction(const ProducibleType * p,
			const CommandType * ct);
		void deApplyCosts(const ProducibleType * p, const CommandType * ct);
		void deApplyStaticCosts(const ProducibleType * p,
			const CommandType * ct);
		void deApplyStaticConsumption(const ProducibleType * p,
			const CommandType * ct);
		void applyCostsOnInterval(const ResourceType * rtApply);
		bool checkCosts(const ProducibleType * pt, const CommandType * ct);

		//reqs
		bool reqsOk(const RequirableType * rt) const;
		bool reqsOk(const CommandType * ct) const;
		int getCountForMaxUnitCount(const UnitType * unitType) const;

		//diplomacy
		bool isAlly(const Faction * faction);

		//other
		Unit *findUnit(int id) const;
		void addUnit(Unit * unit);
		void removeUnit(Unit * unit);
		void addStore(const UnitType * unitType);
		void removeStore(const UnitType * unitType);

		//resources
		void incResourceAmount(const ResourceType * rt, int amount);
		void setResourceBalance(const ResourceType * rt, int balance);

		void setControlType(ControlType value) {
			control = value;
		}

		bool isResourceTargetInCache(const Vec2i & pos,
			bool incrementUseCounter = false);
		void addResourceTargetToCache(const Vec2i & pos,
			bool incrementUseCounter = true);
		void removeResourceTargetFromCache(const Vec2i & pos);
		void addCloseResourceTargetToCache(const Vec2i & pos);
		Vec2i getClosestResourceTypeTargetFromCache(Unit * unit,
			const ResourceType * type,
			int frameIndex);
		Vec2i getClosestResourceTypeTargetFromCache(const Vec2i & pos,
			const ResourceType * type);
		void cleanupResourceTypeTargetCache(std::vector < Vec2i >
			*deleteListPtr, int frameIndex);
		inline int getCacheResourceTargetListSize() const {
			return (int) cacheResourceTargetList.size();
		}

		//      Unit * findClosestUnitWithSkillClass(const Vec2i &pos,const CommandClass &cmdClass,
		//                                                                              const std::vector<SkillClass> &skillClassList,
		//                                                                              const UnitType *unitType);

		void deletePixels();

		inline World *getWorld() {
			return world;
		}
		int getFrameCount();

		void signalWorkerThread(int frameIndex);
		bool isWorkerThreadSignalCompleted(int frameIndex);
		FactionThread *getWorkerThread() {
			return workerThread;
		}

		void limitResourcesToStore();

		void sortUnitsByCommandGroups();

		bool canCreateUnit(const UnitType * ut, bool checkBuild,
			bool checkProduce, bool checkMorph) const;

		string getCacheStats();
		uint64 getCacheKBytes(uint64 * cache1Size, uint64 * cache2Size,
			uint64 * cache3Size, uint64 * cache4Size,
			uint64 * cache5Size);

		std::string toString(bool crcMode = false) const;

		void saveGame(XmlNode * rootNode);
		void loadGame(const XmlNode * rootNode, int factionIndex,
			GameSettings * settings, World * world);

		void clearCaches();

		Checksum getCRC();
		void addCRC_DetailsForWorldFrame(int worldFrameCount,
			bool isNetworkServer);
		string getCRC_DetailsForWorldFrame(int worldFrameCount);
		std::pair < int,
			string > getCRC_DetailsForWorldFrameIndex(int worldFrameIndex) const;
		string getCRC_DetailsForWorldFrames() const;
		uint64 getCRC_DetailsForWorldFrameCount() const;

		void updateUnitTypeWithResourceCostCache(const ResourceType * rt);
		bool hasUnitTypeWithResourceCostInCache(const ResourceType * rt) const;

	private:
		void init();
		void resetResourceAmount(const ResourceType * rt);
		bool hasUnitTypeWithResouceCost(const ResourceType * rt);
	};

} //end namespace

#endif
