#include <iostream>
#include <string>
#include <sys/stat.h>
#include <direct.h>
#include <zlib.h>
#include "IL/il.h"
#include "IL/ilu.h"
#include "IL/ilut.h"

using namespace std;

bool fileExists(string strFilename)
{
	struct stat stFileInfo;
	bool blnReturn;
	int intStat;

	// Attempt to get the file attributes
	intStat = stat(strFilename.c_str(),&stFileInfo);
	if(intStat == 0)
	{
		// We were able to get the file attributes
		// so the file obviously exists.
		blnReturn = true;
	}
	else
	{
		// We were not able to get the file attributes.
		// This may mean that we don't have permission to
		// access the folder which contains this file. If you
		// need to do that level of checking, lookup the
		// return values of stat which will give you
		// more details on why stat failed.
		blnReturn = false;
	}

	return(blnReturn);
}

enum MessageBoxButtons
{
	mb_buttons_abortretryignore,
	mb_buttons_canceltryconinue,
	mb_buttons_okhelp,
	mb_buttons_ok,
	mb_buttons_okcancel,
	mb_buttons_retrycancel,
	mb_buttons_yesno,
	mb_buttons_yesnocancel
};

enum MessageBoxIcon
{
	mb_icon_warning,
	mb_icon_information,
	mb_icon_question,
	mb_icon_error
};

enum MessageBoxResult
{
	mb_result_abort,
	mb_result_cancel,
	mb_result_continue,
	mb_result_ignore,
	mb_result_no,
	mb_result_ok,
	mb_result_retry,
	mb_result_tryagain,
	mb_result_yes
};

MessageBoxResult showMessageBox(string text, string title, MessageBoxIcon icon, MessageBoxButtons buttons)
{
	unsigned int flags = 0;

	switch(icon)
	{
	case mb_icon_warning:
		flags |= MB_ICONWARNING; break;
	case mb_icon_information:
		flags |= MB_ICONINFORMATION; break;
	case mb_icon_question:
		flags |= MB_ICONQUESTION; break;
	case mb_icon_error:
		flags |= MB_ICONERROR; break;
	}

	switch(buttons)
	{
	case mb_buttons_abortretryignore:
		flags |= MB_ABORTRETRYIGNORE; break;
	case mb_buttons_canceltryconinue:
		flags |= MB_CANCELTRYCONTINUE; break;
	case mb_buttons_okhelp:
		flags |= MB_HELP; break;
	case mb_buttons_ok:
		flags |= MB_OK; break;
	case mb_buttons_okcancel:
		flags |= MB_OKCANCEL; break;
	case mb_buttons_retrycancel:
		flags |= MB_RETRYCANCEL; break;
	case mb_buttons_yesno:
		flags |= MB_YESNO; break;
	case mb_buttons_yesnocancel:
		flags |= MB_YESNOCANCEL; break;
	}

	int result = MessageBox(NULL,text.data(),title.data(),flags);

	switch(result)
	{
	case IDABORT:
		return mb_result_abort;
	case IDCANCEL:
		return mb_result_cancel;
	case IDCONTINUE:
		return mb_result_continue;
	case IDIGNORE:
		return mb_result_ignore;
	case IDNO:
		return mb_result_no;
	case IDOK:
		return mb_result_ok;
	case IDRETRY:
		return mb_result_retry;
	case IDTRYAGAIN:
		return mb_result_tryagain;
	case IDYES:
		return mb_result_yes;
	default:
		return mb_result_ok;
	}
}

void convertImage(string filename)
{
	/*
	 * initialize DevIL
	 */
	ilInit();
	iluInit();
	ilutRenderer(ILUT_OPENGL);
	ilutEnable(ILUT_GL_USE_S3TC);
	ilSetInteger(IL_KEEP_DXTC_DATA, IL_TRUE);	
	ILenum error;
	while((error = ilGetError()) != IL_NO_ERROR)
	{
	}

	int lastDot = filename.rfind(".");
	string filenameWithoutExt = filename.substr(0,lastDot);

	/*
	 * load Image
	 */
	ILuint imageName;
	ilGenImages(1,&imageName);
	ilBindImage(imageName);
	if(!ilLoadImage(filename.c_str()))
	{
		cout << "File '" << filename.data() << "' could not be loaded!" << endl;
		ilDeleteImage(imageName);
		return;
	}

	MessageBoxResult result = showMessageBox("Create Mipmaps?", filename.substr(filename.find_last_of("\\/") == string::npos ? 0 : filename.find_last_of("\\/")+1), mb_icon_question, mb_buttons_yesno);
	switch(result)
	{
	case mb_result_yes:
		/*
		* create Mipmaps
		*/
		if(!iluBuildMipmaps())
		{
			cout << "Mipmaps for file '" << filename.data() << "' could not be created!" << endl;
			ilDeleteImage(imageName);
			return;
		}
		break;
	case mb_result_no: break;
	default:
		cout << "Converting file '" << filename.data() << "' aborted." << endl;
		ilDeleteImage(imageName);
		return;
	}



	/*
	 * Delete Image (if exists), query for compression type and save Image
	 */
	if(fileExists(filenameWithoutExt + ".dds"))
		remove((filenameWithoutExt + ".dds").c_str());

	result = showMessageBox("Select Compression mode: Press\n\nYes\t\tfor DXT1\nNo\t\tfor DXT3\nCancel\t\tfor DXT5",filename.substr(filename.find_last_of("\\/") == string::npos ? 0 : filename.find_last_of("\\/")+1),mb_icon_question, mb_buttons_yesnocancel);
	switch(result)
	{
	case mb_result_yes:
		ilSetInteger(IL_DXTC_FORMAT, IL_DXT1); break;
	case mb_result_no:
		ilSetInteger(IL_DXTC_FORMAT, IL_DXT3); break;
	case mb_result_cancel:
		ilSetInteger(IL_DXTC_FORMAT, IL_DXT5); break;
	default:
		ilSetInteger(IL_DXTC_FORMAT, IL_DXT5); break;
	}
	if(!ilSaveImage((filenameWithoutExt + ".dds").data()))
	{
		cout << "File '" << filename.data() << "' could not be converted!" << endl;
		ilDeleteImage(imageName);
		return;
	}
	ilDeleteImage(imageName);
	cout << "File '" << filename.data() << "' successfully converted" << endl;
}

void convertMaterials_v1(gzFile iFile, gzFile oFile)
{
	float color[4];

	int numMaterials;
	gzread(iFile, reinterpret_cast<char*>(&numMaterials), sizeof(int));
	gzwrite(oFile, reinterpret_cast<char*>(&numMaterials), sizeof(int));
	for(int i=0; i<numMaterials; i++)
	{
		// sizeOfName
		int sizeOfName;
		gzread(iFile, reinterpret_cast<char*>(&sizeOfName), sizeof(int));
		gzwrite(oFile, reinterpret_cast<char*>(&sizeOfName), sizeof(int));
		
		// name
		char* name = new char[sizeOfName+1];
		name[sizeOfName] = 0;
		gzread(iFile, name, sizeOfName*sizeof(char));
		gzwrite(oFile, name, sizeOfName*sizeof(char));
		string nameStr(name);
		delete[] name;

		// ambient
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// diffuse
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// specular
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// emissive
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// ks
		float ks;
		gzread(iFile, reinterpret_cast<char*>(&ks), sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&ks), sizeof(float));
			
		// numTextures
		int numTextures;
		gzread(iFile, reinterpret_cast<char*>(&numTextures), sizeof(int));
		gzwrite(oFile, reinterpret_cast<char*>(&numTextures), sizeof(int));

		for(int j=0; j<numTextures; j++)
		{
			// sizeofTextureFilename
			int sizeofTextureFilename;
			gzread(iFile, reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));

			char* textureFilename = new char[sizeofTextureFilename+1];
			textureFilename[sizeofTextureFilename] = 0;
			gzread(iFile, textureFilename, sizeofTextureFilename*sizeof(char));

			// patch string
			string textureFilenameStr(textureFilename);
			delete[] textureFilename;

			size_t lastdot = textureFilenameStr.rfind(".");
			cout << nameStr.c_str() << ": " << textureFilenameStr.c_str() << " --> ";
			textureFilenameStr = (lastdot != string::npos ? textureFilenameStr.substr(0,lastdot) : textureFilenameStr) + string(".dds");
			cout << textureFilenameStr.c_str() << endl;
			sizeofTextureFilename = textureFilenameStr.size();

			gzwrite(oFile, reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));
			gzwrite(oFile, textureFilenameStr.c_str(), sizeOfName*sizeof(char));
		}
	}
}

void convertMaterials_v2(gzFile iFile, gzFile oFile)
{
	float color[4];

	int numMaterials;
	gzread(iFile, reinterpret_cast<char*>(&numMaterials), sizeof(int));
	gzwrite(oFile, reinterpret_cast<char*>(&numMaterials), sizeof(int));
	for(int i=0; i<numMaterials; i++)
	{
		// sizeOfName
		int sizeOfName;
		gzread(iFile, reinterpret_cast<char*>(&sizeOfName), sizeof(int));
		gzwrite(oFile, reinterpret_cast<char*>(&sizeOfName), sizeof(int));
		
		// name
		char* name = new char[sizeOfName+1];
		name[sizeOfName] = 0;
		gzread(iFile, name, sizeOfName*sizeof(char));
		gzwrite(oFile, name, sizeOfName*sizeof(char));
		string nameStr(name);
		delete[] name;

		// ambient
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// diffuse
		gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

		// hasSpecular
		bool hasSpecular;
		gzread(iFile, reinterpret_cast<char*>(&hasSpecular), sizeof(bool));
		gzwrite(oFile, reinterpret_cast<char*>(&hasSpecular), sizeof(bool));

		if(hasSpecular)
		{
			// specular
			gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
			gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));

			// exponent
			float exponent;
			gzread(iFile, reinterpret_cast<char*>(&exponent), sizeof(float));
			gzwrite(oFile, reinterpret_cast<char*>(&exponent), sizeof(float));
		}

		// hasEmissive
		bool hasEmissive;
		gzread(iFile, reinterpret_cast<char*>(&hasEmissive), sizeof(bool));
		gzwrite(oFile, reinterpret_cast<char*>(&hasEmissive), sizeof(bool));

		if(hasEmissive)
		{
			// emissive
			gzread(iFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
			gzwrite(oFile, reinterpret_cast<char*>(&color), 4*sizeof(float));
		}

		
		// numTextures
		int numTextures;
		gzread(iFile, reinterpret_cast<char*>(&numTextures), sizeof(int));
		gzwrite(oFile, reinterpret_cast<char*>(&numTextures), sizeof(int));

		for(int j=0; j<numTextures; j++)
		{
			// sizeofTextureFilename
			int sizeofTextureFilename;
			gzread(iFile, reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));

			char* textureFilename = new char[sizeofTextureFilename+1];
			textureFilename[sizeofTextureFilename] = 0;
			gzread(iFile, textureFilename, sizeofTextureFilename*sizeof(char));

			// patch string
			string textureFilenameStr(textureFilename);
			delete[] textureFilename;

			size_t lastdot = textureFilenameStr.rfind(".");
			cout << nameStr.c_str() << ": " << textureFilenameStr.c_str() << " --> ";
			textureFilenameStr = (lastdot != string::npos ? textureFilenameStr.substr(0,lastdot) : textureFilenameStr) + string(".dds");
			cout << textureFilenameStr.c_str() << endl;
			sizeofTextureFilename = textureFilenameStr.size();

			gzwrite(oFile, reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));
			gzwrite(oFile, textureFilenameStr.c_str(), sizeofTextureFilename*sizeof(char));
		}
	}
}

bool convert(string inFilename, string outFilename)
{
	gzFile iFile = gzopen(inFilename.c_str(),"rb");

	char magicReference[13] = {'p', '0', '5', '3', '1', 'd', '0', 'n', 'm', '0', 'd', '3', 'l'};
	char magic[13];
	gzread(iFile, magic, 13*sizeof(char));
	if(memcmp(magic, magicReference, 13*sizeof(char)) != 0)
	{
		cerr << "Invalid Header found!" << endl;
		return false;
	}

	int version;
	gzread(iFile,reinterpret_cast<char*>(&version), sizeof(int));
	cout << "Found PoseidonScene version " << version << "." << endl;

	cout << "Writing to " << outFilename.c_str() << endl;

	gzFile oFile = gzopen(outFilename.c_str(), "wb");

	gzwrite(oFile, magic, 13*sizeof(char));
	gzwrite(oFile, reinterpret_cast<char*>(&version), sizeof(int));

	switch(version)
	{
	case 1:
		convertMaterials_v1(iFile, oFile);
		break;
	case 2:
		convertMaterials_v2(iFile, oFile);
	}

	// copy the rest of the file in 100KB parts
#define BUF_SIZE 102400
	char buf[BUF_SIZE];
	int read = 0;
	do
	{
		read = gzread(iFile, buf, BUF_SIZE);
		gzwrite(oFile, buf, read);
	}
	while(read == BUF_SIZE);

	gzclose(iFile);
	gzclose(oFile);

	return true;
}

int main(int argc, char** argv)
{
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

	if(argc < 2)
	{
		cout << "Drag and drop poseidon scene onto .exe!" << endl;
		return -1;
	}

	char gzMagic[] = {(char)0x1F, (char)0x8B};
	int gzMagicLength = 2;

	for(int i=1; i<argc; i++)
	{
		char* filename = argv[i];

		if(fileExists(string(filename)))
		{
			FILE* fp = fopen(filename, "rb");
			char buf[10];
			fread(buf,1,10,fp);
			fclose(fp);

			if(memcmp(buf,gzMagic,gzMagicLength) == 0)
			{
				convert(string(filename), string(filename) + ".dds.pos.gz");
			}
			else
			{
				convertImage(string(filename));
			}
		}
		else
			cout << "File '" << filename << "' not found" << endl;
	}
	cout << "Press Enter to Exit!" << endl;
	getchar();
	return 0;
}