#ifndef __INC_METIN_II_GAME_DESC_H__
#define __INC_METIN_II_GAME_DESC_H__

#include "constants.h"
#include "input.h"
#include "SecureCipher.h"
#include "libthecore/ring_buffer.h"

//#define MAX_INPUT_LEN			2048
#define MAX_INPUT_LEN			65536

class CInputProcessor;

enum EDescType
{
	DESC_TYPE_ACCEPTOR,
	DESC_TYPE_CONNECTOR
};

class CLoginKey
{
	public:
		CLoginKey(DWORD dwKey, LPDESC pkDesc) : m_dwKey(dwKey), m_pkDesc(pkDesc)
		{
			m_dwExpireTime = 0;
		}

		void Expire()
		{
			m_dwExpireTime = get_dword_time();
			m_pkDesc = NULL;
		}

		operator DWORD() const
		{
			return m_dwKey;
		}

		DWORD   m_dwKey;
		DWORD   m_dwExpireTime;
		LPDESC  m_pkDesc;
};



class DESC
{
	public:
		EVENTINFO(desc_event_info)
		{
			LPDESC desc;

			desc_event_info()
			: desc(0)
			{
			}
		};

	public:
		DESC();
		virtual ~DESC();

		virtual BYTE		GetType() { return DESC_TYPE_ACCEPTOR; }
		virtual void		Destroy();
		virtual void		SetPhase(int _phase);

		void			FlushOutput();

		bool			Setup(LPFDWATCH _fdw, socket_t _fd, const struct sockaddr_in & c_rSockAddr, DWORD _handle);

		socket_t		GetSocket() const	{ return m_sock; }
		const char *	GetHostName()		{ return m_stHost.c_str(); }
		WORD			GetPort()	{ return m_wPort; }

		void			SetP2P(const char * h, WORD w, BYTE b) { m_stP2PHost = h; m_wP2PPort = w; m_bP2PChannel = b; }
		const char *	GetP2PHost()		{ return m_stP2PHost.c_str();	}
		WORD			GetP2PPort() const		{ return m_wP2PPort; }
		BYTE			GetP2PChannel() const	{ return m_bP2PChannel;	}

		void			BufferedPacket(const void * c_pvData, int iSize);
		void			Packet(const void * c_pvData, int iSize);
		void			LargePacket(const void * c_pvData, int iSize);

		int			ProcessInput();		// returns -1 if error
		int			ProcessOutput();	// returns -1 if error

		CInputProcessor	*	GetInputProcessor()	{ return m_pInputProcessor; }

		DWORD			GetHandle() const	{ return m_dwHandle; }
		size_t			GetOutputBufferSize() const	{ return m_outputBuffer.ReadableBytes(); }

		void			BindAccountTable(TAccountTable * pTable);
		TAccountTable &		GetAccountTable()	{ return m_accountTable; }

		void			BindCharacter(LPCHARACTER ch);
		LPCHARACTER		GetCharacter()		{ return m_lpCharacter; }

		bool			IsPhase(int phase) const	{ return m_iPhase == phase ? true : false; }
		int32_t				GetPhase() const { return m_iPhase; }

		const struct sockaddr_in & GetAddr()		{ return m_SockAddr;	}

		void			Log(const char * format, ...);

		DWORD			GetClientTime();

		void SendKeyChallenge();
		bool HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response);
		void SendKeyComplete();
		SecureCipher& GetSecureCipher() { return m_secureCipher; }
		const uint8_t* GetSessionToken() const { return m_secureCipher.GetSessionToken(); }
		bool IsSecureCipherActivated() const { return m_secureCipher.IsActivated(); }

		// 제국
		BYTE			GetEmpire();

		// for p2p
		void			SetRelay(const char * c_pszName);
		bool			DelayedDisconnect(int iSec);
		void			DisconnectOfSameLogin();

		void			SetAdminMode();
		bool			IsAdminMode();		// Handshake 에서 어드민 명령을 쓸수있나?

		void			SetPong(bool b);
		bool			IsPong();

		void			SendLoginSuccessPacket();
		//void			SendServerStatePacket(int nIndex);


		void			SetLoginKey(DWORD dwKey);
		void			SetLoginKey(CLoginKey * pkKey);
		DWORD			GetLoginKey();

		void			AssembleCRCMagicCube(BYTE bProcPiece, BYTE bFilePiece);

		void			SetClientVersion(const char * c_pszTimestamp) { m_stClientVersion = c_pszTimestamp; }
		const char *		GetClientVersion() { return m_stClientVersion.c_str(); }

		bool			isChannelStatusRequested() const { return m_bChannelStatusRequested; }
		void			SetChannelStatusRequested(bool bChannelStatusRequested) { m_bChannelStatusRequested = bChannelStatusRequested; }

		bool			IsExpiredHandshake() const;
		void			SetHandshakeTime(uint32_t handshake_time) { m_handshake_time = handshake_time; }

	protected:
		void			Initialize();

	protected:
		CInputProcessor *	m_pInputProcessor;
		CInputClose		m_inputClose;
		CInputHandshake	m_inputHandshake;
		CInputLogin		m_inputLogin;
		CInputMain		m_inputMain;
		CInputDead		m_inputDead;
		CInputAuth		m_inputAuth;


		LPFDWATCH		m_lpFdw;
		socket_t		m_sock;
		int				m_iPhase;
		DWORD			m_dwHandle;

		std::string		m_stHost;
		WORD			m_wPort;
		time_t			m_LastTryToConnectTime;

		RingBuffer		m_inputBuffer;

		DWORD			m_dwClientTime;

		RingBuffer		m_bufferedOutputBuffer;
		bool			m_hasBufferedOutput;
		RingBuffer		m_outputBuffer;

		LPEVENT			m_pkPingEvent;
		LPCHARACTER		m_lpCharacter;
		TAccountTable		m_accountTable;

		struct sockaddr_in	m_SockAddr;

		FILE *			m_pLogFile;
		std::string		m_stRelayName;

		std::string		m_stP2PHost;
		WORD			m_wP2PPort;
		BYTE			m_bP2PChannel;

		bool			m_bAdminMode; // Handshake 에서 어드민 명령을 쓸수있나?
		bool			m_bPong;

		CLoginKey *		m_pkLoginKey;
		DWORD			m_dwLoginKey;

		BYTE                    m_bCRCMagicCubeIdx;
		DWORD                   m_dwProcCRC;
		DWORD                   m_dwFileCRC;
		bool			m_bHackCRCQuery;

		std::string		m_stClientVersion;

		std::string		m_Login;
		int				m_outtime;
		int				m_playtime;
		int				m_offtime;

		bool			m_bDestroyed;
		bool			m_bChannelStatusRequested;

		uint32_t		m_handshake_time;
		SecureCipher	m_secureCipher;
		uint8_t			m_challenge[SecureCipher::CHALLENGE_SIZE];

	public:
		LPEVENT			m_pkDisconnectEvent;

	public:
		void SetLogin( const std::string & login ) { m_Login = login; }
		void SetLogin( const char * login ) { m_Login = login; }
		const std::string& GetLogin() { return m_Login; }

		void SetOutTime( int outtime ) { m_outtime = outtime; }
		void SetOffTime( int offtime ) { m_offtime = offtime; }
		void SetPlayTime( int playtime ) { m_playtime = playtime; }

		void RawPacket(const void * c_pvData, int iSize);
		void ChatPacket(BYTE type, const char * format, ...);

	public:
		struct PacketLogEntry
		{
			uint32_t seq;
			uint16_t header;
			uint16_t length;
		};

		static constexpr size_t PACKET_LOG_SIZE = 32;

		void		LogRecvPacket(uint16_t header, uint16_t length);
		void		LogSentPacket(uint16_t header, uint16_t length);
		void		DumpRecentPackets() const;
		uint32_t	GetRecvPacketSeq() const { return m_dwRecvPacketSeq; }
		uint32_t	GetSentPacketSeq() const { return m_dwSentPacketSeq; }

	private:
		PacketLogEntry	m_aRecentRecvPackets[PACKET_LOG_SIZE] = {};
		PacketLogEntry	m_aRecentSentPackets[PACKET_LOG_SIZE] = {};
		uint32_t		m_dwRecvPacketSeq = 0;
		uint32_t		m_dwSentPacketSeq = 0;
};

#endif
