// 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 _CLIENTINTERFACE_H_
#define _CLIENTINTERFACE_H_

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

#include <vector>
#include "network_interface.h"
#include "socket.h"
#include "leak_dumper.h"

using Shared::Platform::Ip;
using Shared::Platform::ClientSocket;
using std::vector;

namespace Game {
	class ClientInterface;

	class ClientInterfaceThread : public BaseThread, public SlaveThreadControllerInterface {
	protected:

		ClientInterface * clientInterface;

		virtual void setQuitStatus(bool value);

	public:
		explicit ClientInterfaceThread(ClientInterface *client);
		virtual ~ClientInterfaceThread();
		virtual void execute();

		virtual void setMasterController(MasterSlaveThreadController *master) {
		}
		virtual void signalSlave(void *userdata) {
		}

		virtual bool canShutdown(bool deleteSelfIfShutdownDelayed = false);
	};

	// =====================================================
	//	class ClientInterface
	// =====================================================

	class ClientInterface : public GameNetworkInterface {
	private:
		static const int messageWaitTimeout;
		static const int waitSleepTime;
		static const int maxNetworkCommandListSendTimeWait;

	private:
		ClientSocket * clientSocket;
		string serverName;
		bool introDone;
		bool launchGame;
		int playerIndex;
		bool gameSettingsReceived;
		int gameSettingsReceivedCount;
		time_t connectedTime;
		bool gotIntro;

		Ip ip;
		int port;

		int currentFrameCount;
		int lastSentFrameCount;
		time_t lastNetworkCommandListSendTime;

		time_t clientSimulationLagStartTime;
		string versionString;
		int sessionKey;
		int serverFTPPort;

		string serverUUID;
		string serverPlatform;

		ClientInterfaceThread *networkCommandListThread;

		Mutex *networkCommandListThreadAccessor;
		std::map<int, Commands> cachedPendingCommands;	//commands ready to be given
		std::map<int, vector<uint32> > cachedPendingCommandCRCs;	//commands ready to be given
		uint64 cachedPendingCommandsIndex;
		uint64 cachedLastPendingFrameCount;
		int64 timeClientWaitedForLastMessage;

		Mutex *flagAccessor;
		bool joinGameInProgress;
		bool joinGameInProgressLaunch;
		bool readyForInGameJoin;
		bool resumeInGameJoin;

		Mutex *quitThreadAccessor;
		bool quitThread;

		bool getQuitThread();
		void setQuitThread(bool value);
		bool getQuit();
		void setQuit(bool value);

	public:
		ClientInterface();
		virtual ~ClientInterface();

		virtual std::string getIpAddress(bool mutexLock = true);
		virtual Socket* getSocket(bool mutexLock = true) {
			return clientSocket;
		}
		virtual void close();

		bool getJoinGameInProgress();
		bool getJoinGameInProgressLaunch();

		bool getReadyForInGameJoin();

		bool getResumeInGameJoin();
		void sendResumeGameMessage();

		uint64 getCachedLastPendingFrameCount();
		int64 getTimeClientWaitedForLastMessage();

		//message processing
		virtual void update();
		virtual void updateLobby();
		virtual void updateKeyframe(int frameCount);
		virtual void setKeyframe(int frameCount) {
			currentFrameCount = frameCount;
		}
		virtual void waitUntilReady(Checksum* checksum);

		// message sending
		virtual void sendTextMessage(const string &text, int teamIndex, bool echoLocal,
			string targetLanguage);
		virtual void quitGame(bool userManuallyQuit);

		virtual void sendMarkCellMessage(Vec2i targetPos, int factionIndex, string note, int playerIndex);
		virtual void sendUnMarkCellMessage(Vec2i targetPos, int factionIndex);
		virtual void sendHighlightCellMessage(Vec2i targetPos, int factionIndex);
		//misc
		virtual string getNetworkStatus();

		//accessors
		string getServerName() const {
			return serverName;
		}
		bool getLaunchGame() const {
			return launchGame;
		}
		bool getIntroDone() const {
			return introDone;
		}
		bool getGameSettingsReceived() const {
			return gameSettingsReceived;
		}
		void setGameSettingsReceived(bool value);

		int getGameSettingsReceivedCount() const {
			return gameSettingsReceivedCount;
		}

		int getPlayerIndex() const {
			return playerIndex;
		}

		void connect(const Ip &ip, int port);
		void reset();

		void discoverServers(DiscoveredServersInterface *cb);
		void stopServerDiscovery();

		void sendSwitchSetupRequest(string selectedFactionName, int8 currentSlotIndex,
			int8 toSlotIndex, int8 toTeam, string networkPlayerName,
			NetworkPlayerStatusType networkPlayerStatus, int8 flags,
			string language);
		virtual bool getConnectHasHandshaked() const {
			return gotIntro;
		}
		std::string getServerIpAddress();

		int getCurrentFrameCount() const {
			return currentFrameCount;
		}

		virtual void sendPingMessage(int32 pingFrequency, int64 pingTime);

		const string &getVersionString() const {
			return versionString;
		}
		virtual string getHumanPlayerName(int index = -1);
		virtual int getHumanPlayerIndex() const {
			return playerIndex;
		}
		int getServerFTPPort() const {
			return serverFTPPort;
		}

		int getSessionKey() const {
			return sessionKey;
		}
		bool isMasterServerAdminOverride();

		void setGameSettings(GameSettings *serverGameSettings);
		void broadcastGameSetup(const GameSettings *gameSettings);
		void broadcastGameStart(const GameSettings *gameSettings);

		void updateNetworkFrame();

		virtual void saveGame(XmlNode *rootNode) {
		};

	protected:

		Mutex * getServerSynchAccessor() {
			return NULL;
		}
		NetworkMessageType waitForMessage(int waitMicroseconds = 0);
		bool shouldDiscardNetworkMessage(NetworkMessageType networkMessageType);

		void updateFrame(int *checkFrame);
		void shutdownNetworkCommandListThread(MutexSafeWrapper &safeMutexWrapper);
		bool getNetworkCommand(int frameCount, int currentCachedPendingCommandsIndex);

		void close(bool lockMutex);
	};

} //end namespace

#endif
