/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */
#ifndef INC_C4Network2
#define INC_C4Network2

#include "control/C4Control.h"
#include "control/C4GameParameters.h"
#include "gui/C4Gui.h"
#include "network/C4NetIO.h"
#include "network/C4Network2Client.h"
#include "network/C4Network2IO.h"
#include "network/C4Network2Players.h"
#include "network/C4Network2Res.h"

// standard ports
const int16_t C4NetStdPortTCP = 11112,
              C4NetStdPortUDP = 11113,
              C4NetStdPortDiscovery = 11114,
              C4NetStdPortRefServer = 11111,
              C4NetStdPortPuncher = 11115,
              C4NetStdPortHTTP = 80;

// resource retrieve wait timeout
const int C4NetResRetrieveTimeout = 100000; // (ms)

// client (de)activation
const int C4NetActivationReqInterval = 5000, // (ms)
          C4NetMaxBehind4Activation = 20, // (ticks)
          C4NetDeactivationDelay = 500; // (ticks)

// client chase
const unsigned int C4NetChaseTargetUpdateInterval = 5; // (s)

// reference
const unsigned int C4NetReferenceUpdateInterval = 120; // (s)
const unsigned int C4NetMinLeagueUpdateInterval = 1; // (s)

// voting
const unsigned int C4NetVotingTimeout = 10; // (s)
const unsigned int C4NetMinVotingInterval = 120; // (s)

// streaming
const size_t C4NetStreamingMinBlockSize = 10 * 1024;
const size_t C4NetStreamingMaxBlockSize = 20 * 1024;
const int C4NetStreamingInterval = 30; // (s)

enum C4NetGameState
{
	GS_None,    // network not active
	GS_Init,    // connecting to host, waiting for join data
	GS_Lobby,   // lobby mode
	GS_Pause,   // game paused
	GS_Go     // game running
};

class C4Network2Status : public C4PacketBase
{
public:
	C4Network2Status();

protected:
	C4NetGameState eState{GS_None};
	int32_t iCtrlMode;
	int32_t iTargetCtrlTick{-1};

public:
	C4NetGameState  getState()            const { return eState; }
	int32_t         getCtrlMode()         const { return iCtrlMode; }
	int32_t         getTargetCtrlTick()   const { return iTargetCtrlTick; }
	const char     *getStateName()        const;
	const char     *getDescription()      const;

	bool            isEnabled()     const { return eState != GS_None; }
	bool            isLobbyActive() const { return eState == GS_Lobby; }
	bool            isPastLobby()   const { return eState  > GS_Lobby; }
	bool            isPaused()      const { return eState == GS_Pause; }
	bool            isRunning()     const { return eState == GS_Go; }

	void Set(C4NetGameState eState, int32_t iTargetCtrlTick);
	void SetCtrlMode(int32_t iCtrlMode);
	void SetTargetTick(int32_t iTargetCtrlTick);
	void Clear();

	void CompileFunc(StdCompiler *pComp, bool fReference);
	void CompileFunc(StdCompiler *pComp) override;
};

class C4Network2 : private C4ApplicationSec1Timer
{
	friend class C4Network2IO;
public:
	C4Network2();
	~C4Network2() override;

public:
	// network i/o class
	C4Network2IO NetIO;

	// resource list
	C4Network2ResList ResList;

	// client list
	C4Network2ClientList Clients;

	// player list
	C4Network2Players Players;

	// game status
	C4Network2Status Status;

protected:

	// role
	bool fHost;

	// options
	bool fAllowJoin{false}, fAllowObserve;

	// join resource
	C4Network2ResCore ResDynamic;

	// resources
	int32_t iDynamicTick{-1};
	bool fDynamicNeeded{false};

	// game status flags
	bool fStatusAck{false}, fStatusReached{false};
	bool fChasing{false};

	// control
	class C4GameControlNetwork *pControl{nullptr};

	// lobby
	C4GameLobby::MainDlg *pLobby{nullptr};
	bool fLobbyRunning{false};
	C4GameLobby::Countdown *pLobbyCountdown{nullptr};

	// master server used
	StdCopyStrBuf MasterServerAddress;

	// clients
	int32_t iNextClientID{0};

	// chase
	uint32_t iLastChaseTargetUpdate{0};

	// time of last activation request.
	C4TimeMilliseconds tLastActivateRequest;

	// reference
	uint32_t iLastReferenceUpdate{0};
	uint32_t iLastLeagueUpdate{0}, iLeagueUpdateDelay;
	bool fLeagueEndSent;

	// league
	class C4LeagueClient *pLeagueClient{nullptr};

	// game password
	StdStrBuf sPassword;

	// connection failed because password needed?
	bool fWrongPassword;

	// delayed activation request?
	bool fDelayedActivateReq{false};

	// voting
	C4Control Votes;
	class C4VoteDialog *pVoteDialog{nullptr};
	bool fPausedForVote{false};
	time_t iVoteStartTime, iLastOwnVoting{0};

