/*********************************************************************

Name:		Raytracer
Author:		Kamil Szatkowski "Netrix" (C)
Website:	http://www.netrix.org.pl/

*********************************************************************/

// RSI PART
#include "RSICommon.hpp"
#include "Client.hpp"
#include "Server.hpp"
// Rest
#include <stdio.h>
#include <stdlib.h>
#include "RayTracer.hpp"
#include "SceneDesc.hpp"
#include "CommandWalker.hpp"
#include "NLib/nLogger.hpp"
using namespace NIne;

#pragma warning(disable: 4996)

#ifdef _WIN32
	#include <windows.h>
#else
	#include <time.h>
	#include <pthread.h>
	typedef unsigned long long LARGE_INTEGER;
	timespec g_ts;
	#define QueryPerformanceCounter(a)		clock_gettime(CLOCK_REALTIME, &g_ts); *a = (LARGE_INTEGER)g_ts.tv_sec * 1000000000u + (LARGE_INTEGER)g_ts.tv_nsec;
	#define QueryPerformanceFrequency(a)	*a = 1000000000u;
#endif

// Raytracer
RayTracer g_rt;

// Parallel
SocketServer g_server;
SocketClient g_client;
NUint32 g_uOption = 0;
const char* g_szIP = null;
unsigned short g_port = 0;
NUint32	g_uMaxConn = 0;

// Server part
NDynamicTable<SocketClient> vClients;
NDataVector<Task> vTasks;
NSize_t uFreeTask = 0;
NSize_t uDoneTasks = 0;
NSize_t uReservedTasks = 0;
NSize_t uLastFullDoneTask = 0;
NSize_t uFreeClient = 0;
NSize_t uConnectedClients = 0;

#ifdef _WIN32
DWORD WINAPI ServerProc(PVOID pvParam);
#else
void* ServerProc(void* pvParam);
#endif

// Declarations
bool setScene(Arguments& pArguments);
bool setCamera(Arguments& pArguments);
bool setTexturedAttribute(Arguments& pArguments);
bool setResetCache(Arguments& pArguments);
bool setBrick(Arguments& pArguments);
bool setOut(Arguments& pArguments);
bool setResolution(Arguments& pArguments);
bool setAngle(Arguments& pArguments);
bool setDirt(Arguments& pArguments);
bool setPackSize(Arguments& pArguments);
bool help(Arguments& pArguments);
bool client(Arguments& pArguments);
bool server(Arguments& pArguments);


int main(int argc, const char* argv[])
{
	NLogInit("RayTracer.log", true);

	CommandWalker cmdWalker(argc, argv);
	cmdWalker.AddCommand("--camera", "\tCustom camera\n\t\t\t<position x y z> <look x y z>", setCamera);
	cmdWalker.AddCommand("--texattr", "\tSet textured attribute:\n\t\t\t<index>", setTexturedAttribute);
	cmdWalker.AddCommand("--resetcache", "\tReset space subdivision cache", setResetCache);
	cmdWalker.AddCommand("--brick", "\tSet brick parameters:\n\t\t\t<width: 600> <height: 125> <mortar_width: 20>", setBrick);
	cmdWalker.AddCommand("--out", "\t\tSet output filename:\n\t\t\t<filename: out_tex.bmp>", setOut);
	cmdWalker.AddCommand("--resolution", "\tSet output resolution:\n\t\t\t<width> <height>", setResolution);
	cmdWalker.AddCommand("--angle", "\tSet angle coefficient:\n\t\t\t<angle coeff: 0.125>", setAngle);
	cmdWalker.AddCommand("--dirt", "\tSet dirt on", setDirt);
	cmdWalker.AddCommand("--packsize", "\tSet pack size", setPackSize);
	cmdWalker.AddCommand("--help", "\tDisplays this help", help);
	cmdWalker.AddCommand("--client", "\tSelect client mode (no need for [file]):\n\t\t\t<IP> <port>", client);
	cmdWalker.AddCommand("--server", "\tSelect server mode:\n\t\t\t<port> <max connections>", server);
	cmdWalker.AddDefault(setScene);

	if(cmdWalker.ParseCommands() || (g_uOption != 2 && g_rt.parameters.szDescFile[0] == 0)) 
	{
		printf("RayTracer.exe <commands> [file]\n\n");
		printf("[file] - scene name (without .txt), see Scenes/scene_descs/ dir\n\n");
		cmdWalker.PrintCommands();
		printf("Example:\n\tRayTracer --texattr 7 room_vn\n\n");
		return false; 
	}

	// WinSock set up
#ifdef _WIN32
	WSADATA wsaData;
	ZeroMemory(&wsaData, sizeof(wsaData));

	if(FAILED(WSAStartup(MAKEWORD(2,2), &wsaData)))	// MAKEWORD(2,2) - Wersja WinSock
	{
		printf("Failed on 'WSAStartup(MAKEWORD(2,2), &wsaData)'\n");
		return 1;
	}
#endif

	// Run
	if(g_uOption == 1)	// server
	{
		if(g_server.initServer(g_port, g_uMaxConn)) { return 1; }
		g_rt.load(0, true);

		const Camera& camera = g_rt.getCamera();

		NBitmap bitmap;
		bitmap.create(g_rt.parameters.uWidth, g_rt.parameters.uHeight);
		NUint8* pSrc = bitmap.getBuffer();

		// Prepare tasks
		vTasks.Create((g_rt.parameters.uWidth * g_rt.parameters.uHeight) / (g_rt.parameters.uPackSize * g_rt.parameters.uPackSize));

		for(NUint32 y = 0; y < g_rt.parameters.uHeight; y += g_rt.parameters.uPackSize)
		{
			for(NUint32 x = 0; x < g_rt.parameters.uWidth; x += g_rt.parameters.uPackSize)
			{
				Task task = { (NIne::NUint32)vTasks.GetSize(),								// uTaskIndex
							x, y,															// uPackX, uPackY
							min(g_rt.parameters.uPackSize, g_rt.parameters.uWidth - x),		// uWidth
							min(g_rt.parameters.uPackSize, g_rt.parameters.uHeight - y),	// uHeight
							g_rt.parameters.uWidth * 3,										// uPitch
							pSrc + (y * g_rt.parameters.uWidth + x) * 3,					// pDestination
							false, false,													// bDone, bReserved
							(NIne::NUint32)vTasks.GetSize() + 1 };							// uNext

				vTasks.PushElement(task);
			}
		}
		uFreeTask = 0;

		// Prepare clients
		vClients.Create(g_server.getMaxConnections());

		NLogMessage("Waiting for clients...\n", uConnectedClients);

		// Run task
		while(uDoneTasks < vTasks.GetSize())
		{
			if(!g_server.checkRequest() && uReservedTasks < vTasks.GetSize() && uConnectedClients < g_server.getMaxConnections())
			{
				SOCKET pClient = g_server.getClientSocket();
				NLogMessage("Client %d connected\n", uConnectedClients);

				while(vClients[uFreeClient].isValid()) uFreeClient++;

				sockaddr_in adr;
				g_server.getClientAddr(&adr);
				vClients[uFreeClient].initClient(pClient, adr);

				#ifdef _WIN32				
				CreateThread(null, 0, ServerProc, (PVOID)&vClients[uFreeClient], 0, null);
				#else
				pthread_t thread;
				pthread_create(&thread, null, &ServerProc, &vClients[uFreeClient]);
				#endif
				uFreeClient++;

				NLogMessage("Client %d created\n", uConnectedClients);
				uConnectedClients++;
			}

			#ifdef _WIN32
			Sleep(100);
			#else
			usleep(100000);
			#endif

			//// Recalculate done task - FIX
			//uint uDone = 0;
			//for(uint i = 0; i < vTasks.GetSize(); ++i)	{ vTasks[i].bDone ? ++uDone : 0; }
			//uDoneTasks = uDone;
		}

		bitmap.saveToFile(g_rt.parameters.szOutFile);
		g_server.release();
	}
	else if(g_uOption == 2)	// client
	{
		while(g_client.initClient(g_szIP, g_port));

		RSIClient rsiClient;
		rsiClient.init(g_client, g_rt);
		rsiClient.run();

		g_client.release();
	}
	else	// standalone
	{
		// Loading scene
		if(g_rt.load()) return 1;

		// Preprocess
		g_rt.preprocessScene();
		printf("Preprocess done\n");

		NUint64 uFreq;
		NUint64 uFirstTime, uSecondTime;
		QueryPerformanceFrequency((LARGE_INTEGER*)&uFreq);

		QueryPerformanceCounter((LARGE_INTEGER*)&uFirstTime);
		g_rt.run();
		QueryPerformanceCounter((LARGE_INTEGER*)&uSecondTime);

		//g_rt.saveResult(g_szOut);

		printf("Time: %f\n", (double)(uSecondTime - uFirstTime) / (double)uFreq);
		NLogMessage("Time: %f\n", (double)(uSecondTime - uFirstTime) / (double)uFreq);

		g_rt.release();
	}

	//system("pause");

#ifdef _WIN32
	WSACleanup();
#endif

	return 1;
}


//////////////
// ThreadProc
#ifdef _WIN32
DWORD WINAPI ServerProc(PVOID pvParam)
#else
void* ServerProc(void* pvParam)
#endif
{
	RSIServer rsiServer;
	rsiServer.init(g_rt.parameters, *(SocketClient*)pvParam);

	while(uDoneTasks < vTasks.GetSize())
	{
		NSize_t uTask = uFreeTask++;

		while(uTask < vTasks.GetSize() && vTasks[uTask].bReserved)	{ uTask++; }
		if(uTask == vTasks.GetSize()) { break; }	// end of tasks

		printf("Current task: %d/%d\n", uTask + 1, vTasks.GetSize());

		vTasks[uTask].bReserved = true;
		uReservedTasks++;
		if(rsiServer.run(vTasks[uTask]))
		{
			uFreeTask = uTask;
			vTasks[uTask].bReserved = false;
			uReservedTasks--;
			break;
		}

		uDoneTasks++;
	}

	rsiServer.done();
	uConnectedClients = 0;
	uFreeClient = (SocketClient*)pvParam - vClients.GetBuffer();

	return 0;
}

// Argument parser definitions
bool setScene(Arguments& pArguments)
{
	if(pArguments.GetActualArgument() == null
	|| pArguments.GetActualArgument()[0] == '-') { return true; }

	NSize_t pathSize = strlen("Scenes/scene_descs/.txt");
	if(strnlen(pArguments.GetActualArgument(), MAX_PATH_SIZE - pathSize) == MAX_PATH_SIZE - pathSize)
	{
		NLogError("Invalid scene");
		return true;
	}

	sprintf(g_rt.parameters.szDescFile, "Scenes/scene_descs/%s.txt", pArguments.GetActualArgument());

	return false;
}

bool setCamera(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }

	float x, y, z;
	if(sscanf(pArguments.GetActualArgument(), "%f", &x) < 1) return false;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &y) < 1) return false;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &z) < 1) return false;

	NMVector3f v3CamPos = NMVector3fLoad(x, y, z);

	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &x) < 1) return false;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &y) < 1) return false;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &z) < 1) return false;

	NMVector3f v3CamLook = NMVector3fLoad(x, y, z);

	g_rt.setCamera(v3CamPos, v3CamLook);

	return false;
}

bool setTexturedAttribute(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	return sscanf(pArguments.GetActualArgument(), "%u", &g_rt.parameters.uTextureAttribute) != 1;
}

bool setResetCache(Arguments& pArguments)
{
	g_rt.parameters.bResetCache = true;
	return false;
}

bool setBrick(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &g_rt.parameters.fBrickWidth) < 1) return true;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &g_rt.parameters.fBrickHeight) < 1) return true;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &g_rt.parameters.fBrickMortar) < 1) return true;

	return false;
}

bool setOut(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }

	if(strnlen(pArguments.GetActualArgument(), MAX_PATH_SIZE) > MAX_PATH_LENGTH)
	{
		NLogError("Invalid string: 'szFile'");
		return true;
	}
	
	strcpy(g_rt.parameters.szOutFile, pArguments.GetActualArgument());
	return false;
}

bool setResolution(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	NUint32 uWidth, uHeight;
	if(sscanf(pArguments.GetActualArgument(), "%u", &uWidth) < 1) return true;
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%u", &uHeight) < 1) return true;
	g_rt.setOutputResolution(uWidth, uHeight);

	return false;
}

bool setAngle(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	if(sscanf(pArguments.GetActualArgument(), "%f", &g_rt.parameters.fAngleCoeff) < 1) return true;

	return false;
}

bool setDirt(Arguments& pArguments)
{
	g_rt.parameters.bDirt = true;
	return false;
}

bool setPackSize(Arguments& pArguments)
{
        if(!pArguments.NextArgument()) { return true; }
        return sscanf(pArguments.GetActualArgument(), "%u", &g_rt.parameters.uPackSize) != 1;	
}

bool help(Arguments& pArguments)
{
	return true;
}

bool client(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	g_szIP = pArguments.GetActualArgument();
	if(!pArguments.NextArgument()) { return true; }
	g_port = (unsigned short)atoi(pArguments.GetActualArgument());

	g_uOption = 2;
	return false;
}

bool server(Arguments& pArguments)
{
	if(!pArguments.NextArgument()) { return true; }
	g_port = (unsigned short)atoi(pArguments.GetActualArgument());
	if(!pArguments.NextArgument()) { return true; }
	g_uMaxConn = atoi(pArguments.GetActualArgument());

	g_uOption = 1;
	return false;
}
