#include "StdInc.h"

#include "CMusicHandler.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../CCallback.h"
#include "../lib/CConsoleHandler.h"
#include "CGameInfo.h"
#include "../lib/CGameState.h"
#include "CPlayerInterface.h"
#include "../lib/StartInfo.h"
#include "../lib/BattleState.h"
#include "../lib/CModHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CDefObjInfoHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CObjectHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/Connection.h"
#ifndef __ANDROID__
#include "../lib/Interprocess.h"
#endif
#include "../lib/NetPacks.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h"
#include "../lib/mapping/CMap.h"
#include "../lib/JsonNode.h"
#include "mapHandler.h"
#include "../lib/CConfigHandler.h"
#include "Client.h"
#include "CPreGame.h"
#include "battle/CBattleInterface.h"
#include "../lib/CThreadHelper.h"
#include "../lib/CScriptingModule.h"
#include "../lib/registerTypes/RegisterTypes.h"
#include "gui/CGuiHandler.h"
#include "CMT.h"

extern std::string NAME;
#ifndef __ANDROID__
namespace intpr = boost::interprocess;
#endif

/*
 * Client.cpp, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */

template <typename T> class CApplyOnCL;

class CBaseForCLApply
{
public:
	virtual void applyOnClAfter(CClient *cl, void *pack) const =0; 
	virtual void applyOnClBefore(CClient *cl, void *pack) const =0; 
	virtual ~CBaseForCLApply(){}

	template<typename U> static CBaseForCLApply *getApplier(const U * t=nullptr)
	{
		return new CApplyOnCL<U>;
	}
};

template <typename T> class CApplyOnCL : public CBaseForCLApply
{
public:
	void applyOnClAfter(CClient *cl, void *pack) const
	{
		T *ptr = static_cast<T*>(pack);
		ptr->applyCl(cl);
	}
	void applyOnClBefore(CClient *cl, void *pack) const
	{
		T *ptr = static_cast<T*>(pack);
		ptr->applyFirstCl(cl);
	}
};

template <> class CApplyOnCL<CPack> : public CBaseForCLApply
{
public:
	void applyOnClAfter(CClient *cl, void *pack) const
	{
		logGlobal->errorStream() << "Cannot apply on CL plain CPack!";
		assert(0);
	}
	void applyOnClBefore(CClient *cl, void *pack) const
	{
		logGlobal->errorStream() << "Cannot apply on CL plain CPack!";
		assert(0);
	}
};


static CApplier<CBaseForCLApply> *applier = nullptr;

void CClient::init()
{
	hotSeat = false;
	connectionHandler = nullptr;
	pathInfo = nullptr;
	applier = new CApplier<CBaseForCLApply>;
	registerTypesClientPacks1(*applier);
	registerTypesClientPacks2(*applier);
	IObjectInterface::cb = this;
	serv = nullptr;
	gs = nullptr;
	erm = nullptr;
	terminate = false;
}

CClient::CClient(void)
{
	init();
}

CClient::CClient(CConnection *con, StartInfo *si)
{
	init();
	newGame(con,si);
}

CClient::~CClient(void)
{
	delete applier;
}

void CClient::waitForMoveAndSend(PlayerColor color)
{
	try
	{
		setThreadName("CClient::waitForMoveAndSend");
		assert(vstd::contains(battleints, color));
		BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
		logNetwork->traceStream() << "Send battle action to server: " << ba;
		MakeAction temp_action(ba);
		sendRequest(&temp_action, color);
		return;
	}
	catch(boost::thread_interrupted&)
	{
        logNetwork->debugStream() << "Wait for move thread was interrupted and no action will be send. Was a battle ended by spell?";
		return;
	}
	HANDLE_EXCEPTION
    logNetwork->errorStream() << "We should not be here!";
}

void CClient::run()
{
	setThreadName("CClient::run");
	try
	{
		while(!terminate)
		{
			CPack *pack = serv->retreivePack(); //get the package from the server
			
			if (terminate) 
			{
				vstd::clear_pointer(pack);
				break;
			}

			handlePack(pack);
		}
	} 
	//catch only asio exceptions
	catch (const boost::system::system_error& e)
	{	
        logNetwork->errorStream() << "Lost connection to server, ending listening thread!";
        logNetwork->errorStream() << e.what();
		if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
		{
            logNetwork->errorStream() << "Something wrong, lost connection while game is still ongoing...";
			throw;
		}
	}
}