	// streaming
	bool fStreaming{false};
	time_t iLastStreamAttempt;
	C4Record *pStreamedRecord;
	StdBuf StreamingBuf;
	z_stream StreamCompressor;

	class C4Network2HTTPClient *pStreamer;
	unsigned int iCurrentStreamAmount, iCurrentStreamPosition;

	// puncher
	C4NetpuncherID NetpuncherGameID;
	StdCopyStrBuf NetpuncherAddr;

public:

	// data access
	bool isEnabled()    const { return Status.isEnabled(); }
	bool isLobbyActive()const { return Status.isLobbyActive(); }
	bool isPastLobby()  const { return Status.isPastLobby(); }
	bool isRunning()    const { return Status.isRunning() && isStatusAck(); }
	bool isPaused()     const { return Status.isPaused() && isStatusAck(); }
	bool isPausing()    const { return Status.isPaused() && !fStatusAck; }
	bool isHost()       const { return fHost; }
	bool isStatusAck()  const { return fStatusAck; }
	bool isFrozen()     const;

	bool isJoinAllowed() const { return fAllowJoin; }
	bool isObservingAllowed() const { return fAllowObserve; }

	class C4GameLobby::MainDlg *GetLobby() const { return pLobby; } // lobby publication
	const char *GetPassword() const { return sPassword.getData(); } // Oh noez, now the password is public!
	bool isPassworded() const { return !sPassword.isNull(); }

	// initialization result type
	enum InitResult
	{
		IR_Success, IR_Error, IR_Fatal
	};

	// initialization
	bool InitHost(bool fLobby);
	InitResult InitClient(const class C4Network2Reference &Ref, bool fObserver);
	InitResult InitClient(const std::vector<class C4Network2Address>& Addrs, const class C4ClientCore &HostCore, const char *szPassword = nullptr);
	bool DoLobby();
	bool Start();
	bool Pause();
	bool Sync();
	bool FinalInit();

	bool RetrieveScenario(char *szScenario);
	C4Network2Res::Ref RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeout, const char *szResName, bool fWaitForCore = false);

	// idle processes
	void OnSec1Timer() override;
	void Execute();

	// termination
	void Clear();

	// some options
	bool ToggleAllowJoin();
	bool ToggleClientListDlg();
	void AllowJoin(bool fAllow);
	void SetAllowObserve(bool fAllow);
	void SetCtrlMode(int32_t iCtrlMode);
	void SetPassword(const char *szToPassword);
	StdStrBuf QueryClientPassword(); // ask client for a password; empty if user canceled

	// interface for C4Network2IO
	void OnConn(C4Network2IOConnection *pConn);
	void OnDisconn(C4Network2IOConnection *pConn);
	void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
	void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
	bool HandlePuncherPacket(C4NetpuncherPacket::uptr, C4NetIO::HostAddress::AddressFamily family);
	void OnPuncherConnect(C4NetIO::addr_t addr);

	// runtime join stuff
	void OnGameSynchronized();

	// status
	void DrawStatus(C4TargetFacet &cgo);

	// client activation
	void RequestActivate();
	void DeactivateInactiveClients(); // host

	// league
	void LeagueGameEvaluate(const char *szRecordName = nullptr, const BYTE *pRecordSHA = nullptr);
	void LeagueSignupDisable(); // if "internet game" button is switched off in lobby: Remove from league server
	bool LeagueSignupEnable();  // if "internet game" button is switched on in lobby: (re)Add to league server
	void InvalidateReference(); // forces a recreation and re-send of the game reference in the next execution cycle
	bool LeaguePlrAuth(C4PlayerInfo *pInfo); // client: get authentication for a player from the league server
	bool LeaguePlrAuthCheck(C4PlayerInfo *pInfo); // host: check AUID of player info with league server
	void LeagueNotifyDisconnect(int32_t iClientID, enum C4LeagueDisconnectReason eReason); //
	void LeagueWaitNotBusy(); // block until league serveris no longer busy. Process update reply if last message was an update
	void LeagueSurrender(); // forfeit in league - just fake a disconnect
	void LeagueShowError(const char *szMsg); // show league error msg box in fullscreen; just log in console

	// voting
	void Vote(C4ControlVoteType eType, bool fApprove = true, int32_t iData = 0);
	void AddVote(const C4ControlVote &Vote);
	C4IDPacket *GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData);
	void EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData);
	void OpenVoteDialog();
	void OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData);
	void OnVoteDialogClosed();

	// lobby countdown
	void StartLobbyCountdown(int32_t iCountdownTime);
	void AbortLobbyCountdown();
	bool isLobbyCountDown() { return pLobbyCountdown != nullptr; }

	// streaming
	size_t getPendingStreamData() const { return StreamingBuf.getSize() - StreamCompressor.avail_out; }
	bool isStreaming() const;
	bool StartStreaming(C4Record *pRecord);
	bool FinishStreaming();
	bool StopStreaming();

	// netpuncher
	C4NetpuncherID::value& getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family);
	C4NetpuncherID getNetpuncherGameID() const { return NetpuncherGameID; };
	StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; }

