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


#include <cstdarg>
#include <cstdlib>
#include <cstdio>
#include "../map2dworld/deMapCompile.hpp"
#include "deFunctors.hpp"
#include "deRenderStates.hpp"
#include "deConsole.hpp"
#include "deScene.hpp"
#include "deFileSystem.hpp"
#include "deFile.hpp"
#include "deBitmap.hpp"
#include "tinyxml/tinyxml.h"

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

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

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\tmap2dexi input.map [-oX] [-sX]\n");
	LogExportMsg("Options:\n");
	LogExportMsg("\t-o : output filename X. to output to test.dexi: -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.dexi, scale = 1/64, unioning enabled\n\n");
}

namespace deMapConverter
{

void UnionAllBrushes(FullData_t * pData, Entity_t * pEntity, TiXmlDocument* xmlDoc, 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;
				}
			}
		}
	}

	// create the destiny3d root element
	TiXmlElement* destiny3d_root = xmlDoc->InsertEndChild(TiXmlElement("destiny3d_content"))->ToElement();
	destiny3d_root->SetAttribute("version", "0.1");
	destiny3d_root->SetAttribute("target", "dworld");

	// children of destiny3d_content root (in order):
	// bitmaps, surfaces, vertices, skeletons, animations, meshes, scenedef, actordef
	// we only handle bitmaps, surfaces, vertices, meshes, scenedef

	TiXmlElement* bitmaps_elem = destiny3d_root->InsertEndChild(TiXmlElement("bitmaps"))->ToElement();
	TiXmlElement* surfaces_elem = destiny3d_root->InsertEndChild(TiXmlElement("surfaces"))->ToElement();
	TiXmlElement* vertices_elem = destiny3d_root->InsertEndChild(TiXmlElement("vertices"))->ToElement();
	TiXmlElement* meshes_elem = destiny3d_root->InsertEndChild(TiXmlElement("meshes"))->ToElement();
	TiXmlElement* scenedef_elem = destiny3d_root->InsertEndChild(TiXmlElement("scenedef"))->ToElement();

	// bitmaps & surfaces
	size = pData->mTextures.size();
	for (i = 0; i < size; ++i)
	{
		char buffer[10] = "sfc";
		TiXmlElement* sfc_elem = surfaces_elem->InsertEndChild(TiXmlElement("surface"))->ToElement();
		itoa(i, buffer+3, 10);
		sfc_elem->SetAttribute("name", buffer);
		IdeRenderTexture* tex = pData->mTextures[i];
		if (tex)
		{
			IdeBitmapProxy* bmp = tex->GetBitmap(0);
			if (bmp)
			{
				// create the bitmap element
				TiXmlElement* bmp_elem = bitmaps_elem->InsertEndChild(TiXmlElement("bitmap"))->ToElement();
				TiXmlElement* file_elem = bmp_elem->InsertEndChild(TiXmlElement("file"))->ToElement();
				deString2 filename = bmp->GetFilename();
				file_elem->SetAttribute("name", filename.const_str());
				bmp_elem->SetAttribute("name", filename.const_str());

				// create the textures element
				/*
				<textures>
					<texture stage="0" textype="2D">
						<bitmap_name>bitmap1</bitmap_name>
					</texture>
				</textures>
				*/
				TiXmlElement* texs_elem = sfc_elem->InsertEndChild(TiXmlElement("textures"))->ToElement();
				TiXmlElement* tex_elem = texs_elem->InsertEndChild(TiXmlElement("texture"))->ToElement();
				tex_elem->SetAttribute("stage", 0);
				tex_elem->SetAttribute("textype", "2D");
				TiXmlElement* bmpname_elem = tex_elem->InsertEndChild(TiXmlElement("bitmap_name"))->ToElement();
				bmpname_elem->InsertEndChild(TiXmlText(filename.const_str()));
			}
		}
	}
	// vertices & meshes
	TiXmlElement* vpool_elem = vertices_elem->InsertEndChild(TiXmlElement("vertexpool"))->ToElement();
	vpool_elem->SetAttribute("name", "vpool1");
	TiXmlElement* mesh_elem = meshes_elem->InsertEndChild(TiXmlElement("trimeshdef"))->ToElement();
	mesh_elem->SetAttribute("name", "worldspawn");
	mesh_elem->SetAttribute("vertexpool", "vpool1");
	TiXmlElement* lastposition = NULL;
	int numpos = 0;
	int numpoints = 0;
	for (i = 0; i < size; ++i)
	{
//	<trimeshsub name="mesh1sub1" winding="clockwise" surface="surface1" facecount="4">
//		<triface v0="0" v1="1" v2="2"/>
		char buffer [21] = "ws";
		TiXmlElement* submesh_elem = mesh_elem->InsertEndChild(TiXmlElement("trimeshsub"))->ToElement();
		itoa(i, buffer+2, 10);
		submesh_elem->SetAttribute("name", buffer);
		submesh_elem->SetAttribute("winding", "counter");
		buffer[0] = 's'; buffer[1] = 'f'; buffer[2] = 'c';
		itoa(i, buffer+3, 10);
		submesh_elem->SetAttribute("surface", buffer);
		deTArray<Face_t*> &faces = texfaces[i];
		int n;
		int numfaces = 0;
		for (n = 0; n < faces.size(); ++n)
		{
			Face_t* face = faces[n];
			while (face)
			{
				int firstvert = numpoints;
				int k;
				for (k = 0; k < face->mVertices.size(); ++k)
				{
					// vertex position
					TiXmlElement* vpos_elem;
					if (lastposition)
						vpos_elem = vpool_elem->InsertAfterChild(lastposition, TiXmlElement("vert_pos"))->ToElement();
					else
						vpos_elem = vpool_elem->InsertEndChild(TiXmlElement("vert_pos"))->ToElement();
					lastposition = vpos_elem;
					TiXmlElement* pos_elem = vpos_elem->InsertEndChild(TiXmlElement("position"))->ToElement();
					deVec3d pos = face->mVertices[k].Pos;
					_snprintf(buffer, 20, "%g", pos.x);
					pos_elem->SetAttribute("x", buffer);
					_snprintf(buffer, 20, "%g", pos.y);
					pos_elem->SetAttribute("y", buffer);
					_snprintf(buffer, 20, "%g", pos.z);
					pos_elem->SetAttribute("z", buffer);

					// vertex entry
					TiXmlElement* vert_elem = vpool_elem->InsertEndChild(TiXmlElement("vertex"))->ToElement();
					vert_elem->SetAttribute("posidx", numpos);
					// normal
					TiXmlElement* norm_elem = vert_elem->InsertEndChild(TiXmlElement("normal"))->ToElement();
					deVec3d norm = face->Normal;
					_snprintf(buffer, 20, "%g", norm.x);
					norm_elem->SetAttribute("nx", buffer);
					_snprintf(buffer, 20, "%g", norm.y);
					norm_elem->SetAttribute("ny", buffer);
					_snprintf(buffer, 20, "%g", norm.z);
					norm_elem->SetAttribute("nz", buffer);
					// texture coordinate
					TiXmlElement* uv_elem = vert_elem->InsertEndChild(TiXmlElement("texcoord"))->ToElement();
					deTexCoord2 uv = face->mVertices[k].TexCoord;
					_snprintf(buffer, 20, "%g", uv.u);
					uv_elem->SetAttribute("u", buffer);
					_snprintf(buffer, 20, "%g", uv.v);
					uv_elem->SetAttribute("v", buffer);
					uv_elem->SetAttribute("w", 0);

					++numpos;
					++numpoints;
				}
				/**/
				// triangle strip
				int left, right, size;
				size = face->mVertices.size();
				left = 0; right = size-1;
				size -= 2;
				for (k = 0; k < size; ++k)
				{
					TiXmlElement* triface = submesh_elem->InsertEndChild(TiXmlElement("triface"))->ToElement();
					if (k % 2)
					{
						triface->SetAttribute("v0", firstvert + right);
						triface->SetAttribute("v1", firstvert + left);
						--right;
						triface->SetAttribute("v2", firstvert + right);
					}
					else
					{
						triface->SetAttribute("v0", firstvert + left);
						++left;
						triface->SetAttribute("v1", firstvert + left);
						triface->SetAttribute("v2", firstvert + right);
					}
					++numfaces;
				}
				/**/
				face = face->pNext;
			}
		}
		submesh_elem->SetAttribute("facecount", numfaces);
	}
	// scenedef
	/*
	<trimeshinstance meshdef="mesh1" instanced="0">
		<transform>
			<position x="0" y="0" z="0"/>
			<rotation qx="0" qy="0" qz="1" qw="0"/>
		</transform>
	</trimeshinstance>
	*/
	TiXmlElement* instance_elem = scenedef_elem->InsertEndChild(TiXmlElement("trimeshinstance"))->ToElement();
	instance_elem->SetAttribute("meshdef", "worldspawn");
	instance_elem->SetAttribute("instanced", 0);

#pragma todo (convert this to spit out XML markup)
#if 0
	size = texfaces.size();
	deTArray<deMeshVertexData> vdata_array(size);
	deTArray<deMeshIndexData> idata_array(size);
	for (i = 0; i < size; ++i)
	{
		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(pData->mWorldID);
		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();
	}
#endif

	// do some cleanup
	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, TiXmlDocument* xmlDoc, bool doUnion, bool doWeld)
{
	Entity_t* pWorldSpawn = FindEntityNamed(pData, "worldspawn");
	if (!pWorldSpawn)
	{
		LogExportMsg("Could not find worldspawn entity\n");
		return deFALSE;
	}

	UnionAllBrushes(pData, pWorldSpawn, xmlDoc, doUnion, doWeld);

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

	return deTRUE;
}

} // namespace deMapConverter

deBoolean map2dexi(const char * inputfilename, TiXmlDocument* xmlDoc,
				   deDouble Scale, IdeRenderTexture * DefaultTex,
				   bool doUnion, bool doWeld)
{
	deMapConverter::FullData_t MapData;
	
	deMapConverter::LoadMAP(inputfilename, Scale, DefaultTex, doUnion, doWeld, MapData);

	deBoolean RetVal = deMapConverter::CompilePolyMesh(&MapData, xmlDoc, doUnion, doWeld);

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

int main(int argc, char* argv[])
{
	int result;
	IdeRenderTexture * pDefaultTex = NULL;
	TiXmlDocument* xmlDoc = 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 DEXI, version %d.%d\n\n", 
			s_VersionMajor, s_VersionMinor);
		if (argc <= 1)
		{
			printusage();
			result = 1;
			goto exit_main;
		}

		if (!Destiny3D_Init("map2dexi"))
		{
			throw mapException("Error initializing Destiny3D\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 += ".dexi";

		// create the XML document
		TiXmlDocument* xmlDoc = new TiXmlDocument(outputFileName.const_str());
		pDefaultTex = IdeRenderState_CreateRenderTexture();

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

		LogExportMsg("Saving XML\n");
		// save the stuff to a dexi file
		if (!xmlDoc->SaveFile())
		{
			throw mapException("Error writing file");
		}

		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:
	delete xmlDoc;
	if (pDefaultTex)
		pDefaultTex->Release();
	LogExportMsg("Destiny3D_ShutDown\n");
	Destiny3D_ShutDown();
	return result;
}