//
// modeltool.cpp : Defines the entry point for the console application.
//
//
// If you're doing a command line compilation, and
// the Makefile is broken, then this may do the job:
//
//   g++ -o modeltool modeltool.cxx  -I../../include ../../src/common/libCommon.a
//

/* system headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include <map>
#include <math.h>

/* common headers */
#include "SimpleTextUtils.h"

#include "model.h"
#include "wavefrontOBJ.h"
#include "Q3BSP.h"

// globals/
const char VersionString[] = "ModelTool v1.7  (WaveFront OBJ/BZW to BZFlag BZW converter)";

std::string texdir = "";
std::string groupName = "";
bool useMaterials = true;
bool useAmbient = true;
bool useDiffuse = true;
bool useSpecular = true;
bool useShininess = true;
bool useEmission = true;
bool useNormals = true;
bool useTexcoords = true;
bool flipYZ = false;
bool useSmoothBounce = false;
float shineFactor = 1.0f;
bool useRicoMat = false;

float maxShineExponent = 128.0f; // OpenGL minimum shininess


float globalScale = 1.0f;
float globalShift[3] = {0,0,0};
std::vector<std::string> bspMaterialSkips; // materials to skip in a bsp map


static void writeBZW  ( CModel &model, std::string file )
{
	if (model.meshes.size() < 1 )
		return;

	FILE *fp = fopen (file.c_str(),"wt");
	if (!fp)
		return;

	if (useMaterials) {
	  tmMaterialMap::iterator materialItr = model.materials.begin();
		while ( materialItr != model.materials.end() )
		{
			CMaterial &material = materialItr->second;
			fprintf (fp,"material\n  name %s\n",materialItr->first.c_str());
			if ( material.texture.size()) {
			  std::string texName = texdir + material.texture;
				// change the extension to png
				const char *p = strrchr(texName.c_str(), '.');
				if (p) {
					texName.resize(p - texName.c_str());
					texName += ".png";
				} else {
				  texName += ".png";
				}
			  fprintf (fp,"  texture %s\n", texName.c_str());
			} else {
				fprintf (fp,"  notextures\n");
			}
			if (useAmbient)
				fprintf (fp,"  ambient %f %f %f %f\n", material.ambient[0], material.ambient[1], material.ambient[2], material.ambient[3]);
			if (useDiffuse)
				fprintf (fp,"  diffuse %f %f %f %f\n", material.diffuse[0], material.diffuse[1], material.diffuse[2], material.diffuse[3]);
			if (useSpecular)
				fprintf (fp,"  specular %f %f %f %f\n", material.specular[0], material.specular[1], material.specular[2], material.specular[3]);
			if (useShininess)
				fprintf (fp,"  shininess %f\n", material.shine);
			if (useEmission)
				fprintf (fp,"  emission %f %f %f %f\n", material.emission[0], material.emission[1], material.emission[2], material.emission[3]);

			fprintf (fp,"end\n\n");

			materialItr++;
		}
		fprintf (fp,"\n");
	}

	if (groupName.size() > 0) {
	  fprintf (fp, "define %s\n", groupName.c_str());
	}

	tvMeshList::iterator	meshItr = model.meshes.begin();

	while ( meshItr != model.meshes.end() )
	{
		CMesh	&mesh = *meshItr;

		mesh.reindex();

		fprintf (fp,"mesh # %s\n", mesh.name.c_str());

		fprintf (fp,"# vertices: %d\n", (int)mesh.verts.size());
		fprintf (fp,"# normals: %d\n", (int)mesh.normals.size());
		fprintf (fp,"# texcoords: %d\n", (int)mesh.texCoords.size());
		fprintf (fp,"# faces: %d\n\n", (int) mesh.faces.size());

		if (useSmoothBounce)
			fprintf (fp,"  smoothbounce\n");

		tvVertList::iterator vertItr = mesh.verts.begin();
		while ( vertItr != mesh.verts.end() )
		{
			fprintf (fp,"  vertex %f %f %f\n", vertItr->x*globalScale+globalShift[0],vertItr->y*globalScale+globalShift[1],vertItr->z*globalScale+globalShift[2]);
			vertItr++;
		}

		vertItr = mesh.normals.begin();
		while ( vertItr != mesh.normals.end() )
		{
			// normalise all normals before writing them
			float dist = sqrtf(vertItr->x*vertItr->x+vertItr->y*vertItr->y+vertItr->z*vertItr->z);
			fprintf (fp,"  normal %f %f %f\n", vertItr->x/dist,vertItr->y/dist,vertItr->z/dist);
			vertItr++;
		}

		tvTexCoordList::iterator uvItr = mesh.texCoords.begin();
		while ( uvItr != mesh.texCoords.end() )
		{
			fprintf (fp,"  texcoord %f %f\n", uvItr->u,uvItr->v);
			uvItr++;
		}


		tvFaceList::iterator	faceItr = mesh.faces.begin();
		while ( faceItr != mesh.faces.end() )
		{
			CFace	&face = *faceItr;

			fprintf (fp,"  face\n");

			tvIndexList::iterator	indexItr = face.verts.begin();
			fprintf (fp,"    vertices");
			while ( indexItr != face.verts.end() )
				fprintf(fp," %d",*indexItr++);
			fprintf (fp,"\n");

      if (useNormals && (face.normals.size() > 0)) {
				indexItr = face.normals.begin();
				fprintf (fp,"    normals");
				while ( indexItr != face.normals.end() )
					fprintf(fp," %d",*indexItr++);
				fprintf (fp,"\n");
			}

      if (useTexcoords && (face.texCoords.size() > 0)) {
				indexItr = face.texCoords.begin();
				fprintf (fp,"    texcoords");
				while ( indexItr != face.texCoords.end() )
					fprintf(fp," %d",*indexItr++);
				fprintf (fp,"\n");
			}

			if (useMaterials && (face.material.size() > 0)) {
			  fprintf (fp, "    matref %s\n", face.material.c_str());
			}

			if (useRicoMat && face.material.size() > 0 && strstr(face.material.c_str(),"rico_") != NULL) {
				fprintf (fp, "    ricochetn");
			}

			fprintf (fp,"  endface\n");

			faceItr++;
		}

		fprintf (fp,"end\n\n");
		meshItr++;
	}

	if (groupName.size() > 0) {
	  fprintf (fp, "enddef # %s\n", groupName.c_str());
	}

	// do the custom objects.

	for ( unsigned int i = 0; i < model.customObjects.size(); i++ )
	{
		fprintf (fp, "%s\n", model.customObjects[i].name.c_str());
		for (unsigned int j = 0; j < model.customObjects[i].params.size(); j++ )
			fprintf (fp, "  %s\n", model.customObjects[i].params[j].c_str());
		fprintf (fp, "end\n\n");
	}

	fclose(fp);
}

static int  dumpUsage ( char *exeName, const char* reason )
{
	printf("\n%s\n\n", VersionString);
	printf("error: %s\n\n",reason);
	printf("usage: %s <input_file_name> [options]\n\n", exeName);
	printf("       -g <name>  : use group definition\n");
	printf("       -tx <dir>  : set texture prefix\n");
	printf("       -sm	: use the smoothbounce property\n");
	printf("       -yz	: flip y and z coordinates\n");
	printf("       -n	 : disable normals\n");
	printf("       -t	 : disable texture coordinates\n");
	printf("       -m	 : disable materials\n");
	printf("       -a	 : disable ambient coloring\n");
	printf("       -d	 : disable diffuse coloring\n");
	printf("       -s	 : disable specular coloring\n");
	printf("       -sh	: disable shininess\n");
	printf("       -sf <val>  : shine multiplier\n");
	printf("       -e	 : disable emission coloring\n\n");
	printf("       -gx <val>  : scale the model by this factor\n\n");
	printf("       -gsx <val> : shift the map by this value in X\n\n");
	printf("       -gsy <val> : shift the map by this value in Y\n\n");
	printf("       -gsz <val> : shift the map by this value in Z\n\n");
	printf("       -bspskip <val> : skip faces with this material when importing a bsp\n\n");
	printf("       -usericomat : faces that use materials that start with rico_ will have the ricochet property\n\n");
	return 1;
}

int main(int argc, char* argv[])
{
	std::string input;
	std::string extenstion = "OBJ";
	std::string output;

	// make sure we have all the right stuff
	if ( argc < 2)
		return dumpUsage(argv[0],"No input file specified");

	// get the input file
	// check argv for
	if ( argv[1][0] == '\"' )
	{
		argv[1]++;
		argv[1][strlen(argv[1])-1] = 0;
	}
	input = argv[1];

	// see if it has an extenstion
	char *p = strrchr(argv[1],'.');
	if (p)
		extenstion = p+1;

	if (!p) {
		output = input + ".bzw";
	} else {
		*p = '\0'; // clip the old extension
		output = argv[1] + std::string(".bzw");
	}

	for ( int i = 2; i < argc; i++) {
		std::string command = argv[i];
		command = SimpleTextUtils::tolower(command);

		if (command == "-yz") {
			flipYZ = true;
		}
		else if (command == "-g") {
		  if ((i + 1) < argc) {
			  i++;
			  groupName = argv[i];
			} else {
			  printf ("missing -g argument\n");
			}
		}
		else if (command == "-tx") {
		  if ((i + 1) < argc) {
			  i++;
			  texdir = argv[i];
			  if (texdir[texdir.size()] != '/') {
			    texdir += '/';
				}
			} else {
			  printf ("missing -tx argument\n");
			}
		}
		else if (command == "-ricomat") {
			useRicoMat = true;
		}
		else if (command == "-sm") {
			useSmoothBounce = true;
		}
		else if (command == "-n") {
			useNormals = false;
		}
		else if (command == "-t") {
			useTexcoords = false;
		}
		else if (command == "-m") {
			useMaterials = false;
		}
		else if (command == "-a") {
			useAmbient = false;
		}
		else if (command == "-d") {
			useDiffuse = false;
		}
		else if (command == "-s") {
			useSpecular = false;
		}
		else if (command == "-sh") {
			useShininess = false;
		}
		else if (command == "-sf") {
		  if ((i + 1) < argc) {
			  i++;
			  shineFactor = (float)atof(argv[i]);
			} else {
			  printf ("missing -sf argument\n");
			}
		}
		else if (command == "-e") {
			useEmission = false;
		}
		else if (command == "-gx") {
			if ((i + 1) < argc) {
				i++;
				globalScale = (float)atof(argv[i]);
			} else {
				printf ("missing -gx argument\n");
			}
		}
		else if (command == "-gsx") {
			if ((i + 1) < argc) {
				i++;
				globalShift[0] = (float)atof(argv[i]);
			} else {
				printf ("missing -gsx argument\n");
			}
		}
		else if (command == "-gsy") {
			if ((i + 1) < argc) {
				i++;
				globalShift[1] = (float)atof(argv[i]);
			} else {
				printf ("missing -gsy argument\n");
			}
		}
		else if (command == "-gsz") {
			if ((i + 1) < argc) {
				i++;
				globalShift[2] = (float)atof(argv[i]);
			} else {
				printf ("missing -gsz argument\n");
			}
		}
		else if (command == "-bspskip") {
			if ((i + 1) < argc) {
				i++;
				bspMaterialSkips.push_back(std::string(argv[i]));
			} else {
				printf ("missing -bspskip argument\n");
			}
		}
	}
	// make a model

	CModel	model;

	if ( SimpleTextUtils::tolower(extenstion) == "obj" )
	{
		readOBJ(model,input);
	}
	else if ( SimpleTextUtils::tolower(extenstion) == "bsp" )
	{
		Quake3Level	level;
		level.loadFromFile(input.c_str());
		level.dumpToModel(model);
	}
	else
	{
		printf("unknown input format\n");
		return 2;
	}

	model.pushAboveAxis(eZAxis);

	if (model.meshes.size() > 0) {
		writeBZW(model,output);
		printf("%s file %s converted to BZW as %s\n", extenstion.c_str(),input.c_str(),output.c_str());
	} else {
		printf("no valid meshes written from %s\n", input.c_str());
	}

	return 0;
}

static int getNewIndex ( CVertex &vert, tvVertList &vertList )
{
	tvVertList::iterator itr = vertList.begin();

	int count = 0;
	while ( itr != vertList.end() )
	{
		if ( itr->same(vert) )
			return count;
		count++;
		itr++;
	}
	vertList.push_back(vert);
	return count;
}

static int getNewIndex ( CTexCoord &vert, tvTexCoordList &vertList )
{
	tvTexCoordList::iterator itr = vertList.begin();

	int count = 0;
	while ( itr != vertList.end() )
	{
		if ( itr->same(vert) )
			return count;
		count++;
		itr++;
	}
	vertList.push_back(vert);
	return count;
}

void CMesh::reindex ( void )
{
	tvVertList		temp_verts;
	tvVertList		temp_normals;
	tvTexCoordList	temp_texCoords;

	tvFaceList::iterator	faceItr = faces.begin();
	while ( faceItr != faces.end() )
	{
		CFace	&face = *faceItr;
		CFace	newFace;

		newFace.material = face.material;

		tvIndexList::iterator indexItr = face.verts.begin();
		while ( indexItr != face.verts.end() )
			newFace.verts.push_back(getNewIndex(verts[*indexItr++],temp_verts));

		indexItr = face.normals.begin();
		while ( indexItr != face.normals.end() )
			newFace.normals.push_back(getNewIndex(normals[*indexItr++],temp_normals));

		indexItr = face.texCoords.begin();
		while ( indexItr != face.texCoords.end() )
			newFace.texCoords.push_back(getNewIndex(texCoords[*indexItr++],temp_texCoords));

		*faceItr = newFace;
		faceItr++;
	}
	verts = temp_verts;
	normals = temp_normals;
	texCoords = temp_texCoords;
}