protected:

	using C4ApplicationSec1Timer::Execute; // to avoid "virtual ... is hidden" warning

	// net i/o initialization
	bool InitNetIO(bool fNoClientID, bool fHost);

	// handling of own packets
	void HandleConn(const class C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient);
	bool CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, StdStrBuf * szReply);
	bool HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply);
	bool Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply);
	void HandleConnRe(const class C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient);
	void HandleStatus(const C4Network2Status &nStatus);
	void HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient);
	void HandleActivateReq(int32_t iTick, C4Network2Client *pClient);
	void HandleJoinData(const class C4PacketJoinData &rPkt);

	// events
	void OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection);
	void OnConnectFail(C4Network2IOConnection *pConn);
	void OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn);
	void OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn);
	void OnClientDisconnect(C4Network2Client *pClient);

	void SendJoinData(C4Network2Client *pClient);

	// resource list
	bool CreateDynamic(bool fInit);
	void RemoveDynamic();

	// status changes
	bool PauseGame(bool fAutoContinue);
	bool ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode = -1);
	void CheckStatusReached(bool fFromFinalInit = false);
	void CheckStatusAck();
	void OnStatusReached();
	void OnStatusAck();

	// chase
	void UpdateChaseTarget();

	// league
	bool InitLeague(bool *pCancel);
	void DeinitLeague();
	bool LeagueStart(bool *pCancel);
	bool LeagueUpdate();
	bool LeagueUpdateProcessReply();
	bool LeagueEnd(const char *szRecordName = nullptr, const BYTE *pRecordSHA = nullptr);

	// streaming
	bool StreamIn(bool fFinish);
	bool StreamOut();

	// puncher
	void InitPuncher();

	// Manages delayed initial connection attempts.
	class InitialConnect : protected CStdTimerProc
	{
		static const uint32_t DELAY = 100; // ms to wait for each batch
		static const int ADDR_PER_TRY = 4; // how many connection attempts to start each time

		const std::vector<C4Network2Address>& Addrs;
		std::vector<C4Network2Address>::const_iterator CurrentAddr;
		const C4ClientCore& HostCore;
		const char *Password;

		void TryNext();
		void Done();

	public:
		InitialConnect(const std::vector<C4Network2Address>& Addrs, const C4ClientCore& HostCore, const char *Password);
		~InitialConnect() override;
		bool Execute(int, pollfd *) override;
	};
};

extern C4Network2 Network;

class C4VoteDialog : public C4GUI::MessageDialog
{
public:
	C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender);

private:
	C4ControlVoteType eVoteType;
	int32_t iVoteData;
	bool fSurrender;

public:
	C4ControlVoteType getVoteType() const { return eVoteType; }
	int32_t getVoteData() const { return iVoteData; }

private:
	bool OnEnter() override { UserClose(false); return true; } // the vote dialog defaults to "No" [MarkFra]
	void OnClosed(bool fOK) override;

	// true for dialogs that receive full keyboard and mouse input even in shared mode
	bool IsExclusiveDialog() override { return true; }
};

// * Packets *

class C4PacketJoinData : public C4PacketBase
{
public:
	C4PacketJoinData() = default;

protected:

	// the client ID
	int32_t iClientID;

	// Dynamic data to boot
	C4Network2ResCore Dynamic;

	// network status
	C4Network2Status GameStatus;

	// control tick
	int32_t iStartCtrlTick;

public:

	// scenario parameter definitions (so the client can see them in the lobby)
	C4ScenarioParameterDefs ScenarioParameterDefs;

	// the game parameters
	C4GameParameters Parameters;

public:
	const int32_t          &getClientID()       const { return iClientID; }
	const C4Network2ResCore&getDynamicCore()    const { return Dynamic; }
	const C4Network2Status &getStatus()         const { return GameStatus; }
	int32_t                 getStartCtrlTick()  const { return iStartCtrlTick; }

	void SetClientID(int32_t inClientID) { iClientID = inClientID; }
	void SetGameStatus(const C4Network2Status &Status) { GameStatus = Status; }
	void SetDynamicCore(const C4Network2ResCore &Core) { Dynamic = Core; }
	void SetStartCtrlTick(int32_t iTick)               { iStartCtrlTick = iTick; }

	void CompileFunc(StdCompiler *pComp) override;
};

class C4PacketActivateReq : public C4PacketBase
{
public:
	C4PacketActivateReq(int32_t iTick = -1) : iTick(iTick) { }

protected:
	int32_t iTick;

public:
	int32_t getTick() const { return iTick; }

	void CompileFunc(StdCompiler *pComp) override;
};

#endif
