// 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 <nel/misc/path.h>
#include <nel/misc/md5.h>
#include <nel/misc/file.h>
#include "shapes_exporter.h"

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

using namespace NLMISC;
using namespace NL3D;

// function to split a string into several substrings delimited by specified characters
void split(const std::string &str, std::vector<std::string> &tokens, const std::string &delimiters)
{
	// Skip delimiters at beginning.
	std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
	// Find first "non-delimiter".
	std::string::size_type pos     = str.find_first_of(delimiters, lastPos);

	while (std::string::npos != pos || std::string::npos != lastPos)
	{
		// Found a token, add it to the vector.
		tokens.push_back(str.substr(lastPos, pos - lastPos));
		// Skip delimiters.  Note the "not_of"
		lastPos = str.find_first_not_of(delimiters, pos);
		// Find next "non-delimiter"
		pos = str.find_first_of(delimiters, lastPos);
	}
}

static CHashKeyMD5 getNewMD5(const std::string &filename)
{
    CMD5Context	md5ctx;
    CHashKeyMD5 Message_Digest;
	Message_Digest.clear();

	CIFile ifile;
	if (!ifile.open(filename))
	{
		nlwarning ("MD5: Can't open the file '%s'", filename.c_str());
		return Message_Digest;
	}

	md5ctx.init();

	uint8 buffer[1024];
	int bufferSize = 1024;
	sint fs = ifile.getFileSize();
	sint n, read = 0;
	do
	{
		//bs = (int)fread (buffer, 1, bufferSize, fp);
		n = std::min (bufferSize, fs-read);
		//nlinfo ("read %d bytes", n);
		ifile.serialBuffer((uint8 *)buffer, n);

		md5ctx.update(buffer, n);

		read += n;
	}
	while (!ifile.eof());

	ifile.close	();

	md5ctx.final(Message_Digest);

	return Message_Digest;
}

#if defined(NL_OS_WINDOWS) && !defined(_CONSOLE)
sint WINAPI WinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, LPSTR cmdline, int /* nCmdShow */)
{
	// we can specify several shapes on command line
	// so we need to process it
	std::vector<std::string> argv;

	// argv[0] is the fullpath to the executable but since it's not used, we can let it empty
	argv.push_back("");

	// split the whole cmdline into args
	split(cmdline, argv, " ");

	// args number
	int argc = (int)argv.size();

#else
sint main(int argc, char **argv)
{
#endif

	NLMISC::CApplicationContext myApplicationContext;

	ShapesExporter exporter;

	exporter.parseConfigFile("shapes_exporter.cfg");
	exporter.init();

	if (argc > 1)
	{
		// process all files specified on command line
		for(int i = 1; i < argc; ++i)
		{
			// TODO: create a different directory for each of them
			exporter.exportShape(argv[i], CPath::standardizePath(exporter.settings.output_path));
		}
	}
	else
	{
		std::vector<std::string> filenames;

		// search all .max files
		CPath::getPathContent(exporter.settings.input_path, true, false, true, filenames);
//		CPath::getFileList("max", filenames);
		// search .max files corresponding to a filter
		// fix bad textures
		CPath::remapFile("ma_hof_armor_00_tibia_c1.tga", "ma_hof_armor00_tibia_c1.png");
		CPath::remapFile("ma_hof_armor_00_foot_c1.tga", "ma_hof_armor00_foot_c1.png");
		CPath::remapFile("ma_hof_armor_01_botte_c1.tga", "ma_hof_armor01_botte_c1.png");
		CPath::remapFile("ma_hof_armor_01_pied_c1.tga", "ma_hof_armor01_pied_c1.png");

		CPath::remapFile("ma_hom_armor_01_botte_c1.tga", "ma_hom_armor01_botte_c1.png");
		CPath::remapFile("ma_hom_armor_01_pied_c1.tga", "ma_hom_armor01_pied_c1.png");

		CPath::remapFile("hair_spec.tga", "spec_hair.png");

		CPath::remapFile("zo_hof_armor_00_mollet_c1.tga", "zo_hof_armor00_mollet_c1.png");
		CPath::remapFile("zo_hof_armor_00_pied_c1.tga", "zo_hof_armor00_pied_c1.png");

		CPath::remapFile("zo_hom_armor_00_mollet_c1.tga", "zo_hom_armor00_mollet_c1.png");
		CPath::remapFile("zo_hom_armor_00_pied_c1.tga", "zo_hom_armor00_pied_c1.png");
		
//		CPath::getFileListByName("ps", "braziera", filenames);
//		CPath::getFileListByName("ps", "fireworka", filenames);
//		CPath::getFileListByName("ps", "fireworkf", filenames);
//		CPath::getFileListByName("ps", "burntreeexplo", filenames);
//		CPath::getFileListByName("ps", "dustdoor", filenames); // trop court
//		CPath::getFileListByName("ps", "aura_recept", filenames);
//		CPath::getFileListByName("ps", "bloblight", filenames);
		CPath::getFileList("ps", filenames);
		// search all .shape and .ps files
		std::vector<std::string> shapes;
		CPath::getFileList("shape", shapes);
		CPath::getFileList("ps", shapes);
		for(size_t i = 0; i < filenames.size(); ++i)
		{
			if (filenames[i].find(".max") == std::string::npos)
				continue;

			std::string baseFilename = toLower(CFile::getFilenameWithoutExtension(filenames[i]));

			// compute the md5 of .max file
			std::string md5 = getNewMD5(filenames[i]).toString();
			nlinfo("processing %s with md5 = %s", filenames[i].c_str(), md5.c_str());

			// the final directory with images
			std::string output_path = exporter.settings.output_path + md5.substr(0, 2) + "/" + md5;

			// file is an animation
			std::string animation; // CPath::lookup(baseFilename + ".anim", false, false, false);
			// file is a skeleton
			std::string skeleton; // CPath::lookup(baseFilename + ".skel", false, false, false);
			// file is a shape
			std::string shape = CPath::lookup(baseFilename + ".shape", false, false, false);

			// copy .shape file
			if (!shape.empty() && false)
			{
				CIFile in;
				COFile out;

				// create output directory if it doesn't already exists
				if (!CFile::isExists(output_path) && !CFile::createDirectoryTree(output_path))
				{
					nlwarning("can't create %s", output_path.c_str());
					continue;
				}

				if (in.open(shape) && out.open(output_path + "/" + baseFilename + ".shape"))
				{
					uint32 size = in.getFileSize();

					uint8 *buffer = new uint8[size];

					in.serialBuffer(buffer, size);
					out.serialBuffer(buffer, size);

					delete [] buffer;
				}
			}

			// try with several shapes binded on a skeleton
			std::vector<std::string> filtered_shapes;

			if (!animation.empty())
			{
				// render animation
				skeleton = ShapesExporter::findSkeleton(filenames[i]);

				// TODO: take from cfg
//				filtered_shapes.push_back();
				continue;

				// no filter if it's a PS
				filtered_shapes.push_back(filenames[i]);
			}
			else if (!shape.empty())
			{
//				skeleton = ShapesExporter::findSkeleton(shape);
				// render shape
			}
			else if (!skeleton.empty())
			{
				// don't render anything
				continue;
			}
			else
			{
				continue;
/*
				// create a temporary list with shapes which could correspond to .max file
				for(size_t j = 0; j < shapes.size(); ++j)
				{
					// only add files with the same beginning
					if (shapes[j].find(baseFilename) == 0)
					{
						filtered_shapes.push_back(shapes[j]);
					}
				}
*/
				// if there is no corresponding file, we can't render it
				if (filtered_shapes.empty())
				{
					nlwarning("didn't find type of %s", filenames[i].c_str());
					continue;
				}

				// if we found only one shape, we don't need a skeleton
				if (filtered_shapes.size() == 1)
				{
					shape = filtered_shapes[0];
				}
				else
				{
					skeleton = ShapesExporter::findSkeleton(filenames[i]);
				}
			}

			// create output directory if it doesn't already exists
			if (!CFile::isExists(output_path) && !CFile::createDirectoryTree(output_path))
			{
				nlwarning("can't create %s", output_path.c_str());
				continue;
			}

			bool res = false;

			if (!skeleton.empty())
			{
				res = exporter.exportSkeleton(skeleton, filtered_shapes, output_path);
			}
			else
			{
				res = exporter.exportShape(shape, output_path);
			}

			if (res)
			{
				if (!exporter.createThumbnail(filenames[i], output_path))
				{
					nlwarning("can't create thumbnail");
				}
			}
			else
			{
				// an error occured, try to delete directory
				nlwarning("can't export shape");
				CFile::deleteDirectory(output_path);
			}
		}
	}

	return 0;
}