#include "main.h"
#include "TGraalPlayer.h"
#include "TServerList.h"

/*
	Create Packet-Functions
*/
bool TGraalPlayer::IsCreated = false;
typedef bool (TGraalPlayer::*P2F)(CString&);
std::vector<P2F> functions(255, &TGraalPlayer::msgPLI_NULL);

void TGraalPlayer::CreateFunctions()
{
	if (TGraalPlayer::IsCreated)
		return;

	functions[PLI_PRE22]			= &TGraalPlayer::msgPLI_PRE22;
	functions[PLI_LOGIN]			= &TGraalPlayer::msgPLI_LOGIN;
	functions[PLI_VER22]			= &TGraalPlayer::msgPLI_VER22;
//	functions[PLI_V2SERVERLISTRC]	= &TGraalPlayer::msgPLI_V2SERVERLISTRC;
	functions[PLI_POST22]			= &TGraalPlayer::msgPLI_POST22;
//	functions[PLI_GRSECURELOGIN]	= &TGraalPlayer::msgPLI_GRSECURELOGIN;

	// Admin Functions
	functions[PLI_ADMINLOGIN]		= &TGraalPlayer::msgPLI_ADMINLOGIN;

	// Created
	TGraalPlayer::IsCreated = true;
}

TGraalPlayer::TGraalPlayer(TServerList *pServerList, CSocket *pSocket)
: mPlayerType(-1), mServerList(pServerList)
{
	if (!TGraalPlayer::IsCreated)
		TGraalPlayer::CreateFunctions();
	mSocket = pSocket;
}

TGraalPlayer::~TGraalPlayer()
{
	printf("REMOVED TGRAALPLAYER\n");
	delete mSocket;
}

/*
	Socket Functions
*/
bool TGraalPlayer::onSend()
{
	// Socket Disconnected?
	if (mSocket == 0 || mSocket->getState() == SOCKET_STATE_DISCONNECTED)
		return false;
	
	// Socket Send
	SendCompress();
	return true;
}

bool TGraalPlayer::onRecv()
{
	// Socket Disconnected?
	if (mSocket == 0 || mSocket->getState() == SOCKET_STATE_DISCONNECTED)
		return false;
	
	// Socket Receive?
	unsigned int size = 0;
	char *data = mSocket->getData(&size);
	if (size != 0)
		mDataIn.write(data, size);
	else if (mSocket->getState() == SOCKET_STATE_DISCONNECTED)
		return false;

	printf("data: %s\n", data);

	// Parse Received Data
	CString unBuffer;
	mDataIn.setRead(0);
	while (mDataIn.length() > 1)
	{
		// Packet Length
		unsigned short len = (unsigned short)mDataIn.readShort();
		if ((unsigned int)len > (unsigned int)mDataIn.length() - 2)
			break;

		// Grab Packet
		unBuffer = mDataIn.readChars(len);
		mDataIn.removeI(0, len+2);

		// decrypt packet
		switch (mCodecIn.getGen())
		{
			case ENCRYPT_GEN_1:		// Gen 1 is not encrypted or compressed.
				break;

			// Gen 2 and 3 are zlib compressed.  Gen 3 encrypts individual packets
			// Uncompress so we can properly decrypt later on.
			case ENCRYPT_GEN_2:
			case ENCRYPT_GEN_3:
				unBuffer.zuncompressI();
				break;

			// Gen 4 and up encrypt the whole combined and compressed packet.
			// Decrypt and decompress.
			default:
				DecryptPacket(unBuffer);
				break;
		}

		// Well theres your buffer
		if (!ParsePacket(unBuffer))
			return false;
	}

	return true;
}

void TGraalPlayer::onUnregister()
{
	// Clear Outgoing Data
	this->SendCompress();

	mServerList->RemoveSocket((CSocketStub *)this, SOCK_PLAYER);
}

unsigned int TGraalPlayer::send(CString pBuffer)
{
	unsigned int len = pBuffer.length();
	mSocket->sendData(pBuffer.text(), &len);
	return len;
}

/*
	Packet-Manipulation
*/
void TGraalPlayer::DecryptPacket(CString& pPacket)
{
	// Version 1.41 - 2.18 encryption
	// Was already decompressed so just decrypt the packet.
	if (mCodecIn.getGen() == ENCRYPT_GEN_3)
	{
		//if (!isClient())
		//	return;

		mCodecIn.decrypt(pPacket);
	}

	// Version 2.19+ encryption.
	// Encryption happens before compression and depends on the compression used so
	// first decrypt and then decompress.
	if (mCodecIn.getGen() == ENCRYPT_GEN_4)
	{
		// Decrypt the packet.
		mCodecIn.limitFromType(COMPRESS_BZ2);
		mCodecIn.decrypt(pPacket);

		// Uncompress packet.
		pPacket.bzuncompressI();
	}
	else if (mCodecIn.getGen() >= ENCRYPT_GEN_5)
	{
		// Find the compression type and remove it.
		int pType = pPacket.readChar();
		pPacket.removeI(0, 1);

		// Decrypt the packet.
		mCodecIn.limitFromType(pType);		// Encryption is partially related to compression.
		mCodecIn.decrypt(pPacket);

		// Uncompress packet
		if (pType == COMPRESS_ZLIB)
			pPacket.zuncompressI();
		else if (pType == COMPRESS_BZ2)
			pPacket.bzuncompressI();
	}
}

bool TGraalPlayer::ParsePacket(CString& pPacket)
{
	while (pPacket.bytesLeft() > 0)
	{
		// read id & packet
		CString str = pPacket.readString("\n");
		int id = str.readGUChar();

		// check lengths
		if (str.length() < 0) //  || id >= (int)plfunc.size()
			continue;

		// valid packet, call function
		int oldRead = str.readPos();
		msgPLI_NULL(str);
		str.setRead(oldRead);
		if (!(*this.*functions[id])(str))
			return false;
	}

	return true;
}

void TGraalPlayer::SendCompress()
{
	// Empty buffer, return.
	if (mDataOut.isEmpty())
		return;

	// Encrypt Packet
	switch (mCodecOut.getGen())
	{
		case ENCRYPT_GEN_1:
			break;

		case ENCRYPT_GEN_2:
		case ENCRYPT_GEN_3:
			mDataOut.zcompressI();
			break;

		case ENCRYPT_GEN_4:
			mCodecOut.limitFromType(COMPRESS_BZ2);
			mDataOut = mCodecOut.encrypt(mDataOut.bzcompressI());
			break;

		case ENCRYPT_GEN_5:
		{
			// Choose which compression to use and apply it.
			int compressionType = COMPRESS_UNCOMPRESSED;
			if (mDataOut.length() > 0x2000)	// 8KB
			{
				compressionType = COMPRESS_BZ2;
				mDataOut.bzcompressI();
			}
			else if (mDataOut.length() > 40)
			{
				compressionType = COMPRESS_ZLIB;
				mDataOut.zcompressI();
			}

			// Encrypt the Packet
			mCodecOut.limitFromType(compressionType);
			mDataOut = CString() << (char)compressionType << mCodecOut.encrypt(mDataOut);
			break;
		}
	}

	// Compress Buffer
	send(CString() << (short)mDataOut.length() << mDataOut);
	mDataOut.clear();
}

void TGraalPlayer::SendPacket(CString pPacket, bool pFlush)
{
	// Empty buffer, return.
	if (pPacket.isEmpty())
		return;

	// Append 'newline' character
	if (pPacket[pPacket.length()-1] != '\n')
		pPacket.writeChar('\n');
	
	// Append buffer
	mDataOut.write(pPacket);

	// Check Buffer & Send
	if (mDataOut.length() > 4096 || pFlush)
		this->SendCompress();
}

void TGraalPlayer::SetCodec(int pEncryption, int pKey)
{
	mCodecIn.setGen(pEncryption);
	mCodecOut.setGen(pEncryption);
	mCodecIn.reset(pKey);
	mCodecOut.reset(pKey);
}

/*
	Packet-Functions
*/
bool TGraalPlayer::msgPLI_NULL(CString& pPacket)
{
	pPacket.setRead(0);
	printf("Unknown Player Packet: %i (%s)\n", pPacket.readGUChar(), pPacket.text()+1);
	for (int i = 0; i < pPacket.length(); ++i) printf("%02x ", (unsigned char)((pPacket.text())[i])); printf("\n");
	return true;
}

bool TGraalPlayer::msgPLI_PRE22(CString& pPacket)
{
	// Account Type
	CString accountType = pPacket.readString(""); // `newmain` or `rc` for the most part..
	if (accountType == "newmain")
		mPlayerType = PLTYPE_NEWMAIN1;
	else if (accountType == "rc")
		mPlayerType = PLTYPE_CONTROL1;
	else return false;
	
	this->SetCodec(ENCRYPT_GEN_2);
	return true;
}

bool TGraalPlayer::msgPLI_LOGIN(CString& pPacket)
{
	// definitions
	CString account, password;
	int res;

	// read data
	account = pPacket.readChars(pPacket.readGUChar());
	password = pPacket.readChars(pPacket.readGUChar());
	
	// verify account
	res = mServerList->VerifyAccount(account, password);

	if (res == ACCSTAT_NONREG)
	{
		if (account.find("create_") == 0)
		{
			account = account.subString(7);
//			res = mServerList->CreateAccount(account, password)
		}
	}

	switch (res)
	{
		case ACCSTAT_NORMAL:
			if (mServerList->GetServerCount() > 0)
				SendPacket(CString() >> (char)PLO_SVRLIST << mServerList->GetServerList(mPlayerType, mSocket->getRemoteIp()), true);
			SendPacket(CString() >> (char)PLO_STATUS << "Welcome to " << mServerList->GetSettings()->getStr("name") << ", " << account << "." << "\r" << "There are " << CString(mServerList->GetServerCount()) << " server(s) online.");
			SendPacket(CString() >> (char)PLO_SITEURL << mServerList->GetSettings()->getStr("url"));
			SendPacket(CString() >> (char)PLO_UPGURL << mServerList->GetSettings()->getStr("donateUrl"));
			return true;

		default:
			SendPacket(CString() >> (char)PLO_ERROR << GetAccountError(res));
			return false;
	}

	return true;
}

bool TGraalPlayer::msgPLI_VER22(CString& pPacket)
{
	int key = pPacket.readGChar();
	this->SetCodec(ENCRYPT_GEN_4, key);

	// Read Graal Version
	mGraalVersion = pPacket.readChars(8);

	// Account Type
	CString accountType = pPacket.readString(""); // `newmain` or `rc` for the most part..
	if (accountType == "newmain")
		mPlayerType = PLTYPE_NEWMAIN2;
	else if (accountType == "rc")
		mPlayerType = PLTYPE_CONTROL2;
	else return false;

	return true;
}

bool TGraalPlayer::msgPLI_POST22(CString& pPacket)
{
	int key = pPacket.readGChar();
	this->SetCodec(ENCRYPT_GEN_5, key);
	
	// Read Graal Version
	mGraalVersion = pPacket.readChars(8);

	// Account Type
	CString accountType = pPacket.readString(""); // `newmain` or `rc` for the most part..
	if (accountType == "newmain")
		mPlayerType = PLTYPE_NEWMAIN2;
	else if (accountType == "rc")
		mPlayerType = PLTYPE_CONTROL2;
	else return false;

	return true;
}

bool TGraalPlayer::msgPLI_ADMINLOGIN(CString& pPacket)
{
	// Account Type
	mPlayerType = PLTYPE_ADMNCTRL;
	
	// Set Codec Information
	int key = pPacket.readGChar();
	this->SetCodec(ENCRYPT_GEN_5, key);
	
	// Read Version
	mGraalVersion = pPacket.readChars(8);

	// definitions
	CString account, password;
	int res;

	// read data
	account = pPacket.readChars(pPacket.readGUChar());
	password = pPacket.readChars(pPacket.readGUChar());
	
	// verify account
	res = mServerList->VerifyAccount(account, password);
	switch (res)
	{
		case ACCSTAT_NORMAL:
			return true;

		default:
			SendPacket(CString() >> (char)PLO_ERROR << GetAccountError(res));
			return false;
	}

	return true;
}
