//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Handles running the OS commands for map compilation.
//
//=============================================================================

#include "stdafx.h"
#include <afxtempl.h>
#include "GameConfig.h"
#include "RunCommands.h"
#include "Options.h"
#include <process.h>
#include "ProcessWnd.h"
#include <io.h>
#include <direct.h>
#include "GlobalFunctions.h"
#include "hammer.h"

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

static bool s_bRunsCommands = false;

bool IsRunningCommands() { return s_bRunsCommands; }

static char *pszDocPath, *pszDocName, *pszDocExt;

CProcessWnd procWnd;

void FixGameVars(char *pszSrc, char *pszDst, BOOL bUseQuotes)
{
	// run through the parms list and substitute $variable strings for
	//  the real thing
	char *pSrc = pszSrc, *pDst = pszDst;
	BOOL bInQuote = FALSE;
	while(pSrc[0])
	{	
		if(pSrc[0] == '$')	// found a parm
		{
			if(pSrc[1] == '$')	// nope, it's a single symbol
			{
				*pDst++ = '$';
				++pSrc;
			}
			else
			{
				// figure out which parm it is .. 
				++pSrc;
				
				if (!bInQuote && bUseQuotes)
				{
					// not in quote, and subbing a variable.. start quote
					*pDst++ = '\"';
					bInQuote = TRUE;
				}

				if(!strnicmp(pSrc, "file", 4))
				{
					pSrc += 4;
					strcpy(pDst, pszDocName);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "ext", 3))
				{
					pSrc += 3;
					strcpy(pDst, pszDocExt);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "path", 4))
				{
					pSrc += 4;
					strcpy(pDst, pszDocPath);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "exedir", 6))
				{
					pSrc += 6;
					strcpy(pDst, g_pGameConfig->m_szGameExeDir);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "bspdir", 6))
				{
					pSrc += 6;
					strcpy(pDst, g_pGameConfig->szBSPDir);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "bsp_exe", 7))
				{
					pSrc += 7;
					strcpy(pDst, g_pGameConfig->szBSP);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "vis_exe", 7))
				{
					pSrc += 7;
					strcpy(pDst, g_pGameConfig->szVIS);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "light_exe", 9))
				{
					pSrc += 9;
					strcpy(pDst, g_pGameConfig->szLIGHT);
					pDst += strlen(pDst);
				}
				else if(!strnicmp(pSrc, "game_exe", 8))
				{
					pSrc += 8;
					strcpy(pDst, g_pGameConfig->szExecutable);
					pDst += strlen(pDst);
				}
				else if (!strnicmp(pSrc, "gamedir", 7))
				{
					pSrc += 7;
					strcpy(pDst, g_pGameConfig->m_szModDir);
					pDst += strlen(pDst);
				}
			}
		}
		else
		{
			if(*pSrc == ' ' && bInQuote)
			{
				bInQuote = FALSE;
				*pDst++ = '\"';	// close quotes
			}

			// just copy the char into the destination buffer
			*pDst++ = *pSrc++;
		}
	}

	if(bInQuote)
	{
		bInQuote = FALSE;
		*pDst++ = '\"';	// close quotes
	}

	pDst[0] = 0;
}

static void RemoveQuotes(char *pBuf)
{
	if(pBuf[0] == '\"')
		strcpy(pBuf, pBuf+1);
	if(pBuf[strlen(pBuf)-1] == '\"')
		pBuf[strlen(pBuf)-1] = 0;
}

LPCTSTR GetErrorString()
{
	static char szBuf[200];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, 
		szBuf, 200, NULL);
	char *p = strchr(szBuf, '\r');	// get rid of \r\n
	if(p) p[0] = 0;
	return szBuf;
}

bool RunCommands(CCommandArray& Commands, LPCTSTR pszOrigDocName)
{
	s_bRunsCommands = true;

	char szCurDir[MAX_PATH];
	_getcwd(szCurDir, MAX_PATH);

	procWnd.GetReady();

	// cut up document name into file and extension components.
	//  create two sets of buffers - one set with the long filename
	//  and one set with the 8.3 format.

	char szDocLongPath[MAX_PATH] = {0}, szDocLongName[MAX_PATH] = {0}, 
		szDocLongExt[MAX_PATH] = {0};
	char szDocShortPath[MAX_PATH] = {0}, szDocShortName[MAX_PATH] = {0}, 
		szDocShortExt[MAX_PATH] = {0};

	GetFullPathName(pszOrigDocName, MAX_PATH, szDocLongPath, NULL);
	GetShortPathName(pszOrigDocName, szDocShortPath, MAX_PATH);

	// split them up
	char *p = strrchr(szDocLongPath, '.');
	if(p && strrchr(szDocLongPath, '\\') < p && strrchr(szDocLongPath, '/') < p)
	{
		// got the extension
		strcpy(szDocLongExt, p+1);
		p[0] = 0;
	}

	p = strrchr(szDocLongPath, '\\');
	if(!p)
		p = strrchr(szDocLongPath, '/');
	if(p)
	{
		// got the filepart
		strcpy(szDocLongName, p+1);
		p[0] = 0;
	}

	// split the short part up
	p = strrchr(szDocShortPath, '.');
	if(p && strrchr(szDocShortPath, '\\') < p && strrchr(szDocShortPath, '/') < p)
	{
		// got the extension
		strcpy(szDocShortExt, p+1);
		p[0] = 0;
	}

	p = strrchr(szDocShortPath, '\\');
	if(!p)
		p = strrchr(szDocShortPath, '/');
	if(p)
	{
		// got the filepart
		strcpy(szDocShortName, p+1);
		p[0] = 0;
	}

	int iSize = Commands.GetSize(), i = 0;
	char *ppParms[32];
	while(iSize--)
	{
		CCOMMAND &cmd = Commands[i++];

		// anything there?
		if((!cmd.szRun[0] && !cmd.iSpecialCmd) || !cmd.bEnable)
			continue;

		// set name pointers for long filenames
		pszDocExt = szDocLongExt;
		pszDocName = szDocLongName;
		pszDocPath = szDocLongPath;
		
		char szNewParms[MAX_PATH*5], szNewRun[MAX_PATH*5];

		// HACK: force the spawnv call for launching the game
		if (!Q_stricmp(cmd.szRun, "$game_exe"))
		{
			cmd.bUseProcessWnd = FALSE;
		}

		FixGameVars(cmd.szRun, szNewRun, TRUE);
		FixGameVars(cmd.szParms, szNewParms, TRUE);

		CString strTmp;
		strTmp.Format("\r\n"
			"** Executing...\r\n"
			"** Command: %s\r\n"
			"** Parameters: %s\r\n\r\n", szNewRun, szNewParms);
		procWnd.Append(strTmp);
		
		// create a parameter list (not always required)
		if(!cmd.bUseProcessWnd || cmd.iSpecialCmd)
		{
			p = szNewParms;
			ppParms[0] = szNewRun;
			int iArg = 1;
			BOOL bDone = FALSE;
			while(p[0])
			{
				ppParms[iArg++] = p;
				while(p[0])
				{
					if(p[0] == ' ')
					{
						// found a space-separator
						p[0] = 0;

						p++;

						// skip remaining white space
						while (*p == ' ')
							p++;

						break;
					}

					// found the beginning of a quoted parameters
					if(p[0] == '\"')
					{
						while(1)
						{
							p++;
							if(p[0] == '\"')
							{
								// found the end
								if(p[1] == 0)
									bDone = TRUE;
								p[1] = 0;	// kick its ass
								p += 2;

								// skip remaining white space
								while (*p == ' ')
									p++;

								break;
							}
						}
						break;
					}

					// else advance p
					++p;
				}

				if(!p[0] || bDone)
					break;	// done.
			}

			ppParms[iArg] = NULL;

			if(cmd.iSpecialCmd)
			{
				BOOL bError = FALSE;
				LPCTSTR pszError = "";

				if(cmd.iSpecialCmd == CCCopyFile && iArg == 3)
				{
					RemoveQuotes(ppParms[1]);
					RemoveQuotes(ppParms[2]);
					
					// don't copy if we're already there
					if (stricmp(ppParms[1], ppParms[2]) && 					
							(!CopyFile(ppParms[1], ppParms[2], FALSE)))
					{
						bError = TRUE;
						pszError = GetErrorString();
					}
				}
				else if(cmd.iSpecialCmd == CCDelFile && iArg == 2)
				{
					RemoveQuotes(ppParms[1]);
					if(!DeleteFile(ppParms[1]))
					{
						bError = TRUE;
						pszError = GetErrorString();
					}
				}
				else if(cmd.iSpecialCmd == CCRenameFile && iArg == 3)
				{
					RemoveQuotes(ppParms[1]);
					RemoveQuotes(ppParms[2]);
					if(rename(ppParms[1], ppParms[2]))
					{
						bError = TRUE;
						pszError = strerror(errno);
					}
				}
				else if(cmd.iSpecialCmd == CCChangeDir && iArg == 2)
				{
					RemoveQuotes(ppParms[1]);
					if(mychdir(ppParms[1]) == -1)
					{
						bError = TRUE;
						pszError = strerror(errno);
					}
				}

				if(bError)
				{
					CString str;
					str.Format("The command failed. Windows reported the error:\r\n"
						"  \"%s\"\r\n", pszError);
					procWnd.Append(str);
					procWnd.SetForegroundWindow();
					str += "\r\nDo you want to continue?";
					if(AfxMessageBox(str, MB_YESNO) == IDNO)
						break;
				}
			}
			else
			{
				// Change to the game exe folder before spawning the engine.
				// This is necessary for Steam to find the correct Steam DLL (it
				// uses the current working directory to search).
				char szDir[MAX_PATH];
				Q_strncpy(szDir, szNewRun, sizeof(szDir));
				Q_StripFilename(szDir);

				mychdir(szDir);

				// YWB Force asynchronous operation so that engine doesn't hang on
				//  exit???  Seems to work.
				// spawnv doesn't like quotes
				RemoveQuotes(szNewRun);
				_spawnv(/*cmd.bNoWait ?*/ _P_NOWAIT /*: P_WAIT*/, szNewRun, 
					(const char *const *)ppParms);
			}
		}
		else
		{
			procWnd.Execute(szNewRun, szNewParms);
		}

		// check for existence?
		if(cmd.bEnsureCheck)
		{
			char szFile[MAX_PATH];
			FixGameVars(cmd.szEnsureFn, szFile, FALSE);
			if(GetFileAttributes(szFile) == 0xFFFFFFFF)
			{
				// not there!
				CString str;
				str.Format("The file '%s' was not built.\n"
					"Do you want to continue?", szFile);
				procWnd.SetForegroundWindow();
				if(AfxMessageBox(str, MB_YESNO) == IDNO)
					break;	// outta here
			}
		}
	}

	mychdir(szCurDir);

	s_bRunsCommands = false;

	return TRUE;
}