// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


// ---------------------------------------------------------------------------
// Includes
// ---------------------------------------------------------------------------


#include "nel/misc/types_nl.h"

#include "nel/misc/file.h"
#include "nel/misc/path.h"
#include "nel/misc/bitmap.h"
#include "nel/misc/events.h"
#include "nel/misc/time_nl.h"
#include "nel/misc/event_server.h"
#include "nel/misc/event_listener.h"

#include "nel/3d/nelu.h"
#include "nel/3d/driver.h"
#include "nel/3d/scene_group.h"
#include "nel/3d/text_context.h"
#include "nel/3d/transform_shape.h"
#include "nel/3d/event_mouse_listener.h"

#ifdef NL_OS_WINDOWS
#	define NOMINMAX
#	include <windows.h>
#endif // NL_OS_WINDOWS

#ifndef CV_DIR
#	define CV_DIR "."
#endif

using namespace std;
using namespace NL3D;
using namespace NLMISC;


// ---------------------------------------------------------------------------
struct SDispCS
{
	string Name;
	CInstanceGroup *pIG;
};


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
char *readLine (char*out, char*in)
{
	out[0] = 0;

	while ((*in != '\r') && (*in != '\n') && (*in != 0))
	{
		*out = *in;
		++out;
		++in;
		*out = 0;
	}
	
	while ((*in == '\r') || (*in == '\n'))
		++in;
	return in;
}


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
CInstanceGroup* LoadInstanceGroup(const char* sFilename)
{
	CIFile file;
	CInstanceGroup *newIG = new CInstanceGroup;

	if( file.open( CPath::lookup( string(sFilename) ) ) )
	{
		try
		{
			// Serial the skeleton
			newIG->serial (file);
			// All is good
		}
		catch (const Exception &)
		{
			// Cannot save the file
			delete newIG;
			return NULL;
		}
	}
	return newIG;
}

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void LoadSceneScript (const char *ScriptName, CScene* pScene, vector<SDispCS> &DispCS, CVector &CameraStart)
{
	char nameIG[256];
	float posx, posy, posz;
	float roti, rotj, rotk;
	
	FILE *f = fopen (ScriptName,"rb");
	fseek (f, 0, SEEK_END);
	uint file_size = ftell (f);
	fseek (f, 0, SEEK_SET);
	char *file_buf = (char*)malloc(file_size+1);
	if (fread (file_buf, 1, file_size, f) != file_size)
		nlwarning("Can't read %d elements", file_size);

	file_buf[file_size] = 0;
	++file_size;
	fclose (f);
	char *buf_ptr = file_buf;
	sint nLastNbPlus = 0;
	vector<CInstanceGroup*> pile;
	pile.clear ();

	do 
	{
		char Line[256], *line_ptr;
		sint nNbPlus = 0;
		line_ptr = &Line[0];
		buf_ptr = readLine (line_ptr, buf_ptr);

		while ((*line_ptr == '\t') || (*line_ptr == ' ') || (*line_ptr == '+'))
		{
			if (*line_ptr == '+') 
				++nNbPlus;
			++line_ptr;
		}

		if (strlen (line_ptr) == 0)
			break;

		if (*line_ptr == '/')
			continue;

		posx = posy = posz = roti = rotj = rotk = 0.0f;
		sscanf (line_ptr, "%s %f %f %f %f %f %f", nameIG, &posx, &posy, &posz, &roti, &rotj, &rotk);

		if (stricmp (nameIG, "CAMERA_START") == 0)
		{
			CameraStart = CVector(posx, posy, posz);
		}
		else
		{
			if (nLastNbPlus >= nNbPlus)
			for (int i = 0; i < ((nLastNbPlus-nNbPlus)+1); ++i)
				if (pile.size() > 0)
					pile.pop_back();
		
			nLastNbPlus = nNbPlus;

			CInstanceGroup *father = pScene->getGlobalInstanceGroup();
			
			if (pile.size() > 0)
				father = pile.back();
		
			CInstanceGroup *ITemp = LoadInstanceGroup (nameIG);
			if (ITemp != NULL)
			{
				SDispCS dcsTemp;
				dcsTemp.Name = "";
				for (sint32 i = 0; i < (1+nNbPlus); ++i)
					dcsTemp.Name += "   ";
				dcsTemp.Name += nameIG;
				dcsTemp.pIG = ITemp;
				DispCS.push_back (dcsTemp);
				
				ITemp->createRoot (*pScene);
				ITemp->setPos (CVector(posx, posy, posz));
				ITemp->setRotQuat (ITemp->getRotQuat() * CQuat(CVector::I, roti));
				ITemp->setRotQuat (ITemp->getRotQuat() * CQuat(CVector::J, rotj));
				ITemp->setRotQuat (ITemp->getRotQuat() * CQuat(CVector::K, rotk));
				ITemp->setClusterSystemForInstances (father);
				ITemp->addToScene (*pScene);
			}
			pile.push_back (ITemp);
		}
	} 
	while (strlen(buf_ptr) > 0);
	free (file_buf);
}


// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
#ifdef NL_OS_WINDOWS
int CALLBACK WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
#else // NL_OS_WINDOWS
int main(int argc, char **argv)
#endif // NL_OS_WINDOWS
{
	double rGlobalTime = 0;
	double rOldGlobalTime = 0;
	double rDeltaTime = 0;

	vector<SDispCS> DispCS;

	bool bAutoDetect = true;
	bool bDisplay = true;
	sint32 nSelected = 0;

	SDispCS dcsTemp;


	CNELU::init (800, 600, CViewport(), 32, true);

	CNELU::Scene->enableLightingSystem(true);
	CNELU::Scene->setAmbientGlobal(CRGBA(128,128,128));

	CPath::addSearchPath(CV_DIR);
	CPath::addSearchPath(CV_DIR"/shapes");
	CPath::addSearchPath(CV_DIR"/groups");
	CPath::addSearchPath(CV_DIR"/fonts");

	CFontManager FontManager;
	CTextContext TextContext;

	TextContext.init (CNELU::Driver, &FontManager);	
	TextContext.setFontGenerator (NLMISC::CPath::lookup("n019003l.pfb"));
	TextContext.setHotSpot (CComputedString::TopLeft);
	TextContext.setColor (CRGBA(255,255,255));
	TextContext.setFontSize (20);

	CEvent3dMouseListener MouseListener;
	MouseListener.addToServer (CNELU::EventServer);
	MouseListener.setFrustrum (CNELU::Camera->getFrustum());
	MouseListener.setMouseMode (CEvent3dMouseListener::firstPerson);
	
	CNELU::Camera->setTransformMode (ITransformable::DirectMatrix);
	// Force to automatically find the cluster system
	CNELU::Camera->setClusterSystem ((CInstanceGroup*)-1); 

	CClipTrav *pClipTrav = &CNELU::Scene->getClipTrav();
	dcsTemp.Name = "Root";
	dcsTemp.pIG = NULL;
	DispCS.push_back (dcsTemp);

	// Add all instance that create the scene
	// --------------------------------------	
	// Beginning of script reading
	CVector CameraStart;

	LoadSceneScript ("view_cs.txt", CNELU::Scene, DispCS, CameraStart);

	CMatrix m = MouseListener.getViewMatrix();
	m.setPos (CameraStart);
	MouseListener.setMatrix (m);
	// End of script reading

	// Main loop
	do
	{
		rGlobalTime = CTime::ticksToSecond(CTime::getPerformanceTime());
		rDeltaTime = rGlobalTime - rOldGlobalTime;
		rOldGlobalTime = rGlobalTime;


		CNELU::Scene->animate ((float)rGlobalTime);

		CNELU::EventServer.pump();

		CNELU::clearBuffers (CRGBA(0,0,0));

		CNELU::Scene->render ();

		// -----------------------------------------------------
		// -----------------------------------------------------
		if (bDisplay)
		{
			vector<CCluster*> vCluster;
			DispCS[0].pIG = pClipTrav->RootCluster->Group;
			TextContext.setColor (CRGBA(255,255,255,255));
			if (bAutoDetect)
			{
				TextContext.printfAt (0, 1, "AutoDetect : ON");
				
				pClipTrav->fullSearch (vCluster, pClipTrav->CamPos);

				for (uint32 i = 0; i < DispCS.size(); ++i)
				{
					TextContext.setColor (CRGBA(255,255,255,255));
					for( uint32 j = 0; j < vCluster.size(); ++j)
					{
						if (DispCS[i].pIG == vCluster[j]->Group)
						{
							TextContext.setColor (CRGBA(255,0,0,255));
							break;
						}
					}

					TextContext.printfAt (0, 1-(i+2)*0.028f, DispCS[i].Name.c_str());
				}

			}
			else
			{
				TextContext.printfAt (0, 1, "AutoDetect : OFF");

				CInstanceGroup *pCurIG = CNELU::Camera->getClusterSystem();
				for (uint32 i = 0; i < DispCS.size(); ++i)
				{
					if (DispCS[i].pIG == pCurIG)
						TextContext.setColor (CRGBA(255,0,0,255));
					else
						TextContext.setColor (CRGBA(255,255,255,255));

					TextContext.printfAt (0, 1-(i+2)*0.028f, DispCS[i].Name.c_str());
				}

				TextContext.setColor (CRGBA(255,255,255,255));

				pClipTrav->Accel.select (pClipTrav->CamPos, pClipTrav->CamPos);
				CQuadGrid<CCluster*>::CIterator itAcc = pClipTrav->Accel.begin();
				while (itAcc != pClipTrav->Accel.end())
				{
					CCluster *pCluster = *itAcc;
					if (pCluster->Group == pClipTrav->Camera->getClusterSystem())
					if (pCluster->isIn (pClipTrav->CamPos))
					{
						vCluster.push_back (pCluster);
					}
					++itAcc;
				}
				if ((vCluster.size() == 0) && (DispCS[0].pIG == pCurIG))
				{
					vCluster.push_back (pClipTrav->RootCluster);
				}

			}			

			TextContext.setColor (CRGBA(255,255,255,255));

			string sAllClusters;
			for( uint32 j = 0; j < vCluster.size(); ++j)
			{
				sAllClusters += vCluster[j]->Name;
				if (j < (vCluster.size()-1))
					sAllClusters += ",  ";
			}
			TextContext.printfAt (0, 1-0.028f, sAllClusters.c_str());
		}

		// -----------------------------------------------------
		// -----------------------------------------------------

		CNELU::Driver->swapBuffers ();

		// Keys management
		// ---------------

		if (CNELU::AsyncListener.isKeyDown (KeySHIFT))
			MouseListener.setSpeed (50.0f);
		else
			MouseListener.setSpeed (10.0f);

		CNELU::Camera->setMatrix (MouseListener.getViewMatrix());

		if (CNELU::AsyncListener.isKeyPushed(KeyL))
		{
			CNELU::Driver->setPolygonMode (IDriver::Line);
		}

		if (CNELU::AsyncListener.isKeyPushed (KeyP))
		{
			CNELU::Driver->setPolygonMode (IDriver::Filled);
		}

		if (CNELU::AsyncListener.isKeyPushed (KeyTAB))
			bDisplay = !bDisplay;

		if (CNELU::AsyncListener.isKeyPushed (KeyA))
		{
			if (bAutoDetect)
			{
				bAutoDetect = false;
				CNELU::Camera->setClusterSystem (NULL);
				nSelected = 0;
			}
			else
			{
				bAutoDetect = true;
				CNELU::Camera->setClusterSystem ((CInstanceGroup*)-1);
			}
		}

		if (!bAutoDetect)
		{
			if (CNELU::AsyncListener.isKeyPushed (KeyZ))
			{
				nSelected--;
				if(nSelected == -1)
					nSelected = (sint32)DispCS.size()-1;
			}
			if (CNELU::AsyncListener.isKeyPushed (KeyS))
			{
				nSelected++;
				if(nSelected == (sint32)DispCS.size())
					nSelected = 0;
			}
			CNELU::Camera->setClusterSystem (DispCS[nSelected].pIG);
		}

	}
	while ((!CNELU::AsyncListener.isKeyPushed(KeyESCAPE)) && CNELU::Driver->isActive());

	return EXIT_SUCCESS;
}