//#define LEAK_HUNTING  // comment out to disable leak-hunts


#include <cstdarg>
#include <cstdlib>
#include <cstdio>

#ifdef _DEBUG
#include <crtdbg.h>
#endif

#include "deMapCompile.hpp"
#include "deFunctors.hpp"
#include "deRenderStates.hpp"
#include "deConsole.hpp"
#include "deScene.hpp"
#include "deFileSystem.hpp"
#include "deFile.hpp"
#include "deArray.hpp"
#include "deMesh.hpp"
#include "deSurface.hpp"
#include "deBitmap.hpp"

static int s_VersionMajor = 0;
static int s_VersionMinor = 1;

static void calc_mean_variance(const double array[], long num_elems, double& mean, double& var)
{
	if (num_elems <= 0)
	{
		mean = 0;
		var = 0;
		return;
	}
	mean_variance<double> m(array[0]);
	int i;
	for (i = 0; i < num_elems; ++i)
		m(array[i]);
	mean = m.mean(num_elems);
	var = m.var(num_elems);
}
static void calc_mean_variance(const deVec3d array[], long num_elems, deVec3d& mean, deVec3d& var)
{
	if (num_elems <= 0)
	{
		mean = deZeroVec;
		var = deZeroVec;
		return;
	}
	mean_variance<deDouble> x(array[0].x),y(array[0].y),z(array[0].z);
	int i;
	for (i = 0; i < num_elems; ++i)
	{
		x(array[i].x);
		y(array[i].y);
		z(array[i].z);
	}
	mean.x = x.mean(num_elems);
	mean.y = y.mean(num_elems);
	mean.z = z.mean(num_elems);
	var.x = x.var(num_elems);
	var.y = y.var(num_elems);
	var.z = z.var(num_elems);
}

void LogExportMsg(const char* format, ...)
{
	va_list Marker;
	static char Buffer[2048];
	
	// Write text to string
	// and append to edit control
	va_start(Marker, format);
	// print to screen
//	vprintf(format, Marker);
	// print to string
	_vsnprintf(Buffer, 2048, format, Marker);
	va_end(Marker);
	Buffer[2047] = '\0';

	// print to screen
	deConsoleOut("%s", Buffer);

	// < possibly output to log file >
}

static void printusage()
{
	LogExportMsg("Usage:\n\tmap2dworld input.map [-oX] [-sX]\n");
	LogExportMsg("Options:\n");
	LogExportMsg("\t-o : output filename X. to output to test.dworld: -otest\n");
	LogExportMsg("\t-s : map scale X. to scale by 0.5: -s0.5\n");
	LogExportMsg("\t-u : brush unioning. -u0 disabled, -u1 enabled\n");
	LogExportMsg("\t-w : edge welding. -w0 disabled, -w1 enabled\n");
	LogExportMsg("Defaults: output = input.dworld, scale = 1/64, unioning enabled\n\n");
}


namespace deMapConverter
{
void UnionAllBrushes(FullData_t * pData, 
					 deWorldID worldID, 
					 Entity_t * pEntity, 
					 IdeMesh * pTargetMesh, 
					 bool doUnion, 
					 bool doWeld)
{
	deString2 name = GetEntityData(pEntity, "classname");
	LogExportMsg("Compiling poly mesh for entity '%s'\n", name.const_str());
	// union all brushes to get continuous surfaces
	// prevents z-fighting on coplanar faces, and removes internal faces

	//foreach b1 in brushes
	//	foreach b2 in brushes
	//		if (b1 != b2)
	//			clip b1 using b2
	//		else
	//			flip coplanar behavior

	deTArray<ClippedBrush_t> clippedBrushes(pEntity->mBrushes.size());
	deTArray<ClippedBrush_t>::iterator clipIt = clippedBrushes.begin();

	deTList<Brush_t>::iterator brushIt = pEntity->mBrushes.begin();
	deTList<Brush_t>::iterator stopBrush = pEntity->mBrushes.end();
	for (; brushIt != stopBrush; ++brushIt, ++clipIt)
	{
		CopyBrush(*clipIt, *brushIt);
		if (doUnion)
		{
			bool bClipOnPlane = false;
			deTList<Brush_t>::iterator brushIt2 = pEntity->mBrushes.begin();
			for (; brushIt2 != stopBrush; ++brushIt2)
			{
				if (brushIt2 != brushIt)
					ClipBrush(*clipIt, *brushIt2, bClipOnPlane);
				else
					bClipOnPlane = true;
			}
		}
	}

	if (doWeld)
		WeldVertices(clippedBrushes);

	// compile face data
	int i, size = pData->mTextures.size();
	deTArray< deTArray<Face_t*> > texfaces(size);
	deTArray<long> numTris(size, 0);
	deTArray<long> numVerts(size, 0);
	deTArray<ClippedBrush_t>::iterator stopClip = clippedBrushes.end();
	for (clipIt = clippedBrushes.begin(); clipIt != stopClip; ++clipIt)
	{
		size = clipIt->mFaces.size();
		for (i = 0; i < size; ++i)
		{
			Face_t* face = clipIt->mFaces[i];
			if (face)
			{
				texfaces[face->mTexture].push_back(face);
				while (face)
				{
					numTris[face->mTexture] += face->mVertices.size() - 2;
					numVerts[face->mTexture] += face->mVertices.size();
					face = face->pNext;
				}
			}
		}
	}

	size = texfaces.size();
	deTArray<deMeshVertexData> vdata_array(size);
	deTArray<deMeshIndexData> idata_array(size);
	for (i = 0; i < size; ++i)
	{
		IdeRenderTexture* pTex = pData->mTextures[i];
		if (pTex)
		{
			pTex->SetWorldID(worldID);
			int k;
			for (k = 0; k < TEXTURESTAGE_COUNT; ++k)
			{
				IdeBitmapProxy* proxy = pTex->GetBitmap(k);
				if (proxy)
					proxy->SetWorldID(worldID);
			}
		}
		deMeshVertexData& vdata = vdata_array[i];
		memset(&vdata, 0, sizeof(deMeshVertexData));
		deMeshIndexData& idata = idata_array[i];
		memset(&idata, 0, sizeof(deMeshIndexData));
		
		if (numVerts[i] == 0)
			continue;

		vdata.numpoints = numVerts[i];
		vdata.positions = new deVertex[vdata.numpoints];
		vdata.uvcoords = new deTexCoord2[vdata.numpoints];
		vdata.normals = new deVertex[vdata.numpoints];
		vdata.numtexstages = 1;

		idata.numindices = 3 * numTris[i];
		idata.surface = IdeSurface_CreateSurface();
		idata.surface->SetRenderTexture(pData->mTextures[i]);
		idata.surface->SetWorldID(worldID);
		idata.indices16 = new u16[idata.numindices];
		memset(idata.indices16, 0, idata.numindices*sizeof(u16));
		DE_ASSERT(vdata.numpoints < 65536);
		int n;
		int numpoints = 0;
		int numfaces = 0;
		deTArray<Face_t*> &faces = texfaces[i];
		for (n = 0; n < faces.size(); ++n)
		{
			int k;
			Face_t* face = faces[n];
			while (face)
			{
				int firstvert = numpoints;
				for (k = 0; k < face->mVertices.size(); ++k)
				{
					vdata.positions[numpoints] = deMake_Vertex(face->mVertices[k].Pos);
					vdata.uvcoords[numpoints] = face->mVertices[k].TexCoord;
					vdata.normals[numpoints] = deMake_Vertex(face->Normal);
					/*/
					// triangle fan
					if (0 < k && k < face->mVertices.size()-1)
					{
						idata.indices16[numfaces*3  ] = firstvert;
						idata.indices16[numfaces*3+1] = numpoints;
						idata.indices16[numfaces*3+2] = numpoints+1;
						++numfaces;
					}
					/*/
					++numpoints;
				}
				/*/*/
				// triangle strip
				int left, right, size;
				size = face->mVertices.size();
				left = 0; right = size-1;
				size -= 2;
				for (k = 0; k < size; ++k)
				{
					if (k % 2)
					{
						idata.indices16[numfaces*3  ] = firstvert + right;
						idata.indices16[numfaces*3+1] = firstvert + left;
						--right;
						idata.indices16[numfaces*3+2] = firstvert + right;
					}
					else
					{
						idata.indices16[numfaces*3  ] = firstvert + left;
						++left;
						idata.indices16[numfaces*3+1] = firstvert + left;
						idata.indices16[numfaces*3+2] = firstvert + right;
					}
					++numfaces;
				}
				/*/*/
				face = face->pNext;
			}
		}
		DE_ASSERT(numpoints == vdata.numpoints);
		DE_ASSERT(numfaces*3 == idata.numindices);
		pTargetMesh->AddVertices(vdata, idata.indexoffset);
		pTargetMesh->AddIndices(idata);
	}

	pTargetMesh->BuildFromVertices();

	for (i = 0; i < size; ++i)
	{
		deMeshVertexData& vdata = vdata_array[i];
		deMeshIndexData& idata = idata_array[i];
		delete[] vdata.positions;
		delete[] vdata.uvcoords;
		delete[] vdata.normals;
		delete[] idata.indices16;
		if (idata.surface)
			idata.surface->Release();
	}

	stopClip = clippedBrushes.end();
	for (clipIt = clippedBrushes.begin(); clipIt != stopClip; ++clipIt)
	{
		size = clipIt->mFaces.size();
		for (i = 0; i < size; ++i)
		{
			Face_t* face = clipIt->mFaces[i];
			while (face)
			{
				Face_t * t = face->pNext;
				delete face;
				face = t;
			}
			clipIt->mFaces[i] = NULL;
		}
	}

	// note: could use a uniform grid to quickly look up local brushes
}

deBoolean CompilePolyMesh(FullData_t * pData, 
						  deWorldID worldID, 
						  IdeSceneABT* pNode, 
						  bool doUnion, 
						  bool doWeld)
{
	Entity_t* pWorldSpawn = FindEntityNamed(pData, "worldspawn");
	if (!pWorldSpawn)
	{
		LogExportMsg("Could not find worldspawn entity\n");
		return deFALSE;
	}

	IdeMesh* pTargetMesh = IdeMesh_CreateMesh();
//	pTargetMesh->SetWorldID(pData->mWorldID);
	UnionAllBrushes(pData, worldID, pWorldSpawn, pTargetMesh, doUnion, doWeld);
	deTransform tform; // identity
	pNode->AddGeometry(pTargetMesh, tform);
	pTargetMesh->Release();

#pragma todo (remove all non-relevant external polygons) // cuts down polycount, optional

	return deTRUE;
}

} // namespace deMapConverter

deBoolean map2dworld(const char * inputfilename, IdeSceneABT* pNode, 
					 deDouble Scale, IdeRenderTexture * DefaultTex,
					 deWorldID worldID, bool doUnion, bool doWeld)
{
	deMapConverter::FullData_t MapData;
	
	deMapConverter::LoadMAP(inputfilename, Scale, DefaultTex, doUnion, doWeld, MapData);
	
	deBoolean RetVal = deMapConverter::CompilePolyMesh(&MapData, worldID, pNode, doUnion, doWeld);

	deReleasePtrsFunctor<IdeRenderTexture> f;
	deIter_App(MapData.mTextures.begin(), MapData.mTextures.end(), f);
	
	return RetVal;
}

int main(int argc, char* argv[])
{
	int result;
	IdeFileSystem* pFS = NULL;
	IdeFile* pOutFile = NULL;
	IdeWorld* pWorld = NULL;
	IdeSceneRoom * pRoom = NULL;
	IdeSceneABT * pABT = NULL;
	IdeRenderTexture * pDefaultTex = NULL;
	try
	{
		/*
		double mean,var;
		double dbl_list[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 100000};
		int num_elems = sizeof(dbl_list)/sizeof(double);
		calc_mean_variance(dbl_list, num_elems, mean, var);
		double std_dev = sqrt(var);
		*/
		deConsole_MakeConsole();
		LogExportMsg("Destiny3D Tool: MAP to DWORLD, version %d.%d\n\n", 
			s_VersionMajor, s_VersionMinor);
		if (argc <= 1)
		{
			printusage();
			result = 1;
			goto exit_main;
		}

		if (!Destiny3D_Init("map2dworld"))
		{
			throw mapException("Error initializing Destiny3D\n");
		}
		IdeWorldManager * manager = IdeWorld_GetManager();
		if (!manager)
		{
			throw mapException("Error getting world manager\n");
		}
		deWorldID worldID = manager->CreateWorld();
		if (worldID == 0)
		{
			throw mapException("Error creating world\n");
		}
		
		int k;
		bool doUnion = true;
		bool doWeld = true;
		deDouble Scale = 1.0/64;
		deString2 inputFileName;
		deString2 outputFileName;
		for (k = 1; k < argc; ++k)
		{
			deString2 arg = argv[k];
			deString2 decision = arg.SubString(0,2);
			// -o = output option
			if (decision == "-o")
			{
				outputFileName = arg.SubString(2, arg.size());
				if (outputFileName.size() <= 0)
				{
					deString error;
					error.Format("invalid output option: %s", arg.const_str());
					throw mapException(error);
				}
			}
			// -s = scale option
			else if (decision == "-s")
			{
				int size = arg.size();
				arg = arg.SubString(2, size);
				int divider = arg.Find('/');
				if (divider > 0 && divider < size)
				{
					deString2 numer = arg.SubString(0, divider);
					deString2 denom = arg.SubString(divider+1, arg.size());
					Scale = atof(numer.const_str()) / atof(denom.const_str());
				}
				else
				{
					Scale = 1.0/atof(arg.const_str());
				}
			}
			else if (decision == "-u")
			{
				arg = arg.SubString(2, arg.size());
				doUnion = !!atoi(arg.const_str());
			}
			else if (decision == "-w")
			{
				arg = arg.SubString(2, arg.size());
				doWeld= !!atoi(arg.const_str());
			}
			else
			{
				inputFileName = arg;
				int i = inputFileName.Find('.', 0, deString2::search_backwards);
				if (i > 0)
					outputFileName = inputFileName.SubString(0, i);
				else
					outputFileName = inputFileName;
			}
		}
		if (inputFileName == "")
		{
			printusage();
			result = 1;
			goto exit_main;
		}
		outputFileName += ".dworld";

		pRoom = IdeSceneRoom_CreateRoom();
		pABT = IdeSceneObject_CreateSceneABT();
		pDefaultTex = IdeRenderState_CreateRenderTexture();
		pRoom->SetWorldID(worldID);
		pABT->SetWorldID(worldID);
		pDefaultTex->SetWorldID(worldID);
		pDefaultTex->Claim();
		pRoom->AddSceneObject(pABT);

		LogExportMsg("Compiling %s ...\n\n", inputFileName.const_str());
		if (!map2dworld(inputFileName.const_str(), pABT, Scale, pDefaultTex, worldID, doUnion, doWeld))
		{
			throw mapException("error while compiling map file");
		}

		LogExportMsg("Setting up DWorld File\n");
		// save the stuff to a dworld file
		pWorld = manager->GetWorld(worldID);
		if (!pWorld)
		{
			throw mapException("could not get world");
		}
		pFS = IdeFileSystem_CreateFSReal();
		if (!pFS)
		{
			throw mapException("could not create filesystem");
		}
		pOutFile = pFS->Open(outputFileName.const_str(), (deFile_Open_CreateAlways | deFile_Open_Write));
		if (!pOutFile)
		{
			deString s;
			s.Format("Could not create file: %s\n", outputFileName.const_str());
			throw mapException(s);
		}
		LogExportMsg("Saving DWorld\n");
		if (!pWorld->Serialize(pOutFile))
		{
			throw mapException("Error writing file");
		}
		pOutFile->Close();

		LogExportMsg("Export Complete\n\n");
		result = 0;
	}
	catch (mapException e)
	{
		LogExportMsg("Error: %s\n", e.what.const_str());
		result = 1;
	}
#ifdef NDEBUG
	catch(...)
	{
		LogExportMsg("unknown exception!\n");
		__asm {int 3}
		result = 1;
	}
#endif

exit_main:
	if (pRoom)
		pRoom->Release();
	if (pOutFile)
		pOutFile->Release();
	if (pFS)
		pFS->Release();
	if (pWorld)
		pWorld->Release();
	if (pDefaultTex)
		pDefaultTex->Release();
	LogExportMsg("Destiny3D_ShutDown\n");
	Destiny3D_ShutDown();
	return result;
}
