/*
 *  Original work: Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 *
 *  Modified work: Copyright (c) 2016-2020, SLikeSoft UG (haftungsbeschränkt)
 *
 *  This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
 *  license found in the license.txt file in the root directory of this source tree.
 */

/// \file
/// \brief This file is a sample for using HTTPConnection and PHPDirectoryServer2


#include "slikenet/TCPInterface.h"
#include "slikenet/HTTPConnection.h"
#include "PHPDirectoryServer2.h"
#include "slikenet/sleep.h"
#include "slikenet/string.h"
#include "slikenet/GetTime.h"
#include "slikenet/DS_Table.h"
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include "slikenet/Gets.h"
#include "slikenet/Getche.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"

using namespace SLNet;

// Allocate rather than create on the stack or the RakString mutex crashes on shutdown
TCPInterface *tcp;
HTTPConnection *httpConnection;
PHPDirectoryServer2 *phpDirectoryServer2;

enum ReadResultEnum
{
	RR_EMPTY_TABLE,
	RR_READ_TABLE,
	RR_TIMEOUT,
};

ReadResultEnum ReadResult(SLNet::RakString &httpResult)
{
	SLNet::TimeMS endTime= SLNet::GetTimeMS()+10000;
	httpResult.Clear();
	while (SLNet::GetTimeMS()<endTime)
	{
		Packet *packet = tcp->Receive();
		if(packet)
		{
			httpConnection->ProcessTCPPacket(packet);
			tcp->DeallocatePacket(packet);
		}

		if (httpConnection->HasRead())
		{
			httpResult = httpConnection->Read();
			// Good response, let the PHPDirectoryServer2 class handle the data
			// If resultCode is not an empty string, then we got something other than a table
			// (such as delete row success notification, or the message is for HTTP only and not for this class).
			HTTPReadResult readResult = phpDirectoryServer2->ProcessHTTPRead(httpResult);

			if (readResult==HTTP_RESULT_GOT_TABLE)
			{
				//printf("RR_READ_TABLE\n");
				return RR_READ_TABLE;
			}
			else if (readResult==HTTP_RESULT_EMPTY)
			{
				//printf("HTTP_RESULT_EMPTY\n");
				return RR_EMPTY_TABLE;
			}
		}

		// Update our two classes so they can do time-based updates
		httpConnection->Update();
		phpDirectoryServer2->Update();

		// Prevent 100% cpu usage
		RakSleep(30);
	}

	return RR_TIMEOUT;
}

bool HaltOnUnexpectedResult(ReadResultEnum result, ReadResultEnum expected)
{
	if (result!=expected)
	{
		printf("TEST FAILED. Expected ");

		switch (expected)
		{

		case RR_EMPTY_TABLE:
			printf("no results");
			break;
		case RR_TIMEOUT:
			printf("timeout");
			break;
		case RR_READ_TABLE:
			printf("to download result");
			break;
		}
		
		switch (result)
		{
		case RR_EMPTY_TABLE:
			printf(". No results were downloaded");
			break;
		case RR_READ_TABLE:
			printf(". Got a result");
			break;
		case RR_TIMEOUT:
			printf(". Timeout");
			break;
		}
		printf("\n");
		return true;
	}

	return false;
}

void DownloadTable()
{
	phpDirectoryServer2->DownloadTable("a");
}
void UploadTable(SLNet::RakString gameName, unsigned short gamePort)
{
	phpDirectoryServer2->UploadTable("a", gameName, gamePort, false);
}
void UploadAndDownloadTable(SLNet::RakString gameName, unsigned short gamePort)
{
	phpDirectoryServer2->UploadAndDownloadTable("a", "a", gameName, gamePort, false);
}
bool PassTestOnEmptyDownloadedTable()
{
	const DataStructures::Table *games = phpDirectoryServer2->GetLastDownloadedTable();

	if (games->GetRowCount()==0)
	{
		printf("Test passed.\n");
		return true;
	}
	printf("TEST FAILED. Empty table should have been downloaded.\n");
	return false;
}
bool VerifyDownloadMatchesUpload(int requiredRowCount, int testRowIndex)
{
	const DataStructures::Table *games = phpDirectoryServer2->GetLastDownloadedTable();
	if (games->GetRowCount()!=(unsigned int)requiredRowCount)
	{
		printf("TEST FAILED. Expected %i result rows, got %i\n", requiredRowCount, games->GetRowCount());
		return false;
	}
	SLNet::RakString columnName;
	SLNet::RakString value;
	unsigned int i;
	DataStructures::Table::Row *row = games->GetRowByIndex(testRowIndex, nullptr);
	const DataStructures::List<DataStructures::Table::ColumnDescriptor>& columns = games->GetColumns();
	unsigned int colIndex;
	// +4 comes from automatic fields
	// _GAME_PORT
	// _GAME_NAME
	// _SYSTEM_ADDRESS
	// __SEC_AFTER_EPOCH_SINCE_LAST_UPDATE
	if (phpDirectoryServer2->GetFieldCount()+4!=games->GetColumnCount())
	{
		printf("TEST FAILED. Expected %i columns, got %i\n", phpDirectoryServer2->GetFieldCount()+4, games->GetColumnCount());
		printf("Expected columns:\n");
		for (colIndex=0; colIndex < phpDirectoryServer2->GetFieldCount(); colIndex++)
		{
			phpDirectoryServer2->GetField(colIndex, columnName, value);
			printf("%i. %s\n", colIndex+1, columnName.C_String());
		}
		printf("%i. _GAME_PORT\n", colIndex++);
		printf("%i. _GAME_NAME\n", colIndex++);
		printf("%i. _System_Address\n", colIndex++);
		printf("%i. __SEC_AFTER_EPOCH_SINCE_LAST_UPDATE\n", colIndex++);

		printf("Got columns:\n");
		for (colIndex=0; colIndex < columns.Size(); colIndex++)
		{
			printf("%i. %s\n", colIndex+1, columns[colIndex].columnName);
		}

		return false;
	}
	for (i=0; i < phpDirectoryServer2->GetFieldCount(); i++)
	{
		phpDirectoryServer2->GetField(i, columnName, value);
		for (colIndex=0; colIndex < columns.Size(); colIndex++)
		{
			if (strcmp(columnName.C_String(), columns[colIndex].columnName)==0)
				break;
		}
		if (colIndex==columns.Size())
		{
			printf("TEST FAILED. Expected column with name %s\n", columnName.C_String());
			return false;
		}

		if (strcmp(value.C_String(), row->cells[colIndex]->c)!=0)
		{
			printf("TEST FAILED. Expected row with value '%s' at index %i for column %s. Got '%s'.\n", value.C_String(), i, columnName.C_String(), row->cells[colIndex]->c);
			return false;
		}
	}

	printf("Test passed.\n");
	return true;
}
void PrintHttpResult(SLNet::RakString httpResult)
{
	printf("--- Last result read ---\n");
	printf("%s", httpResult.C_String());
}
void PrintFieldColumns(void)
{
	unsigned int colIndex;
	SLNet::RakString columnName;
	SLNet::RakString value;
	for (colIndex=0; colIndex < phpDirectoryServer2->GetFieldCount(); colIndex++)
	{
		phpDirectoryServer2->GetField(colIndex, columnName, value);
		printf("%i. %s\n", colIndex+1, columnName.C_String());
	}
}
bool RunTest()
{
	SLNet::RakString httpResult;
	ReadResultEnum rr;
	char ch[32];
	printf("Warning, table must be clear before starting the test.\n");
	printf("Press enter to start\n");
	Gets(ch,sizeof(ch));

	printf("*** Testing initial table is empty.\n");
	// Table should start emptyF
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Downloading again, to ensure download does not modify the table.\n");
	// Downloading should not modify the table
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing upload.\n");
	// Upload values likely to mess up PHP
	phpDirectoryServer2->SetField("TestField1","0");
	phpDirectoryServer2->SetField("TestField2","");
	phpDirectoryServer2->SetField("TestField3"," ");
	phpDirectoryServer2->SetField("TestField4","!@#$%^&*(");
	phpDirectoryServer2->SetField("TestField5","A somewhat big long string as these things typically go.\nIt even has a linebreak!");
	phpDirectoryServer2->SetField("TestField6","=");
	phpDirectoryServer2->UploadTable("a", "FirstGameUpload", 80, false);
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing download, should match upload exactly.\n");
	// Download what we just uploaded
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
		{PrintHttpResult(httpResult); return false;}
	// Check results
	if (VerifyDownloadMatchesUpload(1,0)==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing that download works twice in a row.\n");
	// Make sure download works twice
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
		{PrintHttpResult(httpResult); return false;}
	// Check results
	if (VerifyDownloadMatchesUpload(1,0)==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing reuploading a game to modify fields.\n");
	// Modify fields
	phpDirectoryServer2->SetField("TestField1","zero");
	phpDirectoryServer2->SetField("TestField2","empty");
	phpDirectoryServer2->SetField("TestField3","space");
	phpDirectoryServer2->SetField("TestField4","characters");
	phpDirectoryServer2->SetField("TestField5","A shorter string");
	phpDirectoryServer2->SetField("TestField6","Test field 6");
	phpDirectoryServer2->UploadTable("a", "FirstGameUpload", 80, false);
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
	{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing that downloading returns modified fields.\n");
	// Download what we just uploaded
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
		{PrintHttpResult(httpResult); return false;}
	// Check results
	if (VerifyDownloadMatchesUpload(1,0)==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing that downloading works twice.\n");
	// Make sure download works twice
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
		{PrintHttpResult(httpResult); return false;}
	// Check results
	if (VerifyDownloadMatchesUpload(1,0)==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing upload of a second game.\n");
	// Upload another game
	phpDirectoryServer2->SetField("TestField1","0");
	phpDirectoryServer2->SetField("TestField2","");
	phpDirectoryServer2->SetField("TestField3"," ");
	phpDirectoryServer2->SetField("TestField4","Game two characters !@#$%^&*(");
	phpDirectoryServer2->UploadTable("a", "SecondGameUpload", 80, false);
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
	{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}
	SLNet::TimeMS startTime = SLNet::GetTimeMS();

	printf("*** Testing 20 repeated downloads.\n");
	//printf("Field columns\n");
	//PrintFieldColumns();

	// Download repeatedly
	unsigned int downloadCount=0;
	while (downloadCount < 20)
	{
		printf("*** (%i) Downloading 'FirstGameUpload'\n", downloadCount+1);
		// Download again (First game)
		DownloadTable();
		if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
			{PrintHttpResult(httpResult); return false;}
		// Check results
		// DOn't have this stored anymore
//		if (VerifyDownloadMatchesUpload(2,0)==false)
//			{PrintHttpResult(httpResult); return false;}

		printf("*** (%i) Downloading 'SecondGameUpload'\n", downloadCount+1);
		// Download again (second game)
		DownloadTable();
		if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
			{PrintHttpResult(httpResult); return false;}
		// Check results
		if (VerifyDownloadMatchesUpload(2,1)==false)
			{PrintHttpResult(httpResult); return false;}

		downloadCount++;

		RakSleep(1000);
	}

	printf("*** Waiting for 70 seconds to have elapsed...\n");
	RakSleep(70000 - (SLNet::GetTimeMS()-startTime));


	printf("*** Testing that table is now clear.\n");
	// Table should be cleared
	DownloadTable();
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing upload and download. No games should be downloaded.\n");
	phpDirectoryServer2->ClearFields();
	phpDirectoryServer2->SetField("TestField1","NULL");
	UploadAndDownloadTable("FirstGameUpload", 80);
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (PassTestOnEmptyDownloadedTable()==false)
		{PrintHttpResult(httpResult); return false;}

	printf("*** Testing upload and download. One game should be downloaded.\n");
	UploadAndDownloadTable("ThirdGameUpload", 80);
	if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE))
		{PrintHttpResult(httpResult); return false;}
	if (VerifyDownloadMatchesUpload(1,0)==false)
		{PrintHttpResult(httpResult); return false;}

	return true;
}

void OutputBody(HTTPConnection& http, const char *path, const char *data, TCPInterface& tcp);

void TestPHPDirectoryServer(int argc, char **argv)
{
	printf("PHP Directory server 2.\n");
	printf("Similar to lightweight database, but uses common shared webservers.\n");
	printf("Set columns and one row for your game, and upload it to a\nviewable and downloadable webpage.\n");
	printf("Difficulty: Intermediate\n\n");

// 	tcp = SLNet::OP_NEW<TCPInterface>(_FILE_AND_LINE_);
// 	httpConnection = SLNet::OP_NEW<HTTPConnection>(_FILE_AND_LINE_);
// 	phpDirectoryServer2 = SLNet::OP_NEW<PHPDirectoryServer2>(_FILE_AND_LINE_);



//	SLNet::TimeMS lastTouched = 0;



	char website[256];
	char pathToPHP[256];
	if (argc==3)
	{
		strcpy_s(website, argv[1]);
		strcpy_s(pathToPHP, argv[2]);
	}
	else
	{
		printf("Enter website, e.g. jenkinssoftware.com:\n");
		Gets(website,sizeof(website));
		if (website[0]==0)
			strcpy_s(website, "jenkinssoftware.com");

		printf("Enter path to DirectoryServer.php, e.g. raknet/DirectoryServer.php:\n");
		Gets(pathToPHP,sizeof(pathToPHP));
		if (pathToPHP[0]==0)
			strcpy_s(pathToPHP, "/raknet/DirectoryServer.php");
	}

	if (website[strlen(website)-1]!='/' && pathToPHP[0]!='/')
	{
		memmove(pathToPHP+1, pathToPHP, strlen(pathToPHP)+1);
		pathToPHP[0]='/';
	}

	// This creates an HTTP connection using TCPInterface. It allows you to Post messages to and parse messages from webservers.
	// The connection attempt is asynchronous, and is handled automatically as HTTPConnection::Update() is called
	httpConnection->Init(tcp, website);

	// This adds specific parsing functionality to HTTPConnection, in order to communicate with DirectoryServer.php
	phpDirectoryServer2->Init(httpConnection, pathToPHP);

	if (RunTest())
	{
		printf("All tests passed.\n");
	}

	char str[256];
	do 
	{
		printf("\nPress q to quit.\n");
		Gets(str, sizeof(str));
	} while (str[0]!='q');

	// The destructor of each of these references the other, so delete in this order
	SLNet::OP_DELETE(phpDirectoryServer2,_FILE_AND_LINE_);
	SLNet::OP_DELETE(httpConnection,_FILE_AND_LINE_);
	SLNet::OP_DELETE(tcp,_FILE_AND_LINE_);
}

void TestGet(void)
{
	printf("This is NOT a reliable way to download from a website. Use libcurl instead.\n");
	httpConnection->Init(tcp, "jenkinssoftware.com");
	httpConnection->Get("/trivia/ranking.php?t=single&places=6&top");
	for(;;)
	{
		Packet *packet = tcp->Receive();
		if(packet)
		{
			//printf((char*) packet->data);
			httpConnection->ProcessTCPPacket(packet);
			tcp->DeallocatePacket(packet);
		}
		httpConnection->Update();

		if (httpConnection->IsBusy()==false)
		{
			RakString fileContents = httpConnection->Read();
			printf(fileContents.C_String());
			_getche();
			return;
		}
		// Prevent 100% cpu usage
		RakSleep(30);
	}
}

int main(int argc, char **argv)
{
	printf("PHP Directory server 2.\n");
	printf("Similar to lightweight database, but uses common shared webservers.\n");
	printf("Set columns and one row for your game, and upload it to a\nviewable and downloadable webpage.\n");
	printf("Difficulty: Intermediate\n\n");

	tcp = SLNet::OP_NEW<TCPInterface>(__FILE__,__LINE__);
	httpConnection = SLNet::OP_NEW<HTTPConnection>(__FILE__,__LINE__);
	phpDirectoryServer2 = SLNet::OP_NEW<PHPDirectoryServer2>(__FILE__,__LINE__);



	//    RakNetTime lastTouched = 0;

	// Start the TCP thread. This is used for general TCP communication, whether it is for webpages, sending emails, or telnet
	tcp->Start(0, 64);

	TestPHPDirectoryServer(argc,argv);

	//TestGet();

	return 0;
}
