// 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/>.

#include <string>
#include <deque>

#include <nel/misc/types_nl.h>
#include <nel/misc/config_file.h>
#include <nel/misc/debug.h>
#include <nel/misc/path.h>
#include <nel/misc/i18n.h>

#include <nel/3d/driver.h>
#include <nel/3d/camera.h>
#include <nel/3d/landscape_model.h>
#include <nel/3d/landscape.h>
#include <nel/3d/text_context.h>
#include <nel/3d/mini_col.h>
#include <nel/3d/nelu.h>
#include <nel/3d/scene_group.h>
#include <nel/3d/texture_file.h>

//#include "nel/net/local_entity.h"

#include "move_listener.h"

// Tempyoyo.
#include <nel/3d/height_map.h>

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

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


#define BANK_PAH_RELATIVE

#ifndef NL_ZVIEWER_CFG
#define NL_ZVIEWER_CFG "."
#endif // NL_ZVIEWER_CFG

/**
 * CViewerConfig
 */
struct CViewerConfig
{
	bool			Windowed;
	uint			Width;
	uint			Height;
	uint			Depth;
	CVector			Position;
	CVector			Heading;
	CVector			EyesHeight;
	CRGBA			Background;

	// Landscape
	bool			AutoLight;
	CVector			LightDir;
	string			ZonesPath;
	string			BanksPath;
	string			TilesPath;
	bool			UseDDS;
	bool			AllPathRelative;

	string			IgPath;
	string			ShapePath;
	string			MapsPath;
	string			Bank;
	string			FontPath;
	CTextContext	TextContext;
	CFontManager	FontManager;
	float			ZFar;
	float			LandscapeTileNear;
	float			LandscapeThreshold;
	bool			LandscapeNoise;
	vector<string>	Zones;
	vector<string>	Igs;

	// HeightField.
	string			HeightFieldName;
	float			HeightFieldMaxZ;
	float			HeightFieldOriginX;
	float			HeightFieldOriginY;
	float			HeightFieldSizeX;
	float			HeightFieldSizeY;

	// StaticLight
	CRGBA			LandAmbient;
	CRGBA			LandDiffuse;

	CViewerConfig()
	{
		Windowed = true;
		Width = 800;
		Height = 600;
		Depth = 32;
		Position = CVector( 1088.987793f, -925.732178f, 0.0f );
		Heading = CVector(0,1,0);
		EyesHeight = CVector(0,0,1.8f);
		Background = CRGBA(100,100,255);
		AutoLight = false;
		LightDir = CVector (1, 0, 0);
		ZonesPath = "./";
		BanksPath = "./";
		TilesPath = "./";
		UseDDS = false;
		AllPathRelative = false;
		IgPath = "./";
		ShapePath = "./";
		MapsPath = "./";
		Bank = "bank.bank";
		FontPath = "\\\\server\\code\\fonts\\arialuni.ttf";
		ZFar = 1000;
		LandscapeTileNear = 50.0f;
		LandscapeThreshold = 0.001f;
		LandscapeNoise = true;

		HeightFieldName= "";
		HeightFieldMaxZ= 100;
		HeightFieldOriginX= 16000;
		HeightFieldOriginY= -24000;
		HeightFieldSizeX= 160;
		HeightFieldSizeY= 160;

		CRGBA diffuse (241, 226, 244);
		CRGBA ambiant  (17, 54, 100);
		LandDiffuse= diffuse;
		LandAmbient= ambiant;

	}
};

CViewerConfig			ViewerCfg;



CLandscapeModel			*Landscape = NULL;
CMoveListener			MoveListener;
CMiniCol				CollisionManager;











/*******************************************************************\
						getZoneNameByCoord()
\*******************************************************************/
string getZoneNameByCoord(float x, float y)
{
	const float zoneDim = 160.0f;

	float xcount = x/zoneDim;
	float ycount = -y/zoneDim + 1;

	string zoneName;
	char ych[32];
	sprintf(ych,"%d",(sint)ycount);
	sint sz = (sint)strlen(ych);
	for(sint i = 0; i<sz; i++)
	{
		zoneName += ych[i];
	}
	zoneName += '_';
	zoneName += 'A' + (sint)xcount/26;
	zoneName += 'A' + (sint)xcount%26;

	return zoneName;
}




/*********************************************************\
					displayOrientation()
\*********************************************************/
void displayOrientation()
{
	float x = 0.9f*4.f/3.f;
	float y = 0.1f;
	float radius = 0.015f;

	// Triangle
	CMaterial mat;
	mat.initUnlit();
	mat.setSrcBlend(CMaterial::srcalpha);
	mat.setDstBlend(CMaterial::invsrcalpha);
	mat.setBlend(true);
	
	CVertexBuffer vb;
	vb.setVertexFormat (CVertexBuffer::PositionFlag);
	vb.setNumVertices (7);
	{
		CVertexBufferReadWrite vba;
		vb.lock(vba);
		
		// tri
		vba.setVertexCoord (0, CVector (-radius, 0, 0));
		vba.setVertexCoord (1, CVector (radius, 0, 0));
		vba.setVertexCoord (2, CVector (0, 0, 3*radius));

		// quad
		vba.setVertexCoord (3, CVector (-radius, 0, -radius));
		vba.setVertexCoord (4, CVector (radius, 0, -radius));
		vba.setVertexCoord (5, CVector (radius, 0, radius));
		vba.setVertexCoord (6, CVector (-radius, 0, radius));
	}
	
	CNELU::Driver->activeVertexBuffer(vb);

	CIndexBuffer pbTri;
	pbTri.setNumIndexes (3);
	{
		CIndexBufferReadWrite iba;
		pbTri.lock (iba);
		iba.setTri (0, 0, 1, 2);
	}
	
	CIndexBuffer pbQuad;
	pbQuad.setNumIndexes (6);
	{
		CIndexBufferReadWrite iba;
		pbQuad.lock(iba);
		iba.setTri (0, 3, 4, 5);
		iba.setTri (3, 5, 6, 3);
	}
	
	CNELU::Driver->setFrustum (0.f, 4.f/3.f, 0.f, 1.f, -1.f, 1.f, false);
	CMatrix mtx;
	mtx.identity();
	CNELU::Driver->setupViewMatrix (mtx);

	mat.setColor(CRGBA(50,255,255,150));

	// up
	mtx.identity();
	mtx.translate(CVector(x,0,y));
	mtx.rotateY(MoveListener.getRotZ() );
	mtx.translate(CVector(0,0,radius));
	CNELU::Driver->setupModelMatrix (mtx);
	CNELU::Driver->activeVertexBuffer(vb);
	CNELU::Driver->activeIndexBuffer(pbTri);
	CNELU::Driver->renderTriangles(mat, 0, pbTri.getNumIndexes()/3);

	mat.setColor(CRGBA(50,50,255,150));

	// down
	mtx.identity();
	mtx.translate(CVector(x,0,y));
	mtx.rotateY(MoveListener.getRotZ() + (float)Pi);
	mtx.translate(CVector(0,0,radius));
	CNELU::Driver->setupModelMatrix (mtx);
	CNELU::Driver->renderTriangles(mat, 0, pbTri.getNumIndexes()/3);

	// left
	mtx.identity();
	mtx.translate(CVector(x,0,y));
	mtx.rotateY(MoveListener.getRotZ() - (float)Pi/2);
	mtx.translate(CVector(0,0,radius));
	CNELU::Driver->setupModelMatrix (mtx);
	CNELU::Driver->renderTriangles(mat, 0, pbTri.getNumIndexes()/3);

	// right
	mtx.identity();
	mtx.translate(CVector(x,0,y));
	mtx.rotateY(MoveListener.getRotZ() + (float)Pi/2);
	mtx.translate(CVector(0,0,radius));
	CNELU::Driver->setupModelMatrix (mtx);
	CNELU::Driver->renderTriangles(mat, 0, pbTri.getNumIndexes()/3);
	
	// center
	mtx.identity();
	mtx.translate(CVector(x,0,y));
	mtx.rotateY(MoveListener.getRotZ());
	CNELU::Driver->setupModelMatrix (mtx);
	CNELU::Driver->activeIndexBuffer(pbQuad);
	CNELU::Driver->renderTriangles(mat, 0, pbQuad.getNumIndexes()/3);	
}





/*********************************************************\
					displayZone()
\*********************************************************/
void displayZones()
{
	const float zFarStep = 5.0f;
	const float	tileNearStep = 10.0f;
	const float thresholdStep = 0.005f;

	ViewerCfg.TextContext.setHotSpot(CComputedString::MiddleMiddle);
	ViewerCfg.TextContext.setColor(CRGBA(255,255,255));
	ViewerCfg.TextContext.setFontSize(20);
	
	CNELU::clearBuffers(CRGBA(0,0,0));
	CNELU::swapBuffers();



	// Create landscape
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Creating landscape...");
	CNELU::swapBuffers();
	
	Landscape = (CLandscapeModel*)CNELU::Scene->createModel(LandscapeModelId);
	Landscape->Landscape.setNoiseMode (ViewerCfg.LandscapeNoise);
	Landscape->Landscape.setTileNear(ViewerCfg.LandscapeTileNear);
	Landscape->Landscape.setThreshold(ViewerCfg.LandscapeThreshold);

	Landscape->Landscape.enableAutomaticLighting (ViewerCfg.AutoLight);
	Landscape->Landscape.setupAutomaticLightDir (ViewerCfg.LightDir);

	// Enable Additive Tiles.
	Landscape->enableAdditive(true);

	// HeightField.
	CBitmap		heightBitmap;
	CIFile file(ViewerCfg.HeightFieldName);

	if( ViewerCfg.HeightFieldName!="" && heightBitmap.load(file) )
	{
		CHeightMap	heightMap;
		heightMap.buildFromBitmap(heightBitmap);
		heightMap.MaxZ= ViewerCfg.HeightFieldMaxZ;
		heightMap.OriginX= ViewerCfg.HeightFieldOriginX;
		heightMap.OriginY= ViewerCfg.HeightFieldOriginY;
		heightMap.SizeX = ViewerCfg.HeightFieldSizeX;
		heightMap.SizeY = ViewerCfg.HeightFieldSizeY;
		Landscape->Landscape.setHeightField(heightMap);
	}

	
	// Init TileBank.
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Initializing TileBanks...");
	CNELU::swapBuffers();

	try 
	{
		CIFile bankFile (ViewerCfg.BanksPath + "/" + ViewerCfg.Bank);
		Landscape->Landscape.TileBank.serial(bankFile);
	}
	catch(const Exception &)
	{
		string tmp = string("Cant load bankfile ")+ViewerCfg.BanksPath + "/" + ViewerCfg.Bank;
		nlerror (tmp.c_str());
	}

	if ((Landscape->Landscape.TileBank.getAbsPath ()=="")&&(ViewerCfg.TilesPath!=""))
		Landscape->Landscape.TileBank.setAbsPath (ViewerCfg.TilesPath + "/");

	if (ViewerCfg.UseDDS)
	{
		Landscape->Landscape.TileBank.makeAllExtensionDDS();
	}

	if (ViewerCfg.AllPathRelative)
		Landscape->Landscape.TileBank.makeAllPathRelative();

	string::size_type idx = ViewerCfg.Bank.find(".");
	string farBank = ViewerCfg.Bank.substr(0,idx);
	farBank += ".farbank";

	try
	{
		CIFile farbankFile(ViewerCfg.BanksPath + "/" + farBank);
		Landscape->Landscape.TileFarBank.serial(farbankFile);
	}
	catch(const Exception &)
	{
		string tmp = string("Cant load bankfile ")+ViewerCfg.BanksPath + "/" + farBank;
		nlerror (tmp.c_str());
	}
	
	if ( ! Landscape->Landscape.initTileBanks() )
	{
		nlwarning( "You need to recompute bank.farbank for the far textures" );
	}
	
	// Init light color
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Initializing Light...");
	CNELU::swapBuffers();
	
	Landscape->Landscape.setupStaticLight (ViewerCfg.LandDiffuse, ViewerCfg.LandAmbient, 1.1f);

	// Init collision manager
	CollisionManager.init( &(Landscape->Landscape), 200);
	

	// Preload of TileBank
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Loading TileBank...");
	CNELU::swapBuffers();
	
	for (int ts=0; ts<Landscape->Landscape.TileBank.getTileSetCount (); ts++)
	{
		CTileSet *tileSet=Landscape->Landscape.TileBank.getTileSet (ts);
		sint tl;
		for (tl=0; tl<tileSet->getNumTile128(); tl++)
			Landscape->Landscape.flushTiles (CNELU::Scene->getDriver(), (uint16)tileSet->getTile128(tl), 1);
		for (tl=0; tl<tileSet->getNumTile256(); tl++)
			Landscape->Landscape.flushTiles (CNELU::Scene->getDriver(), (uint16)tileSet->getTile256(tl), 1);
		for (tl=0; tl<CTileSet::count; tl++)
			Landscape->Landscape.flushTiles (CNELU::Scene->getDriver(), (uint16)tileSet->getTransition(tl)->getTile (), 1);
	}
		

	// Build zones.
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Loading zones...");
	CNELU::swapBuffers();
	uint32 i;
	for(i =0; i<ViewerCfg.Zones.size(); i++)
	{
		CZone zone;
		try
		{
			CIFile file(CPath::lookup(ViewerCfg.Zones[i]));
			zone.serial(file);
			file.close();

			// Add it to landscape.
			Landscape->Landscape.addZone(zone);

			// Add it to collision manager.
			CollisionManager.addZone(zone.getZoneId());
		}
		catch(const Exception &e)
		{
			printf("%s\n", e.what ());
		}		
	}

	// Load instance group.
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Loading objects...");
	CNELU::swapBuffers();
	for(i =0; i<ViewerCfg.Igs.size(); i++)
	{
		CInstanceGroup *group = new CInstanceGroup;
		try
		{
			CIFile file(CPath::lookup(ViewerCfg.Igs[i]));
			group->serial(file);
			file.close();

			// Add it to the scene.
			group->addToScene (*CNELU::Scene);
		}
		catch(const Exception &e)
		{
			printf("%s\n", e.what ());
		}		
	}
	
	// Init collision Manager.
	CNELU::clearBuffers(CRGBA(0,0,0));
	ViewerCfg.TextContext.printfAt(0.5f,0.5f,"Initializing collision manager...");
	CNELU::swapBuffers();
	
	
	CollisionManager.setCenter(ViewerCfg.Position);
	ViewerCfg.Position.z = 0.0f;
	CollisionManager.snapToGround( ViewerCfg.Position, 1000.0f );

	

	// hide mouse cursor
	CNELU::Driver->showCursor(false);
#ifdef NL_RELEASE
	CNELU::Driver->setCapture(true);
#endif

	
		
	// Events management
	CNELU::EventServer.addEmitter(CNELU::Driver->getEventEmitter());
	CNELU::AsyncListener.addToServer(CNELU::EventServer);
	
	MoveListener.init(CNELU::Scene, ViewerCfg.Width, ViewerCfg.Height, *CNELU::Camera);
	MoveListener.addToServer(CNELU::EventServer);
	MoveListener.setPos( ViewerCfg.Position );	

	CNELU::Camera->setPerspective (float(80.0*Pi/180.0), 1.33f, 0.1f, 1000.0f);
	
	bool showInfos = true;


	// initializing Z-Clip Far
	float left;
	float right;
	float bottom;
	float top; 
	float znear;
	float zfar;
	CNELU::Camera->getFrustum(left, right, bottom, top, znear, zfar);
	zfar = ViewerCfg.ZFar;
	CNELU::Camera->setFrustum(left, right, bottom, top, znear, zfar);
	
	
	do
	{
		// Time mgt.
		//==========
		static sint64 t0 = (sint64)CTime::getLocalTime();
		static sint64 t1 = (sint64)CTime::getLocalTime();
		static sint64 ts = 0;

		t0 = t1;
		t1 = (sint64)CTime::getLocalTime();
		sint64 dt64 = t1-t0;
		ts += dt64;
		float	dt= ((float)dt64)*0.001f;

	
		CNELU::EventServer.pump();

		
		// Manage movement and collision
		MoveListener.setLocalTime(CTime::getLocalTime());
		CVector oldpos = MoveListener.getPos();
		MoveListener.changeViewMatrix();
		if(MoveListener.getMode()==CMoveListener::WALK)
		{
			CVector pos = MoveListener.getPos();
			CollisionManager.snapToGround( pos , 1000.0f );
			MoveListener.setPos( pos );
		}
		CollisionManager.setCenter(MoveListener.getPos()); 

				
		// Change move mode
		if(CNELU::AsyncListener.isKeyPushed(KeySPACE))
		{
			MoveListener.swapMode();	
		}
		
		
		// Change displaying infos state
		if(CNELU::AsyncListener.isKeyPushed(KeyF1))
		{
			showInfos = !showInfos;
		}


		// Change eyes height
		float eh = MoveListener.getEyesHeight();
		if(CNELU::AsyncListener.isKeyPushed(KeyADD))
		{
			ViewerCfg.EyesHeight.z += 0.1f;
			eh += 0.1f;
		}
		if(CNELU::AsyncListener.isKeyPushed(KeySUBTRACT))
		{
			ViewerCfg.EyesHeight.z -= 0.1f;
			eh -= 0.1f;
		}
		if(ViewerCfg.EyesHeight.z<0.1f) ViewerCfg.EyesHeight.z = 0.1f;
		if(eh<0.1f) eh = 0.1f;
		MoveListener.setEyesHeight(eh);


		// Change TileNear
		float tileNear = Landscape->Landscape.getTileNear();
		if(CNELU::AsyncListener.isKeyPushed(KeyHOME))
			tileNear += tileNearStep;
		if(CNELU::AsyncListener.isKeyPushed(KeyEND))
			tileNear -= tileNearStep;
		if(tileNear<0) tileNear = 0;
		Landscape->Landscape.setTileNear(tileNear);


		// Change Z-Far
		CNELU::Camera->getFrustum(left, right, bottom, top, znear, zfar);
		if(CNELU::AsyncListener.isKeyDown(KeyPRIOR))
			zfar += zFarStep;
		if(CNELU::AsyncListener.isKeyDown(KeyNEXT))
			zfar -= zFarStep;
		if(zfar<0) zfar = 0;
		CNELU::Camera->setFrustum(left, right, bottom, top, znear, zfar);


		// Change Threshold
		float threshold = Landscape->Landscape.getThreshold();
		if(CNELU::AsyncListener.isKeyPushed(KeyINSERT))
			threshold += thresholdStep;
		if(CNELU::AsyncListener.isKeyPushed(KeyDELETE))
			threshold -= thresholdStep;
		if(threshold<0.001f) threshold = 0.001f;
		if(threshold>0.1f) threshold = 0.1f;
		Landscape->Landscape.setThreshold(threshold);


		// Switch between wired and filled scene display
		if(CNELU::AsyncListener.isKeyPushed(KeyF3))
		{
			if (CNELU::Driver->getPolygonMode ()==IDriver::Filled)
				CNELU::Driver->setPolygonMode (IDriver::Line);
			else
				CNELU::Driver->setPolygonMode (IDriver::Filled);
		}

		
		// Switch between mouse move and keyboard-only move
		if(CNELU::AsyncListener.isKeyPushed(KeyRETURN))
		{
			MoveListener.changeControlMode();
		}
		

		
		// Render
		//=======
		CNELU::clearBuffers(ViewerCfg.Background);
		CNELU::Driver->clearZBuffer();
		CNELU::Scene->render();

				
		if(showInfos)
		{
			
			// black top quad
			CDRU::drawQuad(0,0.97f,1.0f,1.0f,*CNELU::Driver,CRGBA(0,0,0),CNELU::Scene->getViewport());

			// black bottom quad
			CDRU::drawQuad(0,0,1.0f,0.03f,*CNELU::Driver,CRGBA(0,0,0),CNELU::Scene->getViewport());

			
			ViewerCfg.TextContext.setFontSize(12);
			ViewerCfg.TextContext.setColor(CRGBA(255,255,255));

			// Display fps.
			ViewerCfg.TextContext.printfAt(0.05f,0.98f,"%.1f fps",1/dt);

			// Display ms
			ViewerCfg.TextContext.printfAt(0.12f,0.98f,"%d ms",dt64);

			// Display Tile Near
			ViewerCfg.TextContext.printfAt(0.75f,0.98f,"Tile Near : %.1f",tileNear);

			//Display moving mode
			ViewerCfg.TextContext.setColor(CRGBA(255,0,0));
			switch(MoveListener.getMode())
			{
				case CMoveListener::WALK :
					ViewerCfg.TextContext.printfAt(0.5f,0.98f,"Walk Mode");
					break;
				case CMoveListener::FREE :
					ViewerCfg.TextContext.printfAt(0.5f,0.98f,"Free-Look Mode");
					break;
				default:
					break;
			}
			ViewerCfg.TextContext.setColor(CRGBA(255,255,255));

			// Display Threshold
			ViewerCfg.TextContext.printfAt(0.3f,0.98f,"Threshold : %.3f",threshold);

			// Display Clip Far
			ViewerCfg.TextContext.printfAt(0.92f,0.98f,"Clip Far : %.1f",zfar);

			
			ViewerCfg.TextContext.setHotSpot(CComputedString::MiddleBottom);
			
			// Display current zone name
			CVector pos = MoveListener.getPos();
			string zoneName = getZoneNameByCoord(pos.x, pos.y);
			ViewerCfg.TextContext.printfAt(0.3f,0.01f,"Zone : %s",zoneName.c_str());

			// Position
			ViewerCfg.TextContext.printfAt(0.1f,0.01f,"Position : %d %d %d",(sint)pos.x,(sint)pos.y,(sint)pos.z);

			// Eyes height
			ViewerCfg.TextContext.printfAt(0.7f,0.01f,"Eyes : %.2f m",ViewerCfg.EyesHeight.z);

			// Display speed in km/h
			ViewerCfg.TextContext.setColor(CRGBA(255,0,0));
			ViewerCfg.TextContext.printfAt(0.5f,0.01f,"Speed : %d km/h",(sint)(MoveListener.getSpeed()*3.6f));
			ViewerCfg.TextContext.setColor(CRGBA(255,255,255));

			// Heading
			sint heading = -(sint)(MoveListener.getRotZ()*180/Pi)%360;
			if(heading<0) heading += 360;
			ViewerCfg.TextContext.printfAt(0.9f,0.01f,"Heading : %d degrees",heading);

			// Display the cool compass.
			displayOrientation();
		}

		
		CNELU::swapBuffers();
		CNELU::screenshot();
	}
	while(!CNELU::AsyncListener.isKeyPushed(KeyESCAPE));





	ViewerCfg.Position = MoveListener.getPos();
	
	CNELU::AsyncListener.removeFromServer(CNELU::EventServer);
	MoveListener.removeFromServer(CNELU::EventServer);

	CNELU::Driver->showCursor(true);
#ifdef NL_RELEASE
	CNELU::Driver->setCapture(false);
#endif
}




/****************************************************************\
							writeConfigFile
\****************************************************************/
void writeConfigFile(const char * configFileName)
{
	FILE * f = fopen(configFileName,"wt");

	if(f==NULL)
	{
		nlerror("can't open file '%s'\n",configFileName);
	}

	fprintf(f,"FullScreen = %d;\n",ViewerCfg.Windowed?0:1);
	fprintf(f,"Width = %d;\n",ViewerCfg.Width);
	fprintf(f,"Height = %d;\n",ViewerCfg.Height);
	fprintf(f,"Depth = %d;\n",ViewerCfg.Depth);
	fprintf(f,"Position = { %f, %f, %f };\n", ViewerCfg.Position.x,ViewerCfg.Position.y,ViewerCfg.Position.z);
	fprintf(f,"EyesHeight = %f;\n", ViewerCfg.EyesHeight.z);
	fprintf(f,"Background = { %d, %d, %d };\n", ViewerCfg.Background.R,ViewerCfg.Background.G,ViewerCfg.Background.B);
	fprintf(f,"ZFar = %f;\n", ViewerCfg.ZFar);

	fprintf(f,"AutoLight = %d;\n", ViewerCfg.AutoLight?1:0);
	fprintf(f,"LightDir = { %f, %f, %f };\n", ViewerCfg.LightDir.x, ViewerCfg.LightDir.y, ViewerCfg.LightDir.z);
	fprintf(f,"LandscapeTileNear = %f;\n", ViewerCfg.LandscapeTileNear);
	fprintf(f,"LandscapeThreshold = %f;\n", ViewerCfg.LandscapeThreshold);
	fprintf(f,"LandscapeNoise = %d;\n", (int)ViewerCfg.LandscapeNoise);
	fprintf(f,"BanksPath = \"%s\";\n",ViewerCfg.BanksPath.c_str());
	fprintf(f,"TilesPath = \"%s\";\n",ViewerCfg.TilesPath.c_str());
	fprintf(f,"UseDDS = \"%d\";\n",ViewerCfg.UseDDS?1:0);
	fprintf(f,"AllPathRelative = \"%d\";\n",ViewerCfg.AllPathRelative?1:0);
	fprintf(f,"Bank = \"%s\";\n",ViewerCfg.Bank.c_str());
	fprintf(f,"ZonesPath = \"%s\";\n",ViewerCfg.ZonesPath.c_str());
	fprintf(f,"IgPath = \"%s\";\n",ViewerCfg.IgPath.c_str());
	fprintf(f,"ShapePath = \"%s\";\n",ViewerCfg.ShapePath.c_str());
	fprintf(f,"MapsPath = \"%s\";\n",ViewerCfg.MapsPath.c_str());
	fprintf(f,"FontPath = \"%s\";\n",ViewerCfg.FontPath.c_str());

	fprintf(f,"HeightFieldName = \"%s\";\n", ViewerCfg.HeightFieldName.c_str());
	fprintf(f,"HeightFieldMaxZ = %f;\n", ViewerCfg.HeightFieldMaxZ);
	fprintf(f,"HeightFieldOriginX = %f;\n", ViewerCfg.HeightFieldOriginX);
	fprintf(f,"HeightFieldOriginY = %f;\n", ViewerCfg.HeightFieldOriginY);
	fprintf(f,"HeightFieldSizeX = %f;\n", ViewerCfg.HeightFieldSizeX);
	fprintf(f,"HeightFieldSizeY = %f;\n", ViewerCfg.HeightFieldSizeY);

	fprintf(f,"LandAmbient = { %d, %d, %d };\n", ViewerCfg.LandAmbient.R,ViewerCfg.LandAmbient.G,ViewerCfg.LandAmbient.B);
	fprintf(f,"LandDiffuse = { %d, %d, %d };\n", ViewerCfg.LandDiffuse.R,ViewerCfg.LandDiffuse.G,ViewerCfg.LandDiffuse.B);

	fprintf(f,"Zones = {\n");
	fprintf(f,"};\n");

	fprintf(f,"Ig = {\n");
	fprintf(f,"};\n");

	fclose(f);
}



/****************************************************************\
						init()
\****************************************************************/
void initViewerConfig(const char * configFileName)
{
	FILE * f = fopen(configFileName,"rt");
	if(f==NULL)
	{
		nlwarning("'%s' not found, default values used", configFileName);
		writeConfigFile(configFileName);
	}
	else fclose (f);
	
	try
	{
		CConfigFile cf;
	
		cf.load(configFileName);
	
		CConfigFile::CVar &cvFullScreen = cf.getVar("FullScreen");
		ViewerCfg.Windowed = cvFullScreen.asInt() ? false : true;
		
		CConfigFile::CVar &cvWidth = cf.getVar("Width");
		ViewerCfg.Width = cvWidth.asInt();

		CConfigFile::CVar &cvHeight = cf.getVar("Height");
		ViewerCfg.Height = cvHeight.asInt();

		CConfigFile::CVar &cvDepth = cf.getVar("Depth");
		ViewerCfg.Depth = cvDepth.asInt();

		CConfigFile::CVar &cvPosition = cf.getVar("Position");
		nlassert(cvPosition.size()==3);
		ViewerCfg.Position.x = cvPosition.asFloat(0);
		ViewerCfg.Position.y = cvPosition.asFloat(1);
		ViewerCfg.Position.z = cvPosition.asFloat(2);

		CConfigFile::CVar &cvEyesHeight = cf.getVar("EyesHeight");
		ViewerCfg.EyesHeight = CVector(0,0,cvEyesHeight.asFloat());

		CConfigFile::CVar &cvBackColor = cf.getVar("Background");
		nlassert(cvBackColor.size()==3);
		ViewerCfg.Background.R = cvBackColor.asInt(0);
		ViewerCfg.Background.G = cvBackColor.asInt(1);
		ViewerCfg.Background.B = cvBackColor.asInt(2);

		CConfigFile::CVar &cvZFar = cf.getVar("ZFar");
		ViewerCfg.ZFar = cvZFar.asFloat();

		CConfigFile::CVar &cvAutoLight = cf.getVar("AutoLight");
		ViewerCfg.AutoLight = cvAutoLight.asInt() ? true : false;

		CConfigFile::CVar &cvLightDir = cf.getVar("LightDir");
		nlassert(cvLightDir.size()==3);
		ViewerCfg.LightDir.x = cvLightDir.asFloat(0);
		ViewerCfg.LightDir.y = cvLightDir.asFloat(1);
		ViewerCfg.LightDir.z = cvLightDir.asFloat(2);

		CConfigFile::CVar &cvLandscapeTileNear = cf.getVar("LandscapeTileNear");
		ViewerCfg.LandscapeTileNear = cvLandscapeTileNear.asFloat();

		CConfigFile::CVar &cvLandscapeThreshold = cf.getVar("LandscapeThreshold");
		ViewerCfg.LandscapeThreshold = cvLandscapeThreshold.asFloat();

		CConfigFile::CVar &cvLandscapeNoise = cf.getVar("LandscapeNoise");
		ViewerCfg.LandscapeNoise = cvLandscapeNoise.asInt() != 0;

		CConfigFile::CVar &cvBanksPath = cf.getVar("BanksPath");
		ViewerCfg.BanksPath = cvBanksPath.asString();

		CConfigFile::CVar &cvTilesPath = cf.getVar("TilesPath");
		ViewerCfg.TilesPath = cvTilesPath.asString();

		CConfigFile::CVar &cvUseDDS = cf.getVar("UseDDS");
		ViewerCfg.UseDDS = cvUseDDS.asInt() ? true : false;

		CConfigFile::CVar &cvAllPathRelative = cf.getVar("AllPathRelative");
		ViewerCfg.AllPathRelative = cvAllPathRelative.asInt() ? true : false;

		CConfigFile::CVar &cvBank = cf.getVar("Bank");
		ViewerCfg.Bank = cvBank.asString();
		
		CConfigFile::CVar &cvZonesPath = cf.getVar("ZonesPath");
		ViewerCfg.ZonesPath = cvZonesPath.asString();
		CPath::addSearchPath(cvZonesPath.asString());

		CConfigFile::CVar &cvIgPath = cf.getVar("IgPath");
		ViewerCfg.IgPath = cvIgPath.asString();
		CPath::addSearchPath(cvIgPath.asString());

		CConfigFile::CVar &cvShapePath = cf.getVar("ShapePath");
		ViewerCfg.ShapePath = cvShapePath.asString();
		CPath::addSearchPath(cvShapePath.asString());

		CConfigFile::CVar &cvMapsPath = cf.getVar("MapsPath");
		ViewerCfg.MapsPath = cvMapsPath.asString();
		CPath::addSearchPath(cvMapsPath.asString());

		CConfigFile::CVar &cvFontPath = cf.getVar("FontPath");
		ViewerCfg.FontPath = cvFontPath.asString();

		CConfigFile::CVar &cvHeightFieldName = cf.getVar("HeightFieldName");
		ViewerCfg.HeightFieldName = cvHeightFieldName.asString();
		
		CConfigFile::CVar &cvHeightFieldMaxZ = cf.getVar("HeightFieldMaxZ");
		ViewerCfg.HeightFieldMaxZ = cvHeightFieldMaxZ.asFloat();

		CConfigFile::CVar &cvHeightFieldOriginX = cf.getVar("HeightFieldOriginX");
		ViewerCfg.HeightFieldOriginX = cvHeightFieldOriginX.asFloat();

		CConfigFile::CVar &cvHeightFieldOriginY = cf.getVar("HeightFieldOriginY");
		ViewerCfg.HeightFieldOriginY = cvHeightFieldOriginY.asFloat();

		CConfigFile::CVar &cvHeightFieldSizeX = cf.getVar("HeightFieldSizeX");
		ViewerCfg.HeightFieldSizeX = cvHeightFieldSizeX.asFloat();

		CConfigFile::CVar &cvHeightFieldSizeY = cf.getVar("HeightFieldSizeY");
		ViewerCfg.HeightFieldSizeY = cvHeightFieldSizeY.asFloat();


		CConfigFile::CVar &cvLandAmb = cf.getVar("LandAmbient");
		nlassert(cvLandAmb.size()==3);
		ViewerCfg.LandAmbient.R = cvLandAmb.asInt(0);
		ViewerCfg.LandAmbient.G = cvLandAmb.asInt(1);
		ViewerCfg.LandAmbient.B = cvLandAmb.asInt(2);

		CConfigFile::CVar &cvLandDiff = cf.getVar("LandDiffuse");
		nlassert(cvLandDiff.size()==3);
		ViewerCfg.LandDiffuse.R = cvLandDiff.asInt(0);
		ViewerCfg.LandDiffuse.G = cvLandDiff.asInt(1);
		ViewerCfg.LandDiffuse.B = cvLandDiff.asInt(2);


		CConfigFile::CVar &cvZones = cf.getVar("Zones");
		for(uint i=0; i<cvZones.size(); i++)
		{
			ViewerCfg.Zones.push_back(cvZones.asString(i));
		}

		CConfigFile::CVar &cvIgs = cf.getVar("Ig");
		for(uint i=0; i<cvIgs.size(); i++)
		{
			ViewerCfg.Igs.push_back(cvIgs.asString(i));
		}

	}
	catch (const EConfigFile &e)
	{
		nlerror("Problem in config file : %s\n", e.what ());
	}

}

/****************************************************************\
							MAIN
\****************************************************************/
#ifdef NL_OS_WINDOWS
int WINAPI WinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, LPSTR cmdline, int /* nCmdShow */)
{
#else
int main(int /* argc */, char ** /* argv */)
{
#endif
	try
	{
		NLMISC::CApplicationContext myApplicationContext;

#ifdef NL_OS_UNIX
		NLMISC::CPath::addSearchPath(NLMISC::CPath::getApplicationDirectory("NeL"));
#endif // NL_OS_UNIX

		NLMISC::CPath::addSearchPath(NL_ZVIEWER_CFG);

		initViewerConfig("zviewer.cfg");

		// Init NELU
		NL3D::CNELU::init(ViewerCfg.Width, ViewerCfg.Height, CViewport(), ViewerCfg.Depth, ViewerCfg.Windowed, EmptyWindow, false, false);
		NL3D::CNELU::Driver->setWindowTitle(ucstring("NeL ZViewer"));
		NL3D::CNELU::Camera->setTransformMode(ITransformable::DirectMatrix);

		// Init the font manager
		ViewerCfg.TextContext.init (CNELU::Driver, &ViewerCfg.FontManager);
		ViewerCfg.TextContext.setFontGenerator(ViewerCfg.FontPath);
		ViewerCfg.TextContext.setFontSize(12);
		ViewerCfg.FontManager.setMaxMemory(2000000);

		displayZones();

		// release nelu
		NL3D::CNELU::release();
	}
	catch (const Exception &e)
	{
		nlerror("main trapped an exception: '%s'", e.what ());
	}

	return 0;
}