void CClient::save(const std::string & fname)
{
	if(gs->curB)
	{
        logNetwork->errorStream() << "Game cannot be saved during battle!";
		return;
	}

	SaveGame save_game(fname);
	sendRequest((CPackForClient*)&save_game, PlayerColor::NEUTRAL);
}

void CClient::endGame( bool closeConnection /*= true*/ )
{
	//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
	for(auto i : playerint)
		i.second->finish();

	// Game is ending
	// Tell the network thread to reach a stable state
	if(closeConnection)
		stopConnection();
    logNetwork->infoStream() << "Closed connection.";

	GH.curInt = nullptr;
	{
		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
        logNetwork->infoStream() << "Ending current game!";
		if(GH.topInt())
			GH.topInt()->deactivate();
		GH.listInt.clear();
		GH.objsToBlit.clear();
		GH.statusbar = nullptr;
        logNetwork->infoStream() << "Removed GUI.";

		vstd::clear_pointer(const_cast<CGameInfo*>(CGI)->mh);
		vstd::clear_pointer(gs);

        logNetwork->infoStream() << "Deleted mapHandler and gameState.";
		LOCPLINT = nullptr;
	}

	playerint.clear();
	battleints.clear();
	callbacks.clear();
	battleCallbacks.clear();
    logNetwork->infoStream() << "Deleted playerInts.";

    logNetwork->infoStream() << "Client stopped.";
}

void CClient::loadGame( const std::string & fname )
{
    logNetwork->infoStream() <<"Loading procedure started!";

	CServerHandler sh;
	sh.startServer();

	CStopWatch tmh;
	try
	{
		std::string clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
		std::string controlServerSaveName;

		if (CResourceHandler::get("local")->existsResource(ResourceID(fname, EResType::SERVER_SAVEGAME)))
		{
			controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME));
		}
		else// create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
		{
			controlServerSaveName = clientSaveName.substr(0, clientSaveName.find_last_of(".")) + ".vsgm1";
			CResourceHandler::get("local")->createResource(controlServerSaveName, true);
		}

		if(clientSaveName.empty())
			throw std::runtime_error("Cannot open client part of " + fname);
		if(controlServerSaveName.empty())
			throw std::runtime_error("Cannot open server part of " + fname);

		unique_ptr<CLoadFile> loader;
		{
			CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, minSupportedVersion);
			loadCommonState(checkingLoader);
			loader = checkingLoader.decay();
		}
        logNetwork->infoStream() << "Loaded common part of save " << tmh.getDiff();
		const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
		const_cast<CGameInfo*>(CGI)->mh->map = gs->map;
		pathInfo = make_unique<CPathsInfo>(getMapSize());
		CGI->mh->init();
        logNetwork->infoStream() <<"Initing maphandler: "<<tmh.getDiff();

		*loader >> *this;
        logNetwork->infoStream() << "Loaded client part of save " << tmh.getDiff();
	}
	catch(std::exception &e)
	{
		logGlobal->errorStream() << "Cannot load game " << fname << ". Error: " << e.what();
		throw; //obviously we cannot continue here
	}

	serv = sh.connectToServer();
	serv->addStdVecItems(gs);

	tmh.update();
	ui8 pom8;
	*serv << ui8(3) << ui8(1); //load game; one client
	*serv << fname;
	*serv >> pom8;
	if(pom8) 
		throw std::runtime_error("Server cannot open the savegame!");
	else
        logNetwork->infoStream() << "Server opened savegame properly.";

	*serv << ui32(gs->scenarioOps->playerInfos.size()+1); //number of players + neutral
	for(auto & elem : gs->scenarioOps->playerInfos)
	{
		*serv << ui8(elem.first.getNum()); //players
	}
	*serv << ui8(PlayerColor::NEUTRAL.getNum());
    logNetwork->infoStream() <<"Sent info to server: "<<tmh.getDiff();

	serv->enableStackSendingByID();
	serv->disableSmartPointerSerialization();

// 	logGlobal->traceStream() << "Objects:";
// 	for(int i = 0; i < gs->map->objects.size(); i++)
// 	{
// 		auto o = gs->map->objects[i];
// 		if(o)
// 			logGlobal->traceStream() << boost::format("\tindex=%5d, id=%5d; address=%5d, pos=%s, name=%s") % i % o->id % (int)o.get() % o->pos % o->getHoverText();
// 		else
// 			logGlobal->traceStream() << boost::format("\tindex=%5d --- nullptr") % i;
// 	}
}

void CClient::newGame( CConnection *con, StartInfo *si )
{
	enum {SINGLE, HOST, GUEST} networkMode = SINGLE;

	if (con == nullptr) 
	{
		CServerHandler sh;
		serv = sh.connectToServer();
	}
	else
	{
		serv = con;
		networkMode = (con->connectionID == 1) ? HOST : GUEST;
	}

	CConnection &c = *serv;
	////////////////////////////////////////////////////

	logNetwork->infoStream() <<"\tWill send info to server...";
	CStopWatch tmh;

	if(networkMode == SINGLE)
	{
		ui8 pom8;
		c << ui8(2) << ui8(1); //new game; one client
		c << *si;
		c >> pom8;
		if(pom8) throw std::runtime_error("Server cannot open the map!");
	}

	c >> si;
    logNetwork->infoStream() <<"\tSending/Getting info to/from the server: "<<tmh.getDiff();
	c.enableStackSendingByID();
	c.disableSmartPointerSerialization();

	// Initialize game state
	gs = new CGameState();
	logNetwork->infoStream() <<"\tCreating gamestate: "<<tmh.getDiff();

	gs->scenarioOps = si;
	gs->init(si);
    logNetwork->infoStream() <<"Initializing GameState (together): "<<tmh.getDiff();

	// Now after possible random map gen, we know exact player count.
	// Inform server about how many players client handles
	std::set<PlayerColor> myPlayers;
	for(auto & elem : gs->scenarioOps->playerInfos)
	{
		if((networkMode == SINGLE)                                                      //single - one client has all player
		   || (networkMode != SINGLE && serv->connectionID == elem.second.playerID)      //multi - client has only "its players"
		   || (networkMode == HOST && elem.second.playerID == PlayerSettings::PLAYER_AI))//multi - host has all AI players
		{
			myPlayers.insert(elem.first); //add player
		}
	}
	if(networkMode != GUEST)
		myPlayers.insert(PlayerColor::NEUTRAL);
	c << myPlayers;

	// Init map handler
	if(gs->map)
	{
		const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
		CGI->mh->map = gs->map;
        logNetwork->infoStream() <<"Creating mapHandler: "<<tmh.getDiff();
		CGI->mh->init();
		pathInfo = make_unique<CPathsInfo>(getMapSize());
        logNetwork->infoStream() <<"Initializing mapHandler (together): "<<tmh.getDiff();
	}

	int humanPlayers = 0;
	for(auto & elem : gs->scenarioOps->playerInfos)//initializing interfaces for players
	{
		PlayerColor color = elem.first;
		gs->currentPlayer = color;
		if(!vstd::contains(myPlayers, color))
			continue;

        logNetwork->traceStream() << "Preparing interface for player " << color;
		if(si->mode != StartInfo::DUEL)
		{
			if(elem.second.playerID == PlayerSettings::PLAYER_AI)
			{
				auto AiToGive = aiNameForPlayer(elem.second, false);
				logNetwork->infoStream() << boost::format("Player %s will be lead by %s") % color % AiToGive;
				installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
			}
			else 
			{
				installNewPlayerInterface(make_shared<CPlayerInterface>(color), color);
				humanPlayers++;
			}
		}
		else
		{
			std::string AItoGive = aiNameForPlayer(elem.second, true);
			installNewBattleInterface(CDynLibHandler::getNewBattleAI(AItoGive), color);
		}
	}

	if(si->mode == StartInfo::DUEL)
	{
		if(!gNoGUI)
		{
			boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
			auto p = make_shared<CPlayerInterface>(PlayerColor::NEUTRAL);
			p->observerInDuelMode = true;
			installNewPlayerInterface(p, boost::none);
			GH.curInt = p.get();
		}
		battleStarted(gs->curB);
	}
	else
	{
		loadNeutralBattleAI();
	}

	serv->addStdVecItems(gs);
	hotSeat = (humanPlayers > 1);

// 	std::vector<FileInfo> scriptModules;
// 	CFileUtility::getFilesWithExt(scriptModules, LIB_DIR "/scripting", "." LIB_EXT);
// 	for(FileInfo &m : scriptModules)
// 	{
// 		CScriptingModule * nm = CDynLibHandler::getNewScriptingModule(m.name);
// 		privilagedGameEventReceivers.push_back(nm);
// 		privilagedBattleEventReceivers.push_back(nm);
// 		nm->giveActionCB(this);
// 		nm->giveInfoCB(this);
// 		nm->init();
// 
// 		erm = nm; //something tells me that there'll at most one module and it'll be ERM
// 	}
}

template <typename Handler>
void CClient::serialize( Handler &h, const int version )
{
	h & hotSeat;
	if(h.saving)
	{
		ui8 players = playerint.size();
		h & players;

		for(auto i = playerint.begin(); i != playerint.end(); i++)
		{
			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
			assert(i->first == i->second->playerID);
			h & i->first & i->second->dllName & i->second->human;
			i->second->saveGame(dynamic_cast<COSer<CSaveFile>&>(h), version); 
			//evil cast that i still like better than sfinae-magic. If I had a "static if"...
		}
	}
	else
	{
		ui8 players = 0; //fix for uninitialized warning
		h & players;

		for(int i=0; i < players; i++)
		{
			std::string dllname;
			PlayerColor pid; 
			bool isHuman = false;

			h & pid & dllname & isHuman;
			LOG_TRACE_PARAMS(logGlobal, "Loading player %s interface", pid);

			shared_ptr<CGameInterface> nInt;
			if(dllname.length())
			{
				if(pid == PlayerColor::NEUTRAL)
				{
					installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);
					//TODO? consider serialization 
					continue;
				}
				else
				{
					assert(!isHuman);
					nInt = CDynLibHandler::getNewAI(dllname);
				}
			}
			else
			{
				assert(isHuman);
				nInt = make_shared<CPlayerInterface>(pid);
			}

			nInt->dllName = dllname;
			nInt->human = isHuman;
			nInt->playerID = pid;

			installNewPlayerInterface(nInt, pid);
			nInt->loadGame(dynamic_cast<CISer<CLoadFile>&>(h), version); //another evil cast, check above
		}

		if(!vstd::contains(battleints, PlayerColor::NEUTRAL))
			loadNeutralBattleAI();
	}
}

void CClient::handlePack( CPack * pack )
{			
	CBaseForCLApply *apply = applier->apps[typeList.getTypeID(pack)]; //find the applier
	if(apply)
	{
		boost::unique_lock<boost::recursive_mutex> guiLock(*LOCPLINT->pim);
		apply->applyOnClBefore(this,pack);
        logNetwork->traceStream() << "\tMade first apply on cl";
		gs->apply(pack);
        logNetwork->traceStream() << "\tApplied on gs";
		apply->applyOnClAfter(this,pack);
        logNetwork->traceStream() << "\tMade second apply on cl";
	}
	else
	{
        logNetwork->errorStream() << "Message cannot be applied, cannot find applier! TypeID " << typeList.getTypeID(pack);
	}
	delete pack;
}

void CClient::updatePaths()
{
	//TODO? lazy evaluation? paths now can get recalculated multiple times upon various game events
	const CGHeroInstance *h = getSelectedHero();
	if (h)//if we have selected hero...
		calculatePaths(h);
}

void CClient::finishCampaign( shared_ptr<CCampaignState> camp )
{
}

void CClient::proposeNextMission(shared_ptr<CCampaignState> camp)
{
	GH.pushInt(new CBonusSelection(camp));
}

void CClient::stopConnection()
{
	terminate = true;

	if (serv) //request closing connection
	{
        logNetwork->infoStream() << "Connection has been requested to be closed.";
		boost::unique_lock<boost::mutex>(*serv->wmx);
		CloseServer close_server;
		sendRequest(&close_server, PlayerColor::NEUTRAL);
        logNetwork->infoStream() << "Sent closing signal to the server";
	}

	if(connectionHandler)//end connection handler
	{
		if(connectionHandler->get_id() != boost::this_thread::get_id())
			connectionHandler->join();

        logNetwork->infoStream() << "Connection handler thread joined";

		delete connectionHandler;
		connectionHandler = nullptr;
	}

	if (serv) //and delete connection
	{
		serv->close();
		delete serv;
		serv = nullptr;
        logNetwork->warnStream() << "Our socket has been closed.";
	}
}

void CClient::battleStarted(const BattleInfo * info)
{
	for(auto &battleCb : battleCallbacks)
	{
		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })  
			||  battleCb.first >= PlayerColor::PLAYER_LIMIT)
		{
			battleCb.second->setBattle(info);
		}
	}
// 	for(ui8 side : info->sides)
// 		if(battleCallbacks.count(side))
// 			battleCallbacks[side]->setBattle(info);

	shared_ptr<CPlayerInterface> att, def;
	auto &leftSide = info->sides[0], &rightSide = info->sides[1];


	//If quick combat is not, do not prepare interfaces for battleint
	if(!settings["adventure"]["quickCombat"].Bool())
	{
		if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
			att = std::dynamic_pointer_cast<CPlayerInterface>( playerint[leftSide.color] );

		if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
	}

	if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL))
	{
		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
		auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
			Rect((screen->w - 800)/2, 
			     (screen->h - 600)/2, 800, 600), att, def);

		GH.pushInt(bi);
	}

	auto callBattleStart = [&](PlayerColor color, ui8 side){
		if(vstd::contains(battleints, color))
			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
	};

	callBattleStart(leftSide.color, 0);
	callBattleStart(rightSide.color, 1);
	callBattleStart(PlayerColor::UNFLAGGABLE, 1);

	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
	{
		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);
	}
}

void CClient::battleFinished()
{
	for(auto & side : gs->curB->sides)
		if(battleCallbacks.count(side.color))
			battleCallbacks[side.color]->setBattle(nullptr);
}

void CClient::loadNeutralBattleAI()
{
	installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL);
}

void CClient::commitPackage( CPackForClient *pack )
{
	CommitPackage cp;
	cp.freePack = false;
	cp.packToCommit = pack;
	sendRequest(&cp, PlayerColor::NEUTRAL);
}

PlayerColor CClient::getLocalPlayer() const
{
	if(LOCPLINT)
		return LOCPLINT->playerID;
	return getCurrentPlayer();
}

void CClient::calculatePaths(const CGHeroInstance *h)
{
	assert(h);
	boost::unique_lock<boost::mutex> pathLock(pathMx);
	gs->calculatePaths(h, *pathInfo);
}

void CClient::commenceTacticPhaseForInt(shared_ptr<CBattleGameInterface> battleInt)
{
	setThreadName("CClient::commenceTacticPhaseForInt");
	try
	{
		battleInt->yourTacticPhase(gs->curB->tacticDistance);
		if(gs && !!gs->curB && gs->curB->tacticDistance) //while awaiting for end of tactics phase, many things can happen (end of battle... or game)
		{
			MakeAction ma(BattleAction::makeEndOFTacticPhase(gs->curB->playerToSide(battleInt->playerID)));
			sendRequest(&ma, battleInt->playerID);
		}
	} HANDLE_EXCEPTION
}

void CClient::invalidatePaths(const CGHeroInstance *h /*= nullptr*/)
{
	if(!h || pathInfo->hero == h)
		pathInfo->isValid = false;
}

int CClient::sendRequest(const CPack *request, PlayerColor player)
{
	static ui32 requestCounter = 0;

	ui32 requestID = requestCounter++;
    logNetwork->traceStream() << boost::format("Sending a request \"%s\". It'll have an ID=%d.")
				% typeid(*request).name() % requestID;

	waitingRequest.pushBack(requestID);
	serv->sendPackToServer(*request, player, requestID);
	if(vstd::contains(playerint, player))
		playerint[player]->requestSent(dynamic_cast<const CPackForServer*>(request), requestID);

	return requestID;
}

void CClient::campaignMapFinished( shared_ptr<CCampaignState> camp )
{
	endGame(false);

	GH.curInt = CGPreGame::create();
	auto & epilogue = camp->camp->scenarios[camp->mapsConquered.back()].epilog;
	auto finisher = [=]()
	{
		if(camp->mapsRemaining.size())
			proposeNextMission(camp);
		else
			finishCampaign(camp);
	};
	if(epilogue.hasPrologEpilog)
	{
		GH.pushInt(new CPrologEpilogVideo(epilogue, finisher));
	}
	else
	{
		finisher();
	}
}

void CClient::installNewPlayerInterface(shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color)
{
	boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);

	if(!color) 
		privilagedGameEventReceivers.push_back(gameInterface);

	playerint[colorUsed] = gameInterface;

	logGlobal->traceStream() << boost::format("\tInitializing the interface for player %s") % colorUsed;
	auto cb = make_shared<CCallback>(gs, color, this);
	callbacks[colorUsed] = cb;
	battleCallbacks[colorUsed] = cb;
	gameInterface->init(cb);

	installNewBattleInterface(gameInterface, color, false);
}

void CClient::installNewBattleInterface(shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback /*= true*/)
{
	boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);

	if(!color) 
		privilagedBattleEventReceivers.push_back(battleInterface);

	battleints[colorUsed] = battleInterface;

	if(needCallback)
	{
		logGlobal->traceStream() << boost::format("\tInitializing the battle interface for player %s") % *color;
		auto cbc = make_shared<CBattleCallback>(gs, color, this);
		battleCallbacks[colorUsed] = cbc;
		battleInterface->init(cbc);
	}
}

std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
{
	if(ps.name.size())
	{
		std::string filename = VCMIDirs::get().libraryPath() + "/AI/" + VCMIDirs::get().libraryName(ps.name);
		if(boost::filesystem::exists(filename))
			return ps.name;
	}

	const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
	std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();
	std::string badAI = battleAI ? "StupidAI" : "EmptyAI";


	//TODO what about human players
	if(battleints.size() >= sensibleAILimit)
		return badAI;

	return goodAI;
}

template void CClient::serialize( CISer<CLoadFile> &h, const int version );
template void CClient::serialize( COSer<CSaveFile> &h, const int version );

void CServerHandler::startServer()
{
	th.update();
	serverThread = new boost::thread(&CServerHandler::callServer, this); //runs server executable;
	if(verbose)
        logNetwork->infoStream() << "Setting up thread calling server: " << th.getDiff();
}

void CServerHandler::waitForServer()
{
	if(!serverThread)
		startServer();

	th.update();
#ifndef __ANDROID__
	intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
	while(!shared->sr->ready)
	{
		shared->sr->cond.wait(slock);
	}
#endif
	if(verbose)
        logNetwork->infoStream() << "Waiting for server: " << th.getDiff();
}

CConnection * CServerHandler::connectToServer()
{
#ifndef __ANDROID__
	if(!shared->sr->ready)
		waitForServer();
#else
	waitForServer();
#endif

	th.update(); //put breakpoint here to attach to server before it does something stupid
	CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port);

	if(verbose)
        logNetwork->infoStream()<<"\tConnecting to the server: "<<th.getDiff();

	return ret;
}

CServerHandler::CServerHandler(bool runServer /*= false*/)
{
	serverThread = nullptr;
	shared = nullptr;
	port = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
	verbose = true;

#ifndef __ANDROID__
	boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
	try
	{
		shared = new SharedMem();
    } HANDLE_EXCEPTIONC(logNetwork->errorStream() << "Cannot open interprocess memory: ";)
#endif
}

CServerHandler::~CServerHandler()
{
	delete shared;
	delete serverThread; //detaches, not kills thread
}

void CServerHandler::callServer()
{
	setThreadName("CServerHandler::callServer");
	std::string logName = VCMIDirs::get().userCachePath() + "/server_log.txt";
	std::string comm = VCMIDirs::get().serverPath() + " --port=" + port + " > " + logName;
	int result = std::system(comm.c_str());
	if (result == 0)
        logNetwork->infoStream() << "Server closed correctly";
	else
	{
        logNetwork->errorStream() << "Error: server failed to close correctly or crashed!";
        logNetwork->errorStream() << "Check " << logName << " for more info";
		exit(1);// exit in case of error. Othervice without working server VCMI will hang
	}
}

CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
{
	CConnection *ret = nullptr;
	while(!ret)
	{
		try
		{
            logNetwork->infoStream() << "Establishing connection...";
			ret = new CConnection(	host.size() ? host : settings["server"]["server"].String(), 
									port.size() ? port : boost::lexical_cast<std::string>(settings["server"]["port"].Float()), 
									NAME);
		}
		catch(...)
		{
            logNetwork->errorStream() << "\nCannot establish connection! Retrying within 2 seconds";
			SDL_Delay(2000);
		}
	}
	return ret;
}