#include "stdafx.h"
#include <random>
#include "MessageManager.h"
#include "GameServerConnection.h"
#include "HandShakeMessage.h"
#include "InputDataMessage.h"
#include "MovieDataMessage.h"
#include "GameInformationMessage.h"
#include "SaveStateMessage.h"
#include "Console.h"
#include "ControlManager.h"
#include "ClientConnectionData.h"
#include "EmulationSettings.h"
#include "StandardController.h"
#include "SelectControllerMessage.h"
#include "PlayerListMessage.h"
#include "GameServer.h"
#include "ForceDisconnectMessage.h"
#include "BaseControlDevice.h"
#include "ServerInformationMessage.h"

GameServerConnection* GameServerConnection::_netPlayDevices[BaseControlDevice::PortCount] = { };

GameServerConnection::GameServerConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, string serverPassword) : GameConnection(console, socket)
{
	//Server-side connection
	_serverPassword = serverPassword;
	_controllerPort = GameConnection::SpectatorPort;
	SendServerInformation();
}

GameServerConnection::~GameServerConnection()
{
	if(!_playerName.empty()) {
		MessageManager::DisplayMessage("NetPlay", _playerName + " (Player " + std::to_string(_controllerPort + 1) + ") disconnected.");
	}

	UnregisterNetPlayDevice(this);
}

void GameServerConnection::SendServerInformation()
{
	std::random_device rd;
	std::mt19937 engine(rd());
	std::uniform_int_distribution<> dist((int)' ', (int)'~');
	string hash(50, ' ');
	for(int i = 0; i < 50; i++) {
		int random = dist(engine);
		hash[i] = (char)random;
	}

	_connectionHash = hash;

	ServerInformationMessage message(hash);
	SendNetMessage(message);
}

void GameServerConnection::SendGameInformation()
{
	_console->Pause();
	RomInfo romInfo = _console->GetRomInfo();
	GameInformationMessage gameInfo(romInfo.RomName, romInfo.Hash.Crc32, _controllerPort, _console->GetSettings()->CheckFlag(EmulationFlags::Paused));
	SendNetMessage(gameInfo);
	SaveStateMessage saveState(_console);
	SendNetMessage(saveState);
	_console->Resume();
}

void GameServerConnection::SendMovieData(uint8_t port, ControlDeviceState state)
{
	if(_handshakeCompleted) {
		MovieDataMessage message(state, port);
		SendNetMessage(message);
	}
}

void GameServerConnection::SendForceDisconnectMessage(string disconnectMessage)
{
	ForceDisconnectMessage message(disconnectMessage);
	SendNetMessage(message);
	Disconnect();
}

void GameServerConnection::PushState(ControlDeviceState state)
{
	if(_inputData.size() == 0 || state != _inputData.back()) {
		_inputData.clear();
		_inputData.push_back(state);
	}
}

ControlDeviceState GameServerConnection::GetState()
{
	size_t inputBufferSize = _inputData.size();
	ControlDeviceState stateData;
	if(inputBufferSize > 0) {
		stateData = _inputData.front();
		if(inputBufferSize > 1) {
			//Always keep the last one the client sent, it will be used until a new one is received
			_inputData.pop_front();
		}
	}
	return stateData;
}

void GameServerConnection::ProcessHandshakeResponse(HandShakeMessage* message)
{
	//Send the game's current state to the client and register the controller
	if(message->IsValid()) {
		if(message->CheckPassword(_serverPassword, _connectionHash)) {
			_console->Pause();

			_controllerPort = message->IsSpectator() ? GameConnection::SpectatorPort : GetFirstFreeControllerPort();
			_playerName = message->GetPlayerName();

			string playerPortMessage = _controllerPort == GameConnection::SpectatorPort ? "Spectator" : "Player " + std::to_string(_controllerPort + 1);

			MessageManager::DisplayMessage("NetPlay", _playerName + " (" + playerPortMessage + ") connected.");

			if(_console->GetRomInfo().RomName.size() > 0) {
				SendGameInformation();
			}

			_handshakeCompleted = true;
			RegisterNetPlayDevice(this, _controllerPort);
			GameServer::SendPlayerList();
			_console->Resume();
		} else {
			SendForceDisconnectMessage("The password you provided did not match - you have been disconnected.");
		}
	} else {
		SendForceDisconnectMessage("Server is using a different version of Mesen (" + EmulationSettings::GetMesenVersionString() + ") - you have been disconnected.");
		MessageManager::DisplayMessage("NetPlay", + "NetplayVersionMismatch", message->GetPlayerName());
	}
}

void GameServerConnection::ProcessMessage(NetMessage* message)
{
	switch(message->GetType()) {
		case MessageType::HandShake:
			ProcessHandshakeResponse((HandShakeMessage*)message);
			break;

		case MessageType::InputData:
			if(!_handshakeCompleted) {
				SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
				return;
			}
			PushState(((InputDataMessage*)message)->GetInputState());
			break;

		case MessageType::SelectController:
			if(!_handshakeCompleted) {
				SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
				return;
			}
			SelectControllerPort(((SelectControllerMessage*)message)->GetPortNumber());
			break;

		default:
			break;
	}
}

void GameServerConnection::SelectControllerPort(uint8_t port)
{
	_console->Pause();
	if(port == GameConnection::SpectatorPort) {
		//Client wants to be a spectator, make sure we are not using any controller
		UnregisterNetPlayDevice(this);
		_controllerPort = port;
	} else {
		GameServerConnection* netPlayDevice = GetNetPlayDevice(port);
		if(netPlayDevice == this) {
			//Nothing to do, we're already this player
		} else if(netPlayDevice == nullptr) {
			//This port is free, we can switch
			UnregisterNetPlayDevice(this);
			RegisterNetPlayDevice(this, port);
			_controllerPort = port;
		} else {
			//Another player is using this port, we can't use it
		}
	}
	SendGameInformation();
	GameServer::SendPlayerList();
	_console->Resume();
}

void GameServerConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
{
	switch(type) {
		case ConsoleNotificationType::GamePaused:
		case ConsoleNotificationType::GameResumed:
		case ConsoleNotificationType::GameReset:
		case ConsoleNotificationType::StateLoaded:
		case ConsoleNotificationType::CheatAdded:
		case ConsoleNotificationType::ConfigChanged:
		case ConsoleNotificationType::GameInitCompleted:
			SendGameInformation();
			break;
		default:
			break;
	}
}

void GameServerConnection::RegisterNetPlayDevice(GameServerConnection* device, uint8_t port)
{
	GameServerConnection::_netPlayDevices[port] = device;
}

void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device)
{
	if(device != nullptr) {
		for(int i = 0; i < BaseControlDevice::PortCount; i++) {
			if(GameServerConnection::_netPlayDevices[i] == device) {
				GameServerConnection::_netPlayDevices[i] = nullptr;
				break;
			}
		}
	}
}

GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port)
{
	return GameServerConnection::_netPlayDevices[port];
}

uint8_t GameServerConnection::GetFirstFreeControllerPort()
{
	uint8_t hostPost = GameServer::GetHostControllerPort();
	for(int i = 0; i < BaseControlDevice::PortCount; i++) {
		if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) {
			return i;
		}
	}
	return GameConnection::SpectatorPort;
}

string GameServerConnection::GetPlayerName()
{
	return _playerName;
}

uint8_t GameServerConnection::GetControllerPort()
{
	return _controllerPort;
}