#pragma once
#include "shared/keyvalues.h"

// Cbuf
enum class ECommandTarget_t
{
	CBUF_FIRST_PLAYER = 0,
	CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2
	CBUF_SERVER = CBUF_LAST_PLAYER + 1,

	CBUF_COUNT,
};

enum class cmd_source_t
{
	// Added to the console buffer by gameplay code.  Generally unrestricted.
	kCommandSrcCode,

	// Sent from code via engine->ClientCmd, which is restricted to commands visible
	// via FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS.
	kCommandSrcClientCmd,

	// Typed in at the console or via a user key-bind.  Generally unrestricted, although
	// the client will throttle commands sent to the server this way to 16 per second.
	kCommandSrcUserInput,

	// Came in over a net connection as a clc_stringcmd
	// host_client will be valid during this state.
	//
	// Restricted to FCVAR_GAMEDLL commands (but not convars) and special non-ConCommand
	// server commands hardcoded into gameplay code (e.g. "joingame")
	kCommandSrcNetClient,

	// Received from the server as the client
	//
	// Restricted to commands with FCVAR_SERVER_CAN_EXECUTE
	kCommandSrcNetServer,

	// Being played back from a demo file
	//
	// Not currently restricted by convar flag, but some commands manually ignore calls
	// from this source.  FIXME: Should be heavily restricted as demo commands can come
	// from untrusted sources.
	kCommandSrcDemoFile,

	// Invalid value used when cleared
	kCommandSrcInvalid = -1
};

typedef ECommandTarget_t (*Cbuf_GetCurrentPlayerType)();
extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer;

typedef void (*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source);
extern Cbuf_AddTextType Cbuf_AddText;

typedef void (*Cbuf_ExecuteType)();
extern Cbuf_ExecuteType Cbuf_Execute;

extern bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, cmd_source_t commandSource);

// CEngine

enum EngineQuitState
{
	QUIT_NOTQUITTING = 0,
	QUIT_TODESKTOP,
	QUIT_RESTART
};

enum class EngineState_t
{
	DLL_INACTIVE = 0, // no dll
	DLL_ACTIVE, // engine is focused
	DLL_CLOSE, // closing down dll
	DLL_RESTART, // engine is shutting down but will restart right away
	DLL_PAUSED, // engine is paused, can become active from this state
};

class CEngine
{
public:
	virtual void unknown() = 0; // unsure if this is where
	virtual bool Load(bool dedicated, const char* baseDir) = 0;
	virtual void Unload() = 0;
	virtual void SetNextState(EngineState_t iNextState) = 0;
	virtual EngineState_t GetState() = 0;
	virtual void Frame() = 0;
	virtual double GetFrameTime() = 0;
	virtual float GetCurTime() = 0;

	EngineQuitState m_nQuitting;
	EngineState_t m_nDllState;
	EngineState_t m_nNextDllState;
	double m_flCurrentTime;
	float m_flFrameTime;
	double m_flPreviousTime;
	float m_flFilteredTime;
	float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited.
};

extern CEngine* g_pEngine;

extern void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...);

#pragma once
typedef enum
{
	NA_NULL = 0,
	NA_LOOPBACK,
	NA_IP,
} netadrtype_t;

#pragma pack(push, 1)
typedef struct netadr_s
{
	netadrtype_t type;
	unsigned char ip[16]; // IPv6
	// IPv4's 127.0.0.1 is [::ffff:127.0.0.1], that is:
	// 00 00 00 00 00 00 00 00    00 00 FF FF 7F 00 00 01
	unsigned short port;
} netadr_t;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct netpacket_s
{
	netadr_t adr; // sender address
	// int				source;		// received source
	char unk[10];
	double received_time;
	unsigned char* data; // pointer to raw packet data
	void* message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer)
	char unk2[16];
	int size;

	// bf_read			message;	// easy bitbuf data access // 'inpacket.message' etc etc (pointer)
	// int				size;		// size in bytes
	// int				wiresize;   // size in bytes before decompression
	// bool			stream;		// was send as stream
	// struct netpacket_s* pNext;	// for internal use, should be NULL in public
} netpacket_t;
#pragma pack(pop)

// #56169 $DB69 PData size
// #512   $200	Trailing data
// #100	  $64	Safety buffer
const int PERSISTENCE_MAX_SIZE = 0xDDCD;

// note: NOT_READY and READY are the only entries we have here that are defined by the vanilla game
// entries after this are custom and used to determine the source of persistence, e.g. whether it is local or remote
enum class ePersistenceReady : char
{
	NOT_READY,
	READY = 3,
	READY_INSECURE = 3,
	READY_REMOTE
};

enum class eSignonState : int
{
	NONE = 0, // no state yet; about to connect
	CHALLENGE = 1, // client challenging server; all OOB packets
	CONNECTED = 2, // client is connected to server; netchans ready
	NEW = 3, // just got serverinfo and string tables
	PRESPAWN = 4, // received signon buffers
	GETTINGDATA = 5, // respawn-defined signonstate, assumedly this is for persistence
	SPAWN = 6, // ready to receive entity packets
	FIRSTSNAP = 7, // another respawn-defined one
	FULL = 8, // we are fully connected; first non-delta packet received
	CHANGELEVEL = 9, // server is changing level; please wait
};

class CBaseClient
{
public:
	char _unk_0x0[22]; // 0x0 ( Size: 22 )
	char m_Name[64]; // 0x16 ( Size: 64 )
	char _unk_0x56[514]; // 0x56 ( Size: 514 )
	KeyValues* m_ConVars; // 0x258 ( Size: 8 )
	char _unk_0x260[64]; // 0x260 ( Size: 64 )
	eSignonState m_Signon; // 0x2a0 ( Size: 4 )
	int32_t m_nDeltaTick; // 0x2a4 ( Size: 4 )
	uint64_t m_nOriginID; // 0x2a8 ( Size: 8 )
	int32_t m_nStringTableAckTick; // 0x2b0 ( Size: 4 )
	int32_t m_nSignonTick; // 0x2b4 ( Size: 4 )
	char _unk_0x2b8[160]; // 0x2b8 ( Size: 180 )
	char m_ClanTag[16]; // 0x358 ( Size: 16 )
	char _unk_0x368[284]; // 0x368 ( Size: 284 )
	bool m_bFakePlayer; // 0x484 ( Size: 1 )
	bool m_bReceivedPacket; // 0x485 ( Size: 1 )
	bool m_bLowViolence; // 0x486 ( Size: 1 )
	bool m_bFullyAuthenticated; // 0x487 ( Size: 1 )
	char _unk_0x488[24]; // 0x488 ( Size: 24 )
	ePersistenceReady m_iPersistenceReady; // 0x4a0 ( Size: 1 )
	char _unk_0x4a1[89]; // 0x4a1 ( Size: 89 )
	char m_PersistenceBuffer[PERSISTENCE_MAX_SIZE]; // 0x4fa ( Size: 56781 )
	char _unk_0xe2c7[4665]; // 0xe2c7 ( Size: 4665 )
	char m_UID[32]; // 0xf500 ( Size: 32 )
	char _unk_0xf520[123400]; // 0xf520 ( Size: 123400 )
};
static_assert(sizeof(CBaseClient) == 0x2D728);
static_assert(offsetof(CBaseClient, m_Name) == 0x16);
static_assert(offsetof(CBaseClient, m_ConVars) == 0x258);
static_assert(offsetof(CBaseClient, m_Signon) == 0x2A0);
static_assert(offsetof(CBaseClient, m_nDeltaTick) == 0x2A4);
static_assert(offsetof(CBaseClient, m_nOriginID) == 0x2A8);
static_assert(offsetof(CBaseClient, m_nStringTableAckTick) == 0x2B0);
static_assert(offsetof(CBaseClient, m_nSignonTick) == 0x2B4);
static_assert(offsetof(CBaseClient, m_ClanTag) == 0x358);
static_assert(offsetof(CBaseClient, m_bFakePlayer) == 0x484);
static_assert(offsetof(CBaseClient, m_bReceivedPacket) == 0x485);
static_assert(offsetof(CBaseClient, m_bLowViolence) == 0x486);
static_assert(offsetof(CBaseClient, m_bFullyAuthenticated) == 0x487);
static_assert(offsetof(CBaseClient, m_iPersistenceReady) == 0x4A0);
static_assert(offsetof(CBaseClient, m_PersistenceBuffer) == 0x4FA);
static_assert(offsetof(CBaseClient, m_UID) == 0xF500);

extern CBaseClient* g_pClientArray;

enum server_state_t
{
	ss_dead = 0, // Dead
	ss_loading, // Spawning
	ss_active, // Running
	ss_paused, // Running, but paused
};

extern server_state_t* g_pServerState;

extern char* g_pModName;

enum class GameMode_t : int
{
	NO_MODE = 0,
	MP_MODE,
	SP_MODE,
};

class CGlobalVars
{
public:
	// Absolute time (per frame still - Use Plat_FloatTime() for a high precision real time
	//  perf clock, but not that it doesn't obey host_timescale/host_framerate)
	double m_flRealTime; // 0x0 ( Size: 8 )

	// Absolute frame counter - continues to increase even if game is paused
	int m_nFrameCount; // 0x8 ( Size: 4 )

	// Non-paused frametime
	float m_flAbsoluteFrameTime; // 0xc ( Size: 4 )

	// Current time
	//
	// On the client, this (along with tickcount) takes a different meaning based on what
	// piece of code you're in:
	//
	//   - While receiving network packets (like in PreDataUpdate/PostDataUpdate and proxies),
	//     this is set to the SERVER TICKCOUNT for that packet. There is no interval between
	//     the server ticks.
	//     [server_current_Tick * tick_interval]
	//
	//   - While rendering, this is the exact client clock
	//     [client_current_tick * tick_interval + interpolation_amount]
	//
	//   - During prediction, this is based on the client's current tick:
	//     [client_current_tick * tick_interval]
	float m_flCurTime; // 0x10 ( Size: 4 )

	char _unk_0x14[28]; // 0x14 ( Size: 28 )

	// Time spent on last server or client frame (has nothing to do with think intervals)
	float m_flFrameTime; // 0x30 ( Size: 4 )

	// current maxplayers setting
	int m_nMaxClients; // 0x34 ( Size: 4 )
	GameMode_t m_nGameMode; // 0x38 ( Size: 4 )

	// Simulation ticks - does not increase when game is paused
	//   this is weird and doesn't seem to increase once per frame?
	uint32_t m_nTickCount; // 0x3c ( Size: 4 )

	// Simulation tick interval
	float m_flTickInterval; // 0x40 ( Size: 4 )
	char _unk_0x44[28]; // 0x44 ( Size: 28 )

	const char* m_pMapName; // 0x60 ( Size: 8 )
	int m_nMapVersion; // 0x68 ( Size: 4 )
};
static_assert(offsetof(CGlobalVars, m_flRealTime) == 0x0);
static_assert(offsetof(CGlobalVars, m_nFrameCount) == 0x8);
static_assert(offsetof(CGlobalVars, m_flAbsoluteFrameTime) == 0xc);
static_assert(offsetof(CGlobalVars, m_flCurTime) == 0x10);
static_assert(offsetof(CGlobalVars, m_flFrameTime) == 0x30);
static_assert(offsetof(CGlobalVars, m_nMaxClients) == 0x34);
static_assert(offsetof(CGlobalVars, m_nGameMode) == 0x38);
static_assert(offsetof(CGlobalVars, m_nTickCount) == 0x3c);
static_assert(offsetof(CGlobalVars, m_flTickInterval) == 0x40);
static_assert(offsetof(CGlobalVars, m_pMapName) == 0x60);
static_assert(offsetof(CGlobalVars, m_nMapVersion) == 0x68);

extern CGlobalVars* g_pGlobals;