#pragma once


#include <iostream>
#include "Config.h"
#include <asio/ts/internet.hpp>
#include <asio.hpp>
#include <map>
#include <vector>
#include <thread>


namespace NetQuake {



	struct NetworkGenericPacket { // generic packet straight from server
		char type[2];
		char length[2];
		std::string remainder;
	};


	enum PacketType {
		NQ_CONTROL_PACKET = 0x8000,
		NQ_MESSAGE_BLOCK_CHUNK = 0x0001,
		NQ_MESSAGE_BLOCK_END = 0x0009,
		NQ_ACK = 0x0002,
		NQ_UNRELIABLE = 0x0010,
		NQ_UNKNOWN_PACKET
	};

	struct SerializedGenericPacket {
		PacketType type;
		int length;
		std::string remainder;
	};

	SerializedGenericPacket Serialize(NetworkGenericPacket ngp);


	enum ClientMessageType {
		CLIENT_MESSAGE_NOOP = 0x00,
		CLIENT_MESSAGE_KEEPALIVE = 0x01,
		CLIENT_MESSAGE_DISCONNECT = 0x02,
		CLIENT_MESSAGE_MOVEMENT = 0x03,
		CLIENT_MESSAGE_CONSOLE = 0x04,
		CLIENT_MESSAGE_UNKNOWN
	};

	struct ClientMessage {
		ClientMessageType type;
		std::string data;
	};

	struct ClientMessageBlock {
		std::vector<ClientMessage> messages;
	};

	struct GenericGamePacket {
		PacketType type;
		int length;
		long packetNumber;
		ClientMessageBlock messageBlock;
		bool valid = false;
	};
	GenericGamePacket toGenericGamePacket(SerializedGenericPacket sgp);

	enum DemoMessageType {
		DEM_BAD = 0x00,
		DEM_NOP = 0x01,
		DEM_DISCONNECT = 0x02,
		DEM_UPDATESTAT = 0x03,
		DEM_VERSION = 0x04,
		DEM_SETVIEW = 0x05,
		DEM_SOUND = 0x06,
		DEM_TIME = 0x07,
		DEM_PRINT = 0x08,
		DEM_STUFFTEXT = 0x09,
		DEM_SETANGLE = 0x0A,
		DEM_SERVERINFO = 0x0B,
		DEM_LIGHTSTYLE = 0x0C,
		DEM_UPDATENAME = 0x0D,
		DEM_UPDATEFRAGS = 0x0E,
		DEM_CLIENTDATA = 0x0F,
		DEM_STOPSOUND = 0x10,
		DEM_UPDATECOLORS = 0x11,
		DEM_PARTICLE = 0x12,
		DEM_DAMAGE = 0x13,
		DEM_SPAWNSTATIC = 0x14,
		DEM_SPAWNBINARY = 0x15,
		DEM_SPAWNBASELINE = 0x16,
		DEM_TEMP_ENTITY = 0x17,
		DEM_SETPAUSE = 0x18,
		DEM_SIGNONUM = 0x19,
		DEM_CENTERPRINT = 0x1A,
		DEM_KILLEDMONSTER = 0x1B,
		DEM_FOUNDSECRET = 0x1C,
		DEM_SPAWNSTATICSOUND = 0x1D,
		DEM_INTERMISSION = 0x1E,
		DEM_FINALE = 0x1F,
		DEM_CDTRACK = 0x20,
		DEM_SELLSCREEN = 0x21,
		DEM_CUTSCENE = 0x22,
		DEM_UPDATEENTITY = 0x80
	};

	class GenericServerMessage {
	public:
		DemoMessageType type;
		virtual std::string Serialize() = 0;
	};

	namespace ServerMessages {
		class Print : public GenericServerMessage { // 0x08
		public:
			std::string text = "";
			Print() { this->type = DEM_PRINT; }
			std::string Serialize();
		};
		class ServerInfo : public GenericServerMessage { // 0x0B
		public:
			long serverVersion = 15;
			long maxClients;
			long multi;
			std::string mapName;
			std::vector<std::string> precacheModels;
			long numModels;
			std::vector<std::string> precacheSounds;
			long numSounds;
			ServerInfo() { 
				this->type = DEM_SERVERINFO;
				precacheModels = std::vector<std::string>(256);
				precacheSounds = std::vector<std::string>(256);
			}

			


			std::string Serialize();
		};

		class SignOn : public GenericServerMessage {
		public:
			long signon;
			SignOn() { type = DEM_SIGNONUM; }
			std::string Serialize();
		};
	}

	class ServerMessageBlock{
	public:
		PacketType type;
		int order;
		std::vector<GenericServerMessage*> serverMessages;
		std::string Serialize();
	};

	

	enum Control_OpCode {
		CONTROL_CONNECTION_REQUEST = 0x01,
		CONTROL_SERVER_INFO_REQUEST = 0x02,
		CONTROL_PLAYER_INFO_REQUEST = 0x03,
		CONTROL_RULE_INFO_REQUEST = 0x04,
		CONTROL_SERVER_INFO_REPLY = 0x83
	};

	struct GenericControlPacket {
		Control_OpCode opcode;
		std::string remainder;
		GenericControlPacket(SerializedGenericPacket sgp);
	};
	
	enum NetProtocolVersion {
		QTEST1 = 0x01,
		QUNKNOWN = 0x02,
		QGOLD = 0x03
	};

	struct ServerInfoRequest {
		std::string gameName;
		NetProtocolVersion netVersion;
		ServerInfoRequest(GenericControlPacket gcp);
	};

	struct Address {
		std::string ip;
		int port;
	};

	struct ServerInfoReply {
		Address address;
		std::string hostname;
		std::string levelname;
		char currentPlayers;
		char maxPlayers;
		NetProtocolVersion netVersion;
	};

	std::string Serialize(ServerInfoReply sirp);

	struct ConnectionRequest {
		std::string gameName;
		NetProtocolVersion netVersion;
		ConnectionRequest(GenericControlPacket gcp);
	};

	struct ConnectionRequestReply {
		bool reject;
		short port; // LITTLE-ENDIAN (!)(!)(!)(!)(!)
		char unknown[2]; // [ have fun :) ]

		std::string reason;
	};

	::std::string Serialize(ConnectionRequestReply crr);

	class GameEndpoint {
		std::thread internalThread;
	public:
		GameEndpoint();
		GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);

		~GameEndpoint();
	};

	namespace Internal {
		
		struct NewEndpointRequestReply {
			bool ready = false;
			bool error = false;
			std::string reason;
			int port;
			asio::ip::udp::endpoint startRemote;
		};
		
		class InternalRequestConsole {
			std::vector<NewEndpointRequestReply> newEndpointRequests;
			int lastEndpointRequestTreated = 0;
		public:
			InternalRequestConsole();
			
			int newEndpointRequest(asio::ip::udp::endpoint ep);
			NewEndpointRequestReply getEndpointRequest(int id);
			void Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector);
			~InternalRequestConsole();
		};
	}


	std::string AcknowledgePacket(int packet);


	void NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning);
	void NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);
	

}