// 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 <vector>
#include <string>

#include "nel/misc/config_file.h"
#include "nel/misc/file.h"
#include "nel/misc/bitmap.h"
#include "nel/misc/block_memory.h"
#include "nel/misc/i_xml.h"
#include "nel/misc/app_context.h"

#include "nel/ligo/zone_region.h"

#include "nel/3d/scene_group.h"

#ifdef NL_OS_WINDOWS
	#include <windows.h>
#else
	#include <dirent.h> /* for directories functions */
	#include <sys/types.h> 
	#include <unistd.h> /* getcwd, chdir -- replacement for getCurDiretory & setCurDirectory on windows */
#endif


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

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

// ---------------------------------------------------------------------------
// Out a string to the stdout and log.log
void outString (string &sText)
{
	createDebug ();
	InfoLog->displayRaw(sText.c_str());
	//printf ("%s", sText.c_str());
}

// ---------------------------------------------------------------------------
struct SExportOptions
{
	string	InputIGDir;
	string	OutputIGDir;
	float	CellSize;
	string	HeightMapFile1;
	float	ZFactor1;
	string	HeightMapFile2;
	float	ZFactor2;
	string	LandFile;

	// -----------------------------------------------------------------------
	bool load (const string &sFilename)
	{
		FILE * f = fopen (sFilename.c_str(), "rt");
		if (f == NULL)
			return false;
		else 
			fclose (f);

		try
		{			
			CConfigFile cf;

			cf.load (sFilename);

			// Out
			CConfigFile::CVar &cvOutputIGDir = cf.getVar("OutputIGDir");
			OutputIGDir = cvOutputIGDir.asString();

			// In
			CConfigFile::CVar &cvInputIGDir = cf.getVar("InputIGDir");
			InputIGDir = cvInputIGDir.asString();

			CConfigFile::CVar &cvCellSize = cf.getVar("CellSize");
			CellSize = cvCellSize.asFloat();

			CConfigFile::CVar &cvHeightMapFile1 = cf.getVar("HeightMapFile1");
			HeightMapFile1 = cvHeightMapFile1.asString();

			CConfigFile::CVar &cvZFactor1 = cf.getVar("ZFactor1");
			ZFactor1 = cvZFactor1.asFloat();

			CConfigFile::CVar &cvHeightMapFile2 = cf.getVar("HeightMapFile2");
			HeightMapFile2 = cvHeightMapFile2.asString();

			CConfigFile::CVar &cvZFactor2 = cf.getVar("ZFactor2");
			ZFactor2 = cvZFactor2.asFloat();

			CConfigFile::CVar &cvLandFile = cf.getVar("LandFile");
			LandFile = cvLandFile.asString();
		}
		catch (const EConfigFile &e)
		{
			string sTmp = string("ERROR : Error in config file : ") + e.what() + "\n";
			outString (sTmp);
			return false;
		}
		return true;
	}
};


struct CZoneLimits
{
	sint32 _ZoneMinX;
	sint32 _ZoneMaxX;
	sint32 _ZoneMinY;
	sint32 _ZoneMaxY;
};

// ---------------------------------------------------------------------------
#ifdef NL_OS_WINDOWS  // win32 code 
void dir (const string &sFilter, vector<string> &sAllFiles, bool bFullPath)
{
	WIN32_FIND_DATA findData;
	HANDLE hFind;
	char sCurDir[MAX_PATH];
	sAllFiles.clear ();
	GetCurrentDirectory (MAX_PATH, sCurDir);
	hFind = FindFirstFile (("*"+sFilter).c_str(), &findData);
	while (hFind != INVALID_HANDLE_VALUE)
	{
		DWORD res = GetFileAttributes(findData.cFileName);
		if (res != INVALID_FILE_ATTRIBUTES && !(res&FILE_ATTRIBUTE_DIRECTORY))
		{
			if (bFullPath)
				sAllFiles.push_back(string(sCurDir) + "\\" + findData.cFileName);
			else
				sAllFiles.push_back(findData.cFileName);
		}
		if (FindNextFile (hFind, &findData) == 0)
			break;
	}
	FindClose (hFind);
}

void getcwd (char *dir, int length)
{
	GetCurrentDirectoryA (length, dir);
}

void chdir(const char *path)
{
	SetCurrentDirectoryA (path);
}

#else   // posix version  of the void dir(...) function.
void dir (const string &sFilter, vector<string> &sAllFiles, bool bFullPath)
{
	char sCurDir[MAX_PATH];
	DIR* dp = NULL;
	struct dirent *dirp= NULL;

	getcwd ( sCurDir, MAX_PATH ) ;
	sAllFiles.clear ();
	if ( (dp = opendir( sCurDir )) == NULL)
	{
		string sTmp = string("ERROR :  Can't open the dir : \"")+string(sCurDir)+string("\"") ;
		outString ( sTmp ) ;
		return ; 
	}

	while ( (dirp = readdir(dp)) != NULL)
	{
		std:string sFileName = std::string(dirp->d_name) ;
		if (sFileName.substr((sFileName.length()-sFilter.length()),sFilter.length()).find(sFilter)!= std::string::npos )
		{
			if (bFullPath)
				sAllFiles.push_back(string(sCurDir) + "/" + sFileName);
			else
				sAllFiles.push_back(sFileName);  
		}

	}
	closedir(dp);
}
#endif


// ---------------------------------------------------------------------------
CZoneRegion *loadLand (const string &filename)
{
	CZoneRegion *ZoneRegion = NULL;
	try
	{
		CIFile fileIn;
		if (fileIn.open (filename))
		{
			// Xml
			CIXml xml (true);
			nlverify (xml.init (fileIn));

			ZoneRegion = new CZoneRegion;
			ZoneRegion->serial (xml);
		}
		else
		{
			string sTmp = string("Can't open the land file : ") + filename;
			outString (sTmp);
		}
	}
	catch (const Exception& e)
	{
		string sTmp = string("Error in land file : ") + e.what();
		outString (sTmp);
	}
	return ZoneRegion;
}


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

	if( file.open( sFilename ) )
	{
		try
		{
			newIG->serial (file);
		}
		catch (const Exception &)
		{
			// Cannot save the file
			delete newIG;
			return NULL;
		}
	}
	else
	{
		delete newIG;
		return NULL;
	}
	return newIG;
}

// ***************************************************************************
void SaveInstanceGroup (const char* sFilename, CInstanceGroup *pIG)
{
	COFile file;

	if( file.open( sFilename ) )
	{
		try
		{
			pIG->serial (file);
		}
		catch (const Exception &e)
		{
			string stTmp = string(e.what()) ;
			outString(  stTmp );
		}
	}
	else
	{
		string stTemp = string("Couldn't create ") + string(sFilename) ;
		outString( stTemp );
	}
}

/** Get the Z of the height map at the given position
 */
static float  getHeightMapZ(float x, float y, const CZoneLimits &zl, const SExportOptions &options, CBitmap *heightMap1, CBitmap *heightMap2)
{
	float deltaZ = 0.0f, deltaZ2 = 0.0f;
	CRGBAF color;
	sint32 SizeX = zl._ZoneMaxX - zl._ZoneMinX + 1;
	sint32 SizeY = zl._ZoneMaxY - zl._ZoneMinY + 1;

	clamp (x, options.CellSize * zl._ZoneMinX, options.CellSize * (zl._ZoneMaxX + 1));
	clamp (y, options.CellSize * zl._ZoneMinY, options.CellSize * (zl._ZoneMaxY + 1));

	if (heightMap1 != NULL)
	{
		color = heightMap1->getColor (	(x - options.CellSize * zl._ZoneMinX) / (options.CellSize * SizeX), 
										1.0f - ((y - options.CellSize * zl._ZoneMinY) / (options.CellSize * SizeY)));
		color *= 255.f;
		deltaZ = color.A;
		deltaZ = deltaZ - 127.0f; // Median intensity is 127
		deltaZ *= options.ZFactor1;
	}

	if (heightMap2 != NULL)
	{
		color = heightMap2->getColor (	(x - options.CellSize * zl._ZoneMinX) / (options.CellSize * SizeX), 
										1.0f - ((y - options.CellSize * zl._ZoneMinY) / (options.CellSize * SizeY)));
		color *= 255.f;
		deltaZ2 = color.A;
		deltaZ2 = deltaZ2 - 127.0f; // Median intensity is 127
		deltaZ2 *= options.ZFactor2;
	}

	return deltaZ + deltaZ2;	
}

// ---------------------------------------------------------------------------
int main(int nNbArg, char**ppArgs)
{
	if (!NLMISC::INelContext::isContextInitialised())
		new CApplicationContext();

	NL3D_BlockMemoryAssertOnPurge = false;
	char sCurDir[MAX_PATH];
	getcwd (sCurDir, MAX_PATH);

	if (nNbArg != 2)
	{
		printf ("Use : ig_elevation configfile.cfg\n");
		printf ("\nExample of config.cfg\n\n");
		printf ("InputIGDir = \"ig_land_max\";\n");
		printf ("OutputIGDir = \"ig_land_max_elev\";\n");
		printf ("CellSize = 160.0;\n");
		printf ("HeightMapFile1 = \"w:/database/landscape/ligo/jungle/big.tga\";\n");
		printf ("ZFactor1 = 1.0;\n");
		printf ("HeightMapFile2 = \"w:/database/landscape/ligo/jungle/noise.tga\";\n");
		printf ("ZFactor2 = 0.5;\n");
		printf ("LandFile = \"w:/matis.land\";\n");

		return -1;
	}

	SExportOptions options;
	if (!options.load(ppArgs[1]))
	{
		return -1;
	}

	// Get all ig files in the input directory and elevate to the z of the double heightmap

	// Load the land
	CZoneRegion *ZoneRegion = loadLand(options.LandFile);

	CZoneLimits zl;
	if (ZoneRegion)
	{
		zl._ZoneMinX = ZoneRegion->getMinX() < 0	? 0		: ZoneRegion->getMinX();
		zl._ZoneMaxX = ZoneRegion->getMaxX() > 255	? 255	: ZoneRegion->getMaxX();
		zl._ZoneMinY = ZoneRegion->getMinY() > 0	? 0		: ZoneRegion->getMinY();
		zl._ZoneMaxY = ZoneRegion->getMaxY() < -255 ? -255	: ZoneRegion->getMaxY();
	}
	else
	{
		nlwarning("A ligo .land file cannot be found");
		zl._ZoneMinX = 0;
		zl._ZoneMaxX = 255;
		zl._ZoneMinY = 0;
		zl._ZoneMaxY = 255;
	}

	// Load the 2 height maps
	CBitmap *HeightMap1 = NULL;
	if (!options.HeightMapFile1.empty())
	{
		HeightMap1 = new CBitmap;
		try 
		{
			CIFile inFile;
			if (inFile.open(options.HeightMapFile1))
			{
				HeightMap1->load (inFile);
			}
			else
			{
				string sTmp = string("Couldn't not open ")+string(options.HeightMapFile1)
					+string(" : heightmap 1 map ignored");
				outString(sTmp);
				delete HeightMap1;
				HeightMap1 = NULL;
			}
		}
		catch (const Exception &e)
		{
			string sTmp = string("Cant load height map : ") + options.HeightMapFile1 + " : " + e.what();
			outString (sTmp);
			delete HeightMap1;
			HeightMap1 = NULL;
		}
	}
	CBitmap *HeightMap2 = NULL;
	if (!options.HeightMapFile2.empty())
	{
		HeightMap2 = new CBitmap;
		try 
		{
			CIFile inFile;
			if (inFile.open(options.HeightMapFile2))
			{
				HeightMap2->load (inFile);
			}
			else
			{
				string sTmp = string("Couldn't not open ")+string(options.HeightMapFile2)
					+string(" : heightmap 2 map ignored\n");
				outString(sTmp);
				delete HeightMap2;
				HeightMap2 = NULL;
			}
		}
		catch (const Exception &e)
		{
			string sTmp = string("Cant load height map : ") + options.HeightMapFile2 + " : " + e.what() + "\n";
			outString (sTmp);
			delete HeightMap2;
			HeightMap1 = NULL;
		}
	}

	// Get all files
	vector<string> vAllFiles;
	chdir (options.InputIGDir.c_str());
	dir (".ig", vAllFiles, false);
	chdir (sCurDir);

	for (uint32 i = 0; i < vAllFiles.size(); ++i)
	{
		chdir (options.InputIGDir.c_str());
		CInstanceGroup *pIG = LoadInstanceGroup (vAllFiles[i].c_str());
		chdir (sCurDir);
		if (pIG != NULL)
		{
			bool realTimeSunContribution = pIG->getRealTimeSunContribution();
			// For all instances !!!
			CVector vGlobalPos;
			CInstanceGroup::TInstanceArray IA;
			vector<CCluster> Clusters;
			vector<CPortal> Portals;
			vector<CPointLightNamed> PLN;
			pIG->retrieve (vGlobalPos, IA, Clusters, Portals, PLN);

			if (IA.empty() && PLN.empty() && Portals.empty() && Clusters.empty()) continue;


			uint k;

			// elevate instance
			for(k = 0; k < IA.size(); ++k)
			{
				CVector instancePos = vGlobalPos + IA[k].Pos;
				IA[k].Pos.z += getHeightMapZ(instancePos.x, instancePos.y, zl, options, HeightMap1, HeightMap2);
			}

			// lights
			for(k = 0; k < PLN.size(); ++k)
			{
				CVector lightPos = vGlobalPos + PLN[k].getPosition();
				PLN[k].setPosition( PLN[k].getPosition() + getHeightMapZ(lightPos.x, lightPos.y, zl, options, HeightMap1, HeightMap2) * CVector::K);
			}

			// portals
			std::vector<CVector> portal;
			for(k = 0; k < Portals.size(); ++k)
			{
				Portals[k].getPoly(portal);
				if (portal.empty())
				{
					nlwarning("Empty portal found");
					continue;
				}
				// compute mean position of the poly
				CVector meanPos(0, 0, 0);
				uint l;
				for(l = 0; l < portal.size(); ++l)
					meanPos += portal[l];
				meanPos /= (float) portal.size();
				meanPos += vGlobalPos;
				float z = getHeightMapZ(meanPos.x, meanPos.y, zl, options, HeightMap1, HeightMap2);
				for(l = 0; l < portal.size(); ++l)
				{
					portal[l].z += z;
				}
				Portals[k].setPoly(portal);
			}

			// clusters
			std::vector<CPlane> volume;
			CMatrix transMatrix;
			for(k = 0; k < Clusters.size(); ++k)
			{
				CVector clusterPos = vGlobalPos + Clusters[k].getBBox().getCenter();
				float z = getHeightMapZ(clusterPos.x, clusterPos.y, zl, options, HeightMap1, HeightMap2);
				transMatrix.setPos(z * CVector::K);
				Clusters[k].applyMatrix(transMatrix);
			}

			CInstanceGroup *pIGout = new CInstanceGroup;
			pIGout->build (vGlobalPos, IA, Clusters, Portals, PLN);
			pIGout->enableRealTimeSunContribution(realTimeSunContribution);


			chdir (options.OutputIGDir.c_str());
			SaveInstanceGroup (vAllFiles[i].c_str(), pIGout);
			chdir (sCurDir);
			delete pIG;
		}
	}

	return 1;
}