//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "stdafx.h"
#include <direct.h>
#include <io.h>
#include <WorldSize.h>
#include "Gameconfig.h"
#include "GlobalFunctions.h"
#include "fgdlib/HelperInfo.h"
#include "hammer.h"
#include "KeyValues.h"
#include "MapDoc.h"
#include "MapDoc.h"
#include "MapEntity.h"
#include "MapInstance.h"
#include "MapWorld.h"
#include "filesystem_tools.h"
#include "TextureSystem.h"
#include "tier1/strtools.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#pragma warning(disable:4244)


const int MAX_ERRORS = 5;


GameData *pGD;
CGameConfig g_DefaultGameConfig;
CGameConfig *g_pGameConfig = &g_DefaultGameConfig;


float g_MAX_MAP_COORD = 4096;
float g_MIN_MAP_COORD = -4096;


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CGameConfig *CGameConfig::GetActiveGame(void)
{
	return g_pGameConfig;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pGame - 
//-----------------------------------------------------------------------------
void CGameConfig::SetActiveGame(CGameConfig *pGame)
{
	if (pGame != NULL)
	{
		g_pGameConfig = pGame;
		pGD = &pGame->GD;

		if (pGame->mapformat == mfHalfLife)
		{
			g_MAX_MAP_COORD = 4096;
			g_MIN_MAP_COORD = -4096;
		}
		else
		{
			g_MAX_MAP_COORD = pGD->GetMaxMapCoord();
			g_MIN_MAP_COORD = pGD->GetMinMapCoord();
		}
	}
	else
	{
		g_pGameConfig = &g_DefaultGameConfig;
		pGD = NULL;

		g_MAX_MAP_COORD = 4096;
		g_MIN_MAP_COORD = -4096;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Constructor. Maintains a static	counter uniquely identifying each
//			game configuration.
//-----------------------------------------------------------------------------
CGameConfig::CGameConfig(void)
{
	nGDFiles = 0;
	textureformat = tfNone;
	m_fDefaultTextureScale = DEFAULT_TEXTURE_SCALE;
	m_nDefaultLightmapScale = DEFAULT_LIGHTMAP_SCALE;
	m_MaterialExcludeCount = 0;

	memset(szName, 0, sizeof(szName));
	memset(szExecutable, 0, sizeof(szExecutable));
	memset(szDefaultPoint, 0, sizeof(szDefaultPoint));
	memset(szDefaultSolid, 0, sizeof(szDefaultSolid));
	memset(szBSP, 0, sizeof(szBSP));
	memset(szLIGHT, 0, sizeof(szLIGHT));
	memset(szVIS, 0, sizeof(szVIS));
	memset(szMapDir, 0, sizeof(szMapDir));
	memset(m_szGameExeDir, 0, sizeof(m_szGameExeDir));
	memset(szBSPDir, 0, sizeof(szBSPDir));
	memset(m_szModDir, 0, sizeof(m_szModDir));
	strcpy(m_szCordonTexture, "BLACK");

	m_szSteamDir[0] = '\0';
	m_szSteamAppID[0] = '\0';

	static DWORD __dwID = 0;
	dwID = __dwID++;
}


//-----------------------------------------------------------------------------
// Purpose: Imports an old binary GameCfg.wc file.
// Input  : file - 
//			fVersion - 
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
BOOL CGameConfig::Import(std::fstream& file, float fVersion)
{
	file.read(szName, sizeof szName);
	file.read((char*)&nGDFiles, sizeof nGDFiles);
	file.read((char*)&textureformat, sizeof textureformat);

	if (fVersion >= 1.1f)
	{
		file.read((char*)&mapformat, sizeof mapformat);
	}
	else
	{
		mapformat = mfQuake;
	}

	//
	// If reading an old (pre 1.4) format file, skip past the obselete palette
	// file path.
	//
	if (fVersion < 1.4f)
	{
		char szPalette[128];
		file.read(szPalette, sizeof szPalette);
	}

	file.read(szExecutable, sizeof szExecutable);
	file.read(szDefaultSolid, sizeof szDefaultSolid);
	file.read(szDefaultPoint, sizeof szDefaultPoint);

	if (fVersion >= 1.2f)
	{
		file.read(szBSP, sizeof szBSP);
		file.read(szLIGHT, sizeof szLIGHT);
		file.read(szVIS, sizeof szVIS);
		file.read(m_szGameExeDir, sizeof m_szGameExeDir);
		file.read(szMapDir, sizeof szMapDir);
	}

	if (fVersion >= 1.3f)
	{
		file.read(szBSPDir, sizeof(szBSPDir));
	}

	if (fVersion >= 1.4f)
	{
		// CSG setting is gone now.
		char szTempCSG[128];
		file.read(szTempCSG, sizeof(szTempCSG));

		file.read(m_szModDir, sizeof(m_szModDir));
		
		// gamedir is gone now.
		char tempGameDir[128];
		file.read(tempGameDir, sizeof(tempGameDir));
	}

	// read game data files
	char szBuf[128];
	for(int i = 0; i < nGDFiles; i++)
	{
		file.read(szBuf, sizeof szBuf);
		GDFiles.Add(CString(szBuf));
	}

	LoadGDFiles();
	
	return TRUE;
}


//-----------------------------------------------------------------------------
// Purpose: Loads this game configuration from a keyvalue block.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CGameConfig::Load(KeyValues *pkv)
{
	char szKey[MAX_PATH];

	// We should at least be able to get the game name and the game dir.
	Q_strncpy(szName, pkv->GetName(), sizeof(szName));
	Q_strncpy(m_szModDir, pkv->GetString("GameDir"), sizeof(m_szModDir));

	// Try to get the Hammer settings.
	KeyValues *pkvHammer = pkv->FindKey("Hammer");
	if (!pkvHammer)
		return true;

	//
	// Load the game data filenames from the "GameData0..GameDataN" keys.
	//
	nGDFiles = 0;
	bool bAdded = true;
	do
	{
		sprintf(szKey, "GameData%d", nGDFiles);
		const char *pszGameData = pkvHammer->GetString(szKey);
		if (pszGameData[0] != '\0')
		{
			GDFiles.Add(pszGameData);
			nGDFiles++;
		}
		else
		{
			bAdded = false;
		}

	} while (bAdded);

	textureformat = (TEXTUREFORMAT)pkvHammer->GetInt("TextureFormat", tfVMT);
	mapformat = (MAPFORMAT)pkvHammer->GetInt("MapFormat", mfHalfLife2);

	m_fDefaultTextureScale = pkvHammer->GetFloat("DefaultTextureScale", DEFAULT_TEXTURE_SCALE);
	if (m_fDefaultTextureScale == 0)
	{
		m_fDefaultTextureScale = DEFAULT_TEXTURE_SCALE;
	}

	m_nDefaultLightmapScale = pkvHammer->GetInt("DefaultLightmapScale", DEFAULT_LIGHTMAP_SCALE);

	Q_strncpy(szExecutable, pkvHammer->GetString("GameExe"), sizeof(szExecutable));
	Q_strncpy(szDefaultSolid, pkvHammer->GetString("DefaultSolidEntity"), sizeof(szDefaultSolid));
	Q_strncpy(szDefaultPoint, pkvHammer->GetString("DefaultPointEntity"), sizeof(szDefaultPoint));

	Q_strncpy(szBSP, pkvHammer->GetString("BSP"), sizeof(szBSP));
	Q_strncpy(szVIS, pkvHammer->GetString("Vis"), sizeof(szVIS));
	Q_strncpy(szLIGHT, pkvHammer->GetString("Light"), sizeof(szLIGHT));
	Q_strncpy(m_szGameExeDir, pkvHammer->GetString("GameExeDir"), sizeof(m_szGameExeDir));
	Q_strncpy(szMapDir, pkvHammer->GetString("MapDir"), sizeof(szMapDir));
	Q_strncpy(szBSPDir, pkvHammer->GetString("BSPDir"), sizeof(szBSPDir));

	SetCordonTexture( pkvHammer->GetString("CordonTexture", "BLACK") );

	char szExcludeDir[MAX_PATH];
	m_MaterialExcludeCount = pkvHammer->GetInt( "MaterialExcludeCount" );
	for ( int i = 0; i < m_MaterialExcludeCount; i++ )
	{
		sprintf( szExcludeDir, "-MaterialExcludeDir%d", i );
		int index = m_MaterialExclusions.AddToTail();
		Q_strncpy( m_MaterialExclusions[index].szDirectory, pkvHammer->GetString( szExcludeDir ), sizeof( m_MaterialExclusions[index].szDirectory ) ); 
		Q_StripTrailingSlash( m_MaterialExclusions[index].szDirectory );
		m_MaterialExclusions[index].bUserGenerated = true;
	}
	
	LoadGDFiles();
	
	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Saves this config's data into a keyvalues object.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CGameConfig::Save(KeyValues *pkv)
{
	pkv->SetName(szName);
	pkv->SetString("GameDir", m_szModDir);


	// Try to get the Hammer settings.
	KeyValues *pkvHammer = pkv->FindKey("Hammer");
	if (pkvHammer)
	{
		pkv->RemoveSubKey(pkvHammer);
		pkvHammer->deleteThis();
	}

	pkvHammer = pkv->CreateNewKey();
	if (!pkvHammer)
		return false;

	pkvHammer->SetName("Hammer");

	//
	// Load the game data filenames from the "GameData0..GameDataN" keys.
	//
	for (int i = 0; i < nGDFiles; i++)
	{
		char szKey[MAX_PATH];
		sprintf(szKey, "GameData%d", i);
		pkvHammer->SetString(szKey, GDFiles.GetAt(i));
	}

	pkvHammer->SetInt("TextureFormat", textureformat);
	pkvHammer->SetInt("MapFormat", mapformat);
	pkvHammer->SetFloat("DefaultTextureScale", m_fDefaultTextureScale);
	pkvHammer->SetInt("DefaultLightmapScale", m_nDefaultLightmapScale);

	pkvHammer->SetString("GameExe", szExecutable);
	pkvHammer->SetString("DefaultSolidEntity", szDefaultSolid);
	pkvHammer->SetString("DefaultPointEntity", szDefaultPoint);

	pkvHammer->SetString("BSP", szBSP);
	pkvHammer->SetString("Vis", szVIS);
	pkvHammer->SetString("Light", szLIGHT);
	pkvHammer->SetString("GameExeDir", m_szGameExeDir);
	pkvHammer->SetString("MapDir", szMapDir);
	pkvHammer->SetString("BSPDir", szBSPDir);

	pkvHammer->SetString("CordonTexture", m_szCordonTexture);

	char szExcludeDir[MAX_PATH];
	pkvHammer->SetInt("MaterialExcludeCount", m_MaterialExcludeCount);
	for (int i = 0; i < m_MaterialExcludeCount; i++)
	{
		sprintf(szExcludeDir, "-MaterialExcludeDir%d", i );
		pkvHammer->SetString(szExcludeDir, m_MaterialExclusions[i].szDirectory);
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
//-----------------------------------------------------------------------------
void CGameConfig::Save(std::fstream &file)
{
	file.write(szName, sizeof szName);
	file.write((char*)&nGDFiles, sizeof nGDFiles);
	file.write((char*)&textureformat, sizeof textureformat);
	file.write((char*)&mapformat, sizeof mapformat);
	file.write(szExecutable, sizeof szExecutable);
	file.write(szDefaultSolid, sizeof szDefaultSolid);
	file.write(szDefaultPoint, sizeof szDefaultPoint);

	// 1.2
	file.write(szBSP, sizeof szBSP);
	file.write(szLIGHT, sizeof szLIGHT);
	file.write(szVIS, sizeof szVIS);
	file.write(m_szGameExeDir, sizeof(m_szGameExeDir));
	file.write(szMapDir, sizeof szMapDir);
	
	// 1.3
	file.write(szBSPDir, sizeof szBSPDir);

	// 1.4
	char tempCSG[128] = "";
	file.write(tempCSG, sizeof(tempCSG));

	file.write(m_szModDir, sizeof(m_szModDir));
	
	char tempGameDir[128] = "";
	file.write(tempGameDir, sizeof(tempGameDir));

	// write game data files
	char szBuf[128];
	for(int i = 0; i < nGDFiles; i++)
	{
		strcpy(szBuf, GDFiles[i]);
		file.write(szBuf, sizeof szBuf);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pConfig - 
//-----------------------------------------------------------------------------
void CGameConfig::CopyFrom(CGameConfig *pConfig)
{
	nGDFiles = pConfig->nGDFiles;

	GDFiles.RemoveAll();
	GDFiles.Append(pConfig->GDFiles);

	strcpy(szName, pConfig->szName);
	strcpy(szExecutable, pConfig->szExecutable);
	strcpy(szDefaultPoint, pConfig->szDefaultPoint);
	strcpy(szDefaultSolid, pConfig->szDefaultSolid);
	strcpy(szBSP, pConfig->szBSP);
	strcpy(szLIGHT, pConfig->szLIGHT);
	strcpy(szVIS, pConfig->szVIS);
	strcpy(szMapDir, pConfig->szMapDir);
	strcpy(m_szGameExeDir, pConfig->m_szGameExeDir);
	strcpy(szBSPDir, pConfig->szBSPDir);
	strcpy(m_szModDir, pConfig->m_szModDir);

	pConfig->m_MaterialExcludeCount = m_MaterialExcludeCount;
	for( int i = 0; i < m_MaterialExcludeCount; i++ )
	{
		strcpy( m_MaterialExclusions[i].szDirectory, pConfig->m_MaterialExclusions[i].szDirectory );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pEntity - 
//			pGD - 
// Output : Returns TRUE to keep enumerating.
//-----------------------------------------------------------------------------
static BOOL UpdateClassPointer(CMapEntity *pEntity, GameData *pGDIn)
{
	GDclass *pClass = pGDIn->ClassForName(pEntity->GetClassName());
	pEntity->SetClass(pClass);
	return(TRUE);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGameConfig::LoadGDFiles(void)
{
	GD.ClearData();
	
	// Save the old working directory
	char szOldDir[MAX_PATH];
	_getcwd( szOldDir, sizeof(szOldDir) );

	// Set our working directory properly
	char szAppDir[MAX_PATH];
	APP()->GetDirectory( DIR_PROGRAM, szAppDir );
	_chdir( szAppDir );

	for (int i = 0; i < nGDFiles; i++)
	{
		GD.Load(GDFiles[i]);
	}

	// Reset our old working directory
	_chdir( szOldDir );

	// All the class pointers have changed - now we have to
	// reset all the class pointers in each map doc that 
	// uses this game.
	for ( int i=0; i<CMapDoc::GetDocumentCount(); i++ )
	{
		CMapDoc *pDoc = CMapDoc::GetDocument(i);

		if (pDoc->GetGame() == this)
		{
			CMapWorld *pWorld = pDoc->GetMapWorld();
			pWorld->SetClass(GD.ClassForName(pWorld->GetClassName()));
			pWorld->EnumChildren((ENUMMAPCHILDRENPROC)UpdateClassPointer, (DWORD)&GD, MAPCLASS_TYPE(CMapEntity));
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Searches for the given filename, starting in szStartDir and looking
//			up the directory tree.
// Input:	szFile - the name of the file to search for.
//			szStartDir - the folder to start searching from, towards the root.
//			szFoundPath - receives the full path of the FOLDER where szFile was found.
// Output : Returns true if the file was found, false if not. If the file was
//			found the full path (not including the filename) is returned in szFoundPath.
//-----------------------------------------------------------------------------
bool FindFileInTree(const char *szFile, const char *szStartDir, char *szFoundPath)
{
	if ((szFile == NULL) || (szStartDir == NULL) || (szFoundPath == NULL))
	{
		return false;
	}

	char szRoot[MAX_PATH];
	strcpy(szRoot, szStartDir);
	Q_AppendSlash(szRoot, sizeof(szRoot));

	char szTemp[MAX_PATH];
	do
	{
		strcpy(szTemp, szRoot);
		strcat(szTemp, szFile);

		if (!_access(szTemp, 0))
		{
			strcpy(szFoundPath, szRoot);
			Q_StripTrailingSlash(szFoundPath);
			return true;
		}

	} while (Q_StripLastDir(szRoot, sizeof(szRoot)));

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szDir - 
//			*szSteamDir - 
//			*szSteamUserDir - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool FindSteamUserDir(const char *szAppDir, const char *szSteamDir, char *szSteamUserDir)
{
	if ((szAppDir == NULL) || (szSteamDir == NULL) || (szSteamUserDir == NULL))
	{
		return false;
	}

	// If the szAppDir was run from within the steam tree, we should be able to find the steam user dir.
	int nSteamDirLen = strlen(szSteamDir);
	if (!Q_strnicmp(szAppDir, szSteamDir, nSteamDirLen ) && (szAppDir[nSteamDirLen] == '\\'))
	{
		strcpy(szSteamUserDir, szAppDir);

		char *pszSlash = strchr(&szSteamUserDir[nSteamDirLen + 1], '\\');
		if (pszSlash)
		{
			pszSlash++;

			pszSlash = strchr(pszSlash, '\\');
			if (pszSlash)
			{
				*pszSlash = '\0';
				return true;
			}
		}
	}

	szSteamUserDir[0] = '\0';

	return false;
}

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgoff.h"

//-----------------------------------------------------------------------------
// Purpose: Loads the settings from <mod dir>\gameinfo.txt into data members.
//-----------------------------------------------------------------------------
void CGameConfig::ParseGameInfo()
{
	KeyValues *pkv = new KeyValues("gameinfo.txt");
	if (!pkv->LoadFromFile(g_pFileSystem, "gameinfo.txt", "GAME"))
	{
		pkv->deleteThis();
		return;
	}

	KeyValues *pKey = pkv->FindKey("FileSystem");
	if (pKey)
	{
		V_strcpy_safe( m_szSteamAppID, pKey->GetString( "SteamAppId", "" ) );
	}

	const char *InstancePath = pkv->GetString( "InstancePath", NULL );
	if ( InstancePath )
	{
		CMapInstance::SetInstancePath( InstancePath );
	}

	pkv->deleteThis();

	char szAppDir[MAX_PATH];
	APP()->GetDirectory(DIR_PROGRAM, szAppDir);
	if (!FindFileInTree("steam.exe", szAppDir, m_szSteamDir))
	{
		// Couldn't find steam.exe in the hammer tree
		m_szSteamDir[0] = '\0';
	}

	if (!FindSteamUserDir(szAppDir, m_szSteamDir, m_szSteamUserDir))
	{
		m_szSteamUserDir[0] = '\0';
	}
}


//-----------------------------------------------------------------------------
// Accessor methods to get at the mod + the game (*not* full paths)
//-----------------------------------------------------------------------------
const char *CGameConfig::GetMod()
{
	// Strip path from modDir
	char szModPath[MAX_PATH];
	static char szMod[MAX_PATH];
	Q_strncpy( szModPath, m_szModDir, MAX_PATH );
	Q_StripTrailingSlash( szModPath );
	if ( !szModPath[0] )
	{
		Q_strcpy( szModPath, "hl2" );
	}

	Q_FileBase( szModPath, szMod, MAX_PATH );

	return szMod;
}

const char *CGameConfig::GetGame()
{
	return "hl2";

//	// Strip path from modDir
//	char szGamePath[MAX_PATH];
//	static char szGame[MAX_PATH];
//	Q_strncpy( szGamePath, m_szGameDir, MAX_PATH );
//	Q_StripTrailingSlash( szGamePath );
//	Q_FileBase( szGamePath, szGame, MAX_PATH );

//	return szGame;
}