//Filename:-	oldskins.cpp
//
// module containing code for old ID/EF1/CHC style skins...
//
#include "stdafx.h"
#include "includes.h"
#include "files.h"
#include "r_common.h"
//#include "ModViewTreeView.h"
//#include "glm_code.h"
//#include "R_Model.h"
//#include "R_Surface.h"
#include "textures.h"
//#include "TEXT.H"
//#include "sequence.h"
//#include "model.h"

////#include "GenericParser.h"
#include "oldskins.h"


// file format:  (dead simple)
//
//armor_chest,models/players/stormtrooper/torso_legs.tga
//armor_neck_augment,models/players/stormtrooper/torso_legs.tga
//body_torso,models/players/stormtrooper/torso_legs.tga

OldSkinSets_t OldSkinsFound;


// returns NULL for all ok, else error string...
//
LPCSTR OldSkins_Parse(LPCSTR psSkinName, LPCSTR psText)
{
	CString strText(psText);
			strText.Replace(va("%c%c",0x0A,0x0D),va("%c",0x0A));	// file was read in binary mode, so account for 0x0A/0x0D pairs
			strText.Replace("\t","");
			strText.Replace(" ","");
			strText.MakeLower();
	
	while (!strText.IsEmpty())
	{
		CString strThisLine;
		
		int iLoc = strText.Find('\n');

		if (iLoc == -1)
		{
			strThisLine = strText;
			strText.Empty();
		}
		else
		{
			strThisLine = strText.Left(iLoc);
			strText = strText.Mid(iLoc+1);
		}

		if (!strThisLine.IsEmpty())
		{
			iLoc = strThisLine.Find(',');
			if (iLoc != -1)
			{	
				CString strSurfaceName	(strThisLine.Left(iLoc));						
				CString strTGAName		(strThisLine.Mid (iLoc+1));

				strSurfaceName.TrimLeft();
				strSurfaceName.TrimRight();
				strTGAName.TrimLeft();
				strTGAName.TrimRight();

				//
				// new bit to cope with faulty ID skin files, where they have spurious lines like "tag_torso,"
				//
				// ("tag" is "*" in ghoul2)
				//
				if (strSurfaceName.GetAt(0) == '*' || strTGAName.IsEmpty())
				{
					// crap line, so ignore it...
				}
				else
				{
					OldSkinsFound[psSkinName].push_back(StringPairVector_t::value_type((LPCSTR)strSurfaceName,(LPCSTR)strTGAName));
				}
			}
			else
			{
				return va("Error parsing line \"%s\" in skin \"%s\"!",(LPCSTR)strThisLine,psSkinName);
			}			
		}
	}

	return NULL;
}

// converts stuff like "<path>/stormtrooper_blue.skin" to "blue"...
//
LPCSTR OldSkins_FilenameToSkinDescription(string strLocalSkinFileName)
{
	static char sTemp[1024];

	CString strSkinName(Filename_WithoutPath(Filename_WithoutExt(strLocalSkinFileName.c_str())));

	strSkinName.MakeLower();
	int iLoc = strSkinName.Find('_');
	if (iLoc != -1)
	{
		strSkinName = strSkinName.Mid(iLoc+1);
	}

	sprintf(sTemp,strSkinName);
	return sTemp;
}


// returns true if at least one set of skin data was read, else false...
//
static bool OldSkins_Read(LPCSTR psLocalFilename_GLM)
{
	LPCSTR psError = NULL;

	CWaitCursor;

	OldSkinsFound.clear();
	
	LPCSTR psSkinsPath = va("%s%s",gamedir,Filename_PathOnly(psLocalFilename_GLM));

	if (psSkinsPath)
	{
		CString strSkinFileMustContainThisName( Filename_WithoutPath( Filename_WithoutExt( psLocalFilename_GLM )) );	
				strSkinFileMustContainThisName += "_";	// eg: "turret_canon_"
		char **ppsSkinFiles;
		int iSkinFiles;

		// scan for skin files...
		//
		ppsSkinFiles =	//ri.FS_ListFiles( "shaders", ".shader", &iSkinFiles );
						Sys_ListFiles(	psSkinsPath,// const char *directory, 
										".skin",	// const char *extension, 
										NULL,		// char *filter, 
										&iSkinFiles,// int *numfiles, 
										qfalse		// qboolean wantsubs 
										);

		if ( !ppsSkinFiles || !iSkinFiles )
		{
			return false;
		}

		if ( iSkinFiles > MAX_SKIN_FILES ) 
		{
			WarningBox(va("%d skin files found, capping to %d\n\n(tell me if this ever happens -Ste)", iSkinFiles, MAX_SKIN_FILES ));

			iSkinFiles = MAX_SKIN_FILES;
		}

		// load and parse skin files...
		//
		char *buffers[MAX_SKIN_FILES] = {0};
		long iTotalBytesLoaded = 0;
		for ( int i=0; i<iSkinFiles && !psError; i++ )
		{
			char sFileName[MAX_QPATH];

			string strLocalSkinFileName(ppsSkinFiles[i]);

			// only look at skins that begin "modelname_skinvariation" for a given "modelname_"
			if (!strnicmp(strSkinFileMustContainThisName,strLocalSkinFileName.c_str(),strlen(strSkinFileMustContainThisName)))
			{
				Com_sprintf( sFileName, sizeof( sFileName ), "%s/%s", Filename_PathOnly(psLocalFilename_GLM), strLocalSkinFileName.c_str() );
				//ri.Printf( PRINT_ALL, "...loading '%s'\n", sFileName );

				iTotalBytesLoaded += ri.FS_ReadFile( sFileName, (void **)&buffers[i] );

				if ( !buffers[i] ) {
					ri.Error( ERR_DROP, "Couldn't load %s", sFileName );
				}

				char *psDataPtr = buffers[i];

				psError = OldSkins_Parse( OldSkins_FilenameToSkinDescription(strLocalSkinFileName), psDataPtr);
				if (psError)
				{
					ErrorBox(va("Skins_Read(): Error reading file \"%s\"!\n\n( Skins will be ignored for this model )\n\nError was:\n\n%s",sFileName,psError));
					OldSkinsFound.clear();
				}
			}
		}

		// free loaded skin files...
		//
		for ( i=0; i<iSkinFiles; i++ )
		{
			if (buffers[i])
			{
				ri.FS_FreeFile( buffers[i] );
			}
		}

		// ... and file list...
		//		
		Sys_FreeFileList( ppsSkinFiles );
	}

	if (psError)
	{
		return false;
	}

	return !!(OldSkinsFound.size());
}



int DaysSinceCompile(void);
bool OldSkins_FilesExist(LPCSTR psLocalFilename_GLM)
{		
	return OldSkins_Read(psLocalFilename_GLM);
}

bool OldSkins_Read(LPCSTR psLocalFilename_GLM, ModelContainer_t *pContainer)
{
	if (OldSkins_Read(psLocalFilename_GLM))
	{
		pContainer->OldSkinSets = OldSkinsFound;	// huge nested stl-copy

/*		for (OldSkinSets_t::iterator it = OldSkinsFound.begin(); it != OldSkinsFound.end(); ++it)
		{
			string strFile = (*it).first;
			StringPairVector_t &blah = (*it).second;

			for (StringPairVector_t::iterator it2 = blah.begin(); it2 != blah.end(); ++it2)
			{
				string str1 = (*it2).first;
				string str2 = (*it2).second;

				OutputDebugString(va("Skin %s:  %s, %s\n",strFile.c_str(),str1.c_str(),str2.c_str()));
				OutputDebugString(__DATE__);
				OutputDebugString("\n");
			}
		}
*/
		return true;
	}

	return false;
}



// psSkinName = "blue", or "default" etc...
//
// this fills in a modelcontainer's "MaterialBinds" and "MaterialShaders" fields (registering textures etc)
//	based on the skinset pointed at by pContainer->OldSkinSets and strSkinFile
//
bool OldSkins_Apply( ModelContainer_t *pContainer, LPCSTR psSkinName )
{
	CWaitCursor wait;

	bool bReturn = true;

	pContainer->strCurrentSkinFile	= psSkinName;
//	pContainer->strCurrentSkinEthnic= "";

	pContainer->MaterialBinds.clear();
	pContainer->MaterialShaders.clear();

	for (int iSurface = 0; iSurface<pContainer->iNumSurfaces; iSurface++)
	{
		// when we're at this point we know it's GLM model, and that the shader name is in fact a material name...
		//
		LPCSTR psMaterialName = GLMModel_GetSurfaceShaderName( pContainer->hModel, iSurface );

		pContainer->MaterialShaders	[psMaterialName] = "";			// just insert the key for now, so the map<> is legit.
		pContainer->MaterialBinds	[psMaterialName] = (GLuint) 0;	// default to gl-white-notfound texture
	}

//typedef vector< pair<string,string> > StringPairVector_t;
//typedef map<string,StringPairVector_t> OldSkinSets_t;	// map key = (eg) "blue", string-pairs

	OldSkinSets_t::iterator itOldSkins = pContainer->OldSkinSets.find(psSkinName);
	if (itOldSkins != pContainer->OldSkinSets.end())
	{
		StringPairVector_t &StringPairs = (*itOldSkins).second;

		for (int iSkinEntry = 0; iSkinEntry < StringPairs.size(); iSkinEntry++)
		{
			LPCSTR psMaterialName = StringPairs[iSkinEntry].first.c_str();
			LPCSTR psShaderName   = StringPairs[iSkinEntry].second.c_str();

			pContainer->MaterialShaders[psMaterialName] = psShaderName;

//			LPCSTR psLocalTexturePath = R_FindShader( psShaderName );		// shader->texture name
//			if (psLocalTexturePath && strlen(psLocalTexturePath))
//			{
//				TextureHandle_t hTexture = TextureHandle_ForName( psLocalTexturePath );
//
//				if (hTexture == -1)
//				{
//					hTexture = Texture_Load(psLocalTexturePath);
//				}
//
//				GLuint uiBind = Texture_GetGLBind( hTexture );
//
//				pContainer->MaterialBinds[psMaterialName] = uiBind;
//			}
			TextureHandle_t hTexture = Texture_Load(psShaderName);
			GLuint uiBind = Texture_GetGLBind( hTexture );
			pContainer->MaterialBinds[psMaterialName] = uiBind;
		}
	}

	return bReturn;
}



bool OldSkins_ApplyToTree(HTREEITEM hTreeItem_Parent, ModelContainer_t *pContainer)
{
	bool bReturn = false;

	if (pContainer->OldSkinSets.size())
	{
		bReturn = true;

		TreeItemData_t	TreeItemData={0};
						TreeItemData.iItemType		= TREEITEMTYPE_OLDSKINSHEADER;
						TreeItemData.iModelHandle	= pContainer->hModel;

		HTREEITEM hTreeItem_SkinsHeader = ModelTree_InsertItem("Skins available", hTreeItem_Parent, TreeItemData.uiData);

		// skins...
		//
		int iSkinNumber = 0;
		for (OldSkinSets_t::iterator itSkins = pContainer->OldSkinSets.begin(); itSkins != pContainer->OldSkinSets.end(); ++itSkins, iSkinNumber++)
		{
			string strSkinName((*itSkins).first);	// eg "blue"

			TreeItemData.iItemNumber	= iSkinNumber;
			TreeItemData.iItemType		= TREEITEMTYPE_OLDSKIN;
			
			HTREEITEM hTreeItem_ThisSkin = ModelTree_InsertItem(strSkinName.c_str(), hTreeItem_SkinsHeader, TreeItemData.uiData);
/*			
			// body parts...
			//
			StringPairVector_t &StringPairs = (*itSkins).second);
			int iMaterialNumber = 0;
			for (StringPairVector_t::iterator itMaterial = StringPairs.begin(); itMaterial != StringPairs.end(); ++itMaterial, iMaterialNumber++)
			{
				string strMaterialName((*itMaterial).first);	// eg "face"
				string strShaderName  ((*itMaterial).second);	// eg "face"

				TreeItemData.iItemNumber	= iMaterialNumber;
				TreeItemData.iItemType		= TREEITEMTYPE_SKINMATERIAL;

				HTREEITEM hTreeItem_ThisMaterial = ModelTree_InsertItem(strMaterialName.c_str(), hTreeItem_ThisEthnic, TreeItemData.uiData);

				// available shader variants for this material...
				//
				for (int iShaderVariantIndex=0; iShaderVariantIndex<(*itMaterial).second.size(); iShaderVariantIndex++)
				{
					string strShaderVariantName((*itMaterial).second[iShaderVariantIndex]);	// eg "models/characters/average_sleeves/face"

					TreeItemData.iItemNumber	= iShaderVariantIndex;
					TreeItemData.iItemType		= TREEITEMTYPE_SKINMATERIALSHADER;

					HTREEITEM hTreeItem_ThisShaderVariant = ModelTree_InsertItem(strShaderVariantName.c_str(), hTreeItem_ThisMaterial, TreeItemData.uiData);
				}
			}
*/
		}
	}

	return bReturn;
}


// sets up valid skin tables based on first entries loaded, also registers/binds appropriate textures...
//
void OldSkins_ApplyDefault(ModelContainer_t *pContainer)
{
	string strCurrentSkin;

	// look for one called "default" first...
	//
	OldSkinSets_t::iterator itSkins = pContainer->OldSkinSets.find("default");
	if (itSkins != pContainer->OldSkinSets.end())
	{
		strCurrentSkin = (*itSkins).first;
	}
	else
	{
		// just use the first one we have...
		//
		for (itSkins = pContainer->OldSkinSets.begin(); itSkins != pContainer->OldSkinSets.end();)
		{
			strCurrentSkin = (*itSkins).first;
			break;
		}
	}

	// apply it, but don't barf if there wasn't one...
	//
	if (!strCurrentSkin.empty())
	{
		OldSkins_Apply(pContainer, strCurrentSkin.c_str());
	}
}


GLuint OldSkins_GetGLBind(ModelContainer_t *pContainer, LPCSTR psSurfaceName)
{
/*	debug only, do NOT leave in or massive performance hit!!

	int iSize = pContainer->MaterialBinds.size();
	for (MaterialBinds_t::iterator it = pContainer->MaterialBinds.begin(); it != pContainer->MaterialBinds.end(); ++it)
	{
		string str = (*it).first;
		GLuint gl  = (*it).second;

		OutputDebugString(va("%s (%d)\n",str.c_str(), gl));
	}
*/
	return pContainer->MaterialBinds[psSurfaceName];
}


extern bool g_bReportImageLoadErrors;
bool OldSkins_Validate( ModelContainer_t *pContainer, int iSkinNumber )
{
	bool bReturn = true;	
	bool bPREV_bReportImageLoadErrors = g_bReportImageLoadErrors;
										g_bReportImageLoadErrors = false;	

	// build up a list of shaders used...
	//	
	StringSet_t UniqueSkinShaders;	
	//SkinFileMaterialsMissing_t SkinFileMaterialsMissing;
	int iThisSkinIndex = 0;

	CString strSkinFileSurfaceDiscrepancies;
	
	for (OldSkinSets_t::iterator itOldSkins = pContainer->OldSkinSets.begin(); itOldSkins != pContainer->OldSkinSets.end(); ++itOldSkins, iThisSkinIndex++)
	{					
		string strSkinName				= (*itOldSkins).first;
		StringPairVector_t &StringPairs = (*itOldSkins).second;

		if (iSkinNumber == iThisSkinIndex || iSkinNumber == -1)
		{
			for (int iSurface = 0; iSurface < StringPairs.size(); iSurface++)
			{
				string strSurface(StringPairs[iSurface].first);
				string strTGAName(StringPairs[iSurface].second);

				UniqueSkinShaders.insert(UniqueSkinShaders.end(),strTGAName);

				if (iSkinNumber == -1)
				{
					// compare the current material against every other skin file, and report any that don't contain it...
					//					
					for (OldSkinSets_t::iterator itOldSkins_Other = pContainer->OldSkinSets.begin(); itOldSkins_Other != pContainer->OldSkinSets.end(); ++itOldSkins_Other)
					{
						string strSkinName_Other				= (*itOldSkins_Other).first;
						StringPairVector_t &StringPairs_Other	= (*itOldSkins_Other).second;

						for (int iSurface_Other = 0; iSurface_Other < StringPairs_Other.size(); iSurface_Other++)
						{
							string strSurface_Other(StringPairs_Other[iSurface_Other].first);							

							if (strSurface_Other == strSurface)
							{
								break;
							}
						}
						if (iSurface_Other == StringPairs_Other.size())
						{
							// surface not found in this file...
							//
							strSkinFileSurfaceDiscrepancies += va("Surface \"%s\" ( skin \"%s\" ) had no entry in skin \"%s\"\n",strSurface.c_str(),strSkinName.c_str(),strSkinName_Other.c_str());
						}
					}
				}

			}			
		}
	}

	// now process the unique list we've just built...
	//
	CWaitCursor wait;
	string strFoundList;
	string strNotFoundList;
	int iUniqueIndex = 0;
	for (StringSet_t::iterator it = UniqueSkinShaders.begin(); it != UniqueSkinShaders.end(); ++it, iUniqueIndex++)
	{			
		string strShader(*it);

		StatusMessage(va("Processing shader %d/%d: \"%s\"\n",iUniqueIndex,UniqueSkinShaders.size(),strShader.c_str()));

		OutputDebugString(va("Unique: \"%s\"... ",strShader.c_str()));

		int iTextureHandle = Texture_Load(strShader.c_str(), true);	// bInhibitStatus

		GLuint uiGLBind = Texture_GetGLBind( iTextureHandle );

		if (uiGLBind == 0)
		{
			OutputDebugString("NOT FOUND\n");
			
			strNotFoundList += strShader;
			strNotFoundList += "\n";
		}
		else
		{
			OutputDebugString("found\n");

			strFoundList += strShader;
			strFoundList += "\n";
		}
	}

	StatusMessage(NULL);

	
	// Now output results...

	// If too many lines to fit on screen (which is now happening), send 'em to notepad instead...
	//
	// ( tacky way of counting lines...)
	CString strTackyCount(strNotFoundList.c_str());
			strTackyCount += strFoundList.c_str();			

	int iLines = strTackyCount.Replace('\n','?');	// :-)

	#define MAX_BOX_LINES_HERE 50

	// new popup before the other ones...
	//
	if (!strSkinFileSurfaceDiscrepancies.IsEmpty())
	{
		strSkinFileSurfaceDiscrepancies.Insert(0,va("( \"%s\" )\n\nThe following skin file errors occured during cross-checking...\n\n",pContainer->sLocalPathName));

		if (GetYesNo(va("%s\n\nSend copy of report to Notepad?", (LPCSTR) strSkinFileSurfaceDiscrepancies)))
		{
			SendStringToNotepad( strSkinFileSurfaceDiscrepancies, "skinfile_discrepancies.txt");
		}
	}

	if (strNotFoundList.empty())
	{
		if (iLines > MAX_BOX_LINES_HERE)
		{
			if (GetYesNo(va("All shaders found...    :-)\n\nList has > %d entries, send to Notepad?",MAX_BOX_LINES_HERE)))
			{
				SendStringToNotepad(va("All shaders found...    :-)\n\nList follows:\n\n%s",strFoundList.c_str()),"found_shaders.txt");
			}
		}
		else
		{
			InfoBox(va("All shaders found...    :-)\n\nList follows:\n\n%s",strFoundList.c_str()));
		}
	}
	else
	{
		if (iLines > MAX_BOX_LINES_HERE)
		{
			if (GetYesNo(va("Some missing shader, some found, but list is > %d entries, send to Notepad?",MAX_BOX_LINES_HERE)))
			{
				SendStringToNotepad(va("Missing shaders:\n\n%s\n\nFound shaders:\n\n%s",strNotFoundList.c_str(),strFoundList.c_str()),"found_shaders.txt");
			}
		}
		else
		{
			WarningBox(va("Missing shaders:\n\n%s\n\nFound shaders:\n\n%s",strNotFoundList.c_str(),strFoundList.c_str()));
		}
		bReturn = false;
	}


	g_bReportImageLoadErrors = bPREV_bReportImageLoadErrors;
	return bReturn;
}


/*
#include "sys/timeb.h"

typedef struct
{
	char *psName;
	int iDays;
} MonthInfo_t;

MonthInfo_t Months[]=
{
//	{"January",		31},
//	{"February",	28},	// fuck the leap years
//	{"March",		31},
//	{"April",		30},
//	{"May",			31},
//	{"June",		30},
//	{"July",		31},
//	{"August",		31},
//	{"September",	30},
//	{"October", 	31},
//	{"November",	30},
//	{"December",	31}
	{"Jan",	31},
	{"Feb",	28},
	{"Mar",	31},
	{"Apr",	30},
	{"May",	31},
	{"Jun",	30},
	{"Jul",	31},
	{"Aug",	31},
	{"Sep",	30},
	{"Oct", 31},
	{"Nov",	30},
	{"Dec",	31}
};
char sCompileDate[] = {__DATE__};
	
// returns # days since compile.
//
// If return is -ve, then someone's messing with their machine time, or there's an error working it out, so
//	make your own mind up what to do.
//
int DaysSinceCompile(void)
{
	int iCompileDay = 0;
	int iThisDay = 0;

	for (int iPass=0; iPass<2; iPass++)
	{			
		char sTestDate[1024];
		char _month[1024];
		int _day,_year;		

		if (!iPass){
			strcpy(sTestDate,sCompileDate);
		} else {
			time_t ltime;			
			_tzset();			
			time( &ltime );    
			struct tm *today = localtime(&ltime);
			strftime(sTestDate,sizeof(sTestDate),"%b %d %Y",today);			
		}

		sscanf(sTestDate,"%s %d %d",&_month,&_day,&_year);

		int iDay = 0;
		for (int i=0; i<12; i++)
		{
			if (!strnicmp(_month,Months[i].psName,strlen(Months[i].psName)))		
				break;

			iDay += Months[i].iDays;
		}
		if (i==12)
		{
			// ERROR: couldn't determine month
			// 
			return -1;
		}
		iDay += _day;
		iDay += _year*365;

		if (!iPass){
			iCompileDay = iDay;
		} else {
			iThisDay = iDay;
		}
	}

	return iThisDay - iCompileDay;
}

*/
////////////////// eof /////////////////


