//
// Tools_BatchProcessing.h
//
// Created by Richard Rice - 01/10/2003
// 
// Copyright (C) 2003 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Load&Save/Tools_BatchProcessing.h"

#include "Core/Asura_NameTree.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Debug_Timers.h"
#include "Debugging/Asura_Logfile.h"

#include "Core/Asura_Windows.h"

#include "Tools_System.h"
#include "Lighting/Tools_Batch_GlobalIllumination.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Misc/Tools_IniFile.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "PC/OS/Tools_OS.h"
#include "Waypointing/Tools_Batch_Waypointing.h"


bool Tools_BatchProcessing::s_bAbortExport = false;
int Tools_BatchProcessing::s_iBatchIndex = 0;
int Tools_BatchProcessing::s_iBatchFileIndex = 0;
bool Tools_BatchProcessing::s_bIsFirstFile = true;
Tools_BatchProcessing::BatchType Tools_BatchProcessing::s_eBatchType = BATCH_NONE;
bool Tools_BatchProcessing::s_bIsConsoleMode = false;
List<Asura_String> Tools_BatchProcessing::s_xIniFilenameList;
Asura_String Tools_BatchProcessing::s_xCommandOnCompletion;


static Tools_IniFile* g_pxBatchIniFile = 0;


bool Tools_BatchProcessing::IsBatchFile(const char* pszFilename)
{
	return GetBatchFileType(pszFilename) != BATCH_NONE;	
}

Tools_BatchProcessing::BatchType Tools_BatchProcessing::GetBatchFileType(const char* pszFilename)
{
	//determine the type of batch export file from the filename
	const char* pszExtension = strrchr(pszFilename,'.')	;

	if(pszExtension)
	{
		if(!stricmp(pszExtension,".AsuraExport"))
		{
			return BATCH_LEVELEXPORT;
		}
		if(!stricmp(pszExtension,".AsuraWaypoint"))
		{
			return BATCH_WAYPOINTING;
		}
		if(!stricmp(pszExtension,".AsuraGI"))
		{
			return BATCH_GLOBAL_ILLUMINATION;
		}
	}
	return BATCH_NONE;
}

bool Tools_BatchProcessing::Initialise( const char* szIniName )
{
	extern char* DefaultFileToLoad;
	Asura_Assert( IsBatchFile(szIniName), "" );

	s_iBatchIndex = 0;
	s_iBatchFileIndex = 0;

	//check the command line for progress
	const char* pszBatch = strstr(GetCommandLine(),"-batch");
	if(pszBatch)
	{
		sscanf(pszBatch,"-batch%d,%d",&s_iBatchFileIndex,&s_iBatchIndex);
        s_bIsFirstFile = false;
	}
    else
    {
        s_bIsFirstFile = true;
    }
    
    if( s_bIsFirstFile )
    {
        Asura_SimpleLogfileClear(( TOOLS_BATCH_PROCESS_LOGFILE ));
        Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Beginning batch process from '%s'.\n", szIniName ));
    }

	g_pxBatchIniFile = new Tools_IniFile(0);
	
	//build a list of all the ini files being used
	s_xIniFilenameList.add_entry( szIniName );

	int iJunkIndex;
	Asura_StringTree xIniTree;
	xIniTree.FindNodeIndexByName( szIniName, true, iJunkIndex );

	//Verify ini files if this is the start of the first file.
	bool bVerifyIniFiles = (s_iBatchIndex == 0 && s_iBatchFileIndex == 0);
	if(bVerifyIniFiles)
	{
		//Need access to some project derived classes that won't be allocated yet
		Tools_System::AllocateToolsSystemAndToolsClasses();
	}

	bool bOkay = true;
	bool bFirstIniFile = true;
	for(LIF<Asura_String> xIniLIF(&s_xIniFilenameList);!xIniLIF.done();xIniLIF.next())
	{
		g_pxBatchIniFile->Reset();
		g_pxBatchIniFile->SetFilename(xIniLIF().GetString(),false);
		s_eBatchType = GetBatchFileType(xIniLIF().GetString());
		if(!g_pxBatchIniFile->LoadIniFile())
		{
		   	Asura_Assertf(0,("Failed to load %s",g_pxBatchIniFile->GetFilename()));
			bOkay = false;
			continue;
		}

		//if this is the first time , make sure this file is okay
		if(bVerifyIniFiles)
		{
			if(!VerifyIniFile())
			{
				bOkay = false;
			}
		}

		if(bFirstIniFile)
		{
			bFirstIniFile = false;
			//get the command line to run on completion from the first ini file loaded (though
			//it will only be used on the last iteration through tools).
			if(g_pxBatchIniFile->GetIniTextSection("Default"))
			{
				g_pxBatchIniFile->GetStringInCurrentSection("Command_On_Completion", s_xCommandOnCompletion);
			}

		}

		//does the file have a list of others to include?
		if(g_pxBatchIniFile->GetIniTextSection("Include"))
		{
			while(g_pxBatchIniFile->GetNextIniTextLine())
			{
				Asura_String xTemp;
				g_pxBatchIniFile->GetString(xTemp);
				//add the file to the list (if it isn't already there)
				if(!xIniTree.FindNodeIndexByName(xTemp,true,iJunkIndex))
				{
					s_xIniFilenameList.add_entry(xTemp);
				}
			}
		}
	}
	if(bVerifyIniFiles)
	{
		//Can get rid of the tools classes now
		Tools_System::DeallocateToolsSystemAndToolsClasses();
	}

	if(!bOkay)
	{
		Shutdown();
		return false;
	}

	//make sure the correct ini file is open
	//(will currently have the last one in the list)
	if(s_iBatchFileIndex >= s_xIniFilenameList.size())
	{
		Shutdown();
		return false;
	}
	if(s_iBatchFileIndex != s_xIniFilenameList.size()-1)
	{
		g_pxBatchIniFile->Reset();
		g_pxBatchIniFile->SetFilename(s_xIniFilenameList[s_iBatchFileIndex].GetString(),false);
		s_eBatchType = GetBatchFileType(s_xIniFilenameList[s_iBatchFileIndex].GetString());

		#if defined ASURA_ASSERT
			bool bLoaded =
		#endif
			g_pxBatchIniFile->LoadIniFile();

		Asura_Assertf(bLoaded,("Failed to load %s",g_pxBatchIniFile->GetFilename()));
	}

	
	
	//advance the batch indeces as needed to get to a valid step
	if(!GetNextValidBatchStage(true))
	{
		//finished
		#if defined( ASURA_TOOLS_BATCHPROCESSING_WAITON_SUBPROCESS)
		if(s_bIsFirstFile)
		#endif
		{
			Project_OnFinishLastFile();
			ExecuteCommandOnCompletion();
		}
		Shutdown();
		return false;
	}
	


	//initalise the file to load
	static Asura_String s_xFilename;
	if(!g_pxBatchIniFile->GetStringInCurrentSection("File",s_xFilename))
	{
		Asura_Assertf(0,("Failed to find 'File' identifier in section '%s'",g_pxBatchIniFile->GetCurrentSectionName()));
		Shutdown();
		return false;
	}

	DefaultFileToLoad = s_xFilename.GetString();

    Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "\nProcessing '%s'.\n", DefaultFileToLoad ));

	AsuraAssert_SetIgnoreAll(true);
	return true;
}

void Tools_BatchProcessing::Shutdown()
{
	delete g_pxBatchIniFile;
	g_pxBatchIniFile = 0;
}

void Tools_BatchProcessing::DoBatchProcess()
{
	ASURA_CPU_PROFILE_SCOPED_TIMER( "Tools_BatchProcessing::DoBatchProcess");
	
	extern char* DefaultFileToLoad;
	
	switch(s_eBatchType)
	{
		case BATCH_LEVELEXPORT :
			Tools_Level_Export::BatchExport_ExportLevel();
			break;

		case BATCH_WAYPOINTING :
			Tools_Batch_Waypointing::GetThis()->BatchWaypointing_WaypointLevel();
			break;

		case BATCH_GLOBAL_ILLUMINATION:
			Tools_Batch_GlobalIllumination::BatchLighting_RelightLevel();
			break;

		default:
			break;
	}
}


#if defined( ASURA_TOOLS_BATCHPROCESSING_WAITON_SUBPROCESS)

int Tools_BatchProcessing::NextFile()
{
	int iError = 0;
	
	Asura_Assert(IsBatch(),"");

	if( s_bAbortExport )
   	{
    	Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Stopping - batch was aborted.\n" ));
		Shutdown();
   	    return( TOOLS_EXIT_CODE_BATCH_ABORTED);
   	}

	//The first instance of tools deals with opening up instances for all the other files in turn
	if(s_bIsFirstFile)
	{

		while(GetNextValidBatchStage(false))
		{
			Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Moving to next file.\n" ));

			Asura_String xCmdLine = GetCommandLine();
   
			// Construct our new batch argument.
			char szNewBatchArg[16];
    		Asura_PrintfToBfr( szNewBatchArg, " -batch%i,%i", 16, true, s_iBatchFileIndex, s_iBatchIndex );

    		// Replace the existing one in the command line, or just append it if
    		// we're currently at the first stage.
    		{
    		    Asura_String xLowercaseCmdLine = xCmdLine;
    		    xLowercaseCmdLine.MakeLowercase();

    		    const char* szLowercaseCmdLine = xLowercaseCmdLine.GetString();

    		    const char* szBatchArg = strstr( szLowercaseCmdLine, " -batch" );

    		    if( szBatchArg )
    		    {
    		        const char* szArgEnd = szBatchArg + 7;
    		        for( ; *szArgEnd; ++szArgEnd )
    		        {
    		            // Any whitespace delimits.
    		            if( isspace(*szArgEnd) ) { break; }
    		        }

    		        xCmdLine.Replace( szBatchArg - szLowercaseCmdLine, szArgEnd - szBatchArg, szNewBatchArg );
    		    }
    		    else
    		    {
    		        xCmdLine += szNewBatchArg;
    		    }
    		}

    		// Spawn the new tools process.
    		{
    		    STARTUPINFO xSI;
    		    ZeroMemory( &xSI, sizeof(STARTUPINFO) );
    		    xSI.cb = sizeof(STARTUPINFO);

    		    PROCESS_INFORMATION xPI;
    		    ZeroMemory( &xPI, sizeof(PROCESS_INFORMATION) );

    		    Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Spawning: %s\n", xCmdLine.GetString() ));

    		    if( CreateProcess(NULL, xCmdLine.GetString(), NULL, NULL, FALSE, 0, NULL, NULL, &xSI, &xPI) )
    		    {
    		   		WaitForSingleObject( xPI.hProcess, INFINITE);
    		   		
    		   		DWORD uProcessExitCode = static_cast< u_int>( ~0);
    		   		BOOL bGetExitCodeProcessStatus = GetExitCodeProcess( xPI.hProcess, &uProcessExitCode);
    		   		if( 0 != bGetExitCodeProcessStatus)
    		   		{
    		   			iError = uProcessExitCode;
    		   		}
					if(iError == TOOLS_EXIT_CODE_BATCH_ABORTED)
					{
						s_bAbortExport = true;
						break;
					}
					else
					{
						Asura_Assertf( 0 != bGetExitCodeProcessStatus && 0 == iError, ("Exit Process Status %d Value %d\n\r\n\r %s \n\r\n\r", bGetExitCodeProcessStatus, uProcessExitCode, xCmdLine.GetString()));
					}

    		        CloseHandle( xPI.hProcess );
    		        CloseHandle( xPI.hThread );
    		    }
    		    else
    		    {
    		        Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "ERROR: Failed to spawn. Reason: %s\n", Tools_OS::GetLastSystemError() ));
    		    }
    		}
		}

		//no more files
		if(!s_bAbortExport)
		{
			Project_OnFinishLastFile();
			ExecuteCommandOnCompletion();
		}
	}
	Shutdown();
	
	return( iError);
}
#else
int Tools_BatchProcessing::NextFile()
{
	int iError = 0;
	
	Asura_Assert(IsBatch(),"");

    
    Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Moving to next file.\n" ));

	if( s_bAbortExport )
    {
        Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Stopping - batch was aborted.\n" ));
		Shutdown();
        return( iError);
    }

	s_iBatchIndex++;
    s_bIsFirstFile = false;

	Asura_String xCmdLine = GetCommandLine();
   
	// Construct our new batch argument.
	char szNewBatchArg[16];
    Asura_PrintfToBfr( szNewBatchArg, " -batch%i,%i", 16, true, s_iBatchFileIndex, s_iBatchIndex );

    // Replace the existing one in the command line, or just append it if
    // we're currently at the first stage.
    {
        Asura_String xLowercaseCmdLine = xCmdLine;
        xLowercaseCmdLine.MakeLowercase();

        const char* szLowercaseCmdLine = xLowercaseCmdLine.GetString();

        const char* szBatchArg = strstr( szLowercaseCmdLine, " -batch" );

        if( szBatchArg )
        {
            const char* szArgEnd = szBatchArg + 7;
            for( ; *szArgEnd; ++szArgEnd )
            {
                // Any whitespace delimits.
                if( isspace(*szArgEnd) ) { break; }
            }

            xCmdLine.Replace( szBatchArg - szLowercaseCmdLine, szArgEnd - szBatchArg, szNewBatchArg );
        }
        else
        {
            xCmdLine += szNewBatchArg;
        }
    }

    // Spawn the new tools process.
    {
        STARTUPINFO xSI;
        ZeroMemory( &xSI, sizeof(STARTUPINFO) );
        xSI.cb = sizeof(STARTUPINFO);

        PROCESS_INFORMATION xPI;
        ZeroMemory( &xPI, sizeof(PROCESS_INFORMATION) );

        Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Spawning: %s\n", xCmdLine.GetString() ));

        if( CreateProcess(NULL, xCmdLine.GetString(), NULL, NULL, FALSE, 0, NULL, NULL, &xSI, &xPI) )
        {

            CloseHandle( xPI.hProcess );
            CloseHandle( xPI.hThread );
        }
        else
        {
            Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "ERROR: Failed to spawn. Reason: %s\n", Tools_OS::GetLastSystemError() ));
        }
    }
	Shutdown();
	
	return( iError);
}
#endif

bool Tools_BatchProcessing::IsBatch()
{
	return s_eBatchType != BATCH_NONE;
}

bool Tools_BatchProcessing::VerifyIniFile()
{
	List<const char*> xKnownIdentifiers;

	switch(s_eBatchType)
	{
		case BATCH_LEVELEXPORT :
		{
            if( Tools_Level_Export::This )
            {
                Tools_Level_Export::This->BatchExport_GetIdentifiers( &xKnownIdentifiers );
            }
            else
            {
                // Presumably the same logic applies here as for the
                // waypointing case immediately below.
                return true;
            }
			break;
		}

		case BATCH_WAYPOINTING :
		{
			if (!Tools_Batch_Waypointing::GetThis())
			{
				// (AW - 02.02.2004) - We might not have called Tools_System::Initialise yet, so it's 
				// OK if Tools_Batch_Waypointing::GetThis() returns NULL.
				return true;
			}	
			else
			{
				Tools_Batch_Waypointing::GetThis()->BatchWaypointing_GetIdentifiers( &xKnownIdentifiers );
			}
			break;
		}

		case BATCH_GLOBAL_ILLUMINATION:
		{
			Tools_Batch_GlobalIllumination::BatchLighting_GetIdentifiers( &xKnownIdentifiers );
			break;
		}

		default :
			Asura_Assert(0,"Unexpected file type");
			return false;
	}
	xKnownIdentifiers.add_entry("Command_On_Completion");

	bool bFileOkay = true;
	
	for(int iSectionIndex=0;g_pxBatchIniFile->GetIniTextSectionByIndex(iSectionIndex);iSectionIndex++)
	{
		if(!stricmp("Include",g_pxBatchIniFile->GetCurrentSectionName()))
		{
			continue;//ignore the Include section
		}

		//all sections must have the 'File' identifier
		Asura_String xFilename;
		if(stricmp("Default",g_pxBatchIniFile->GetCurrentSectionName()))
		{
			if(!g_pxBatchIniFile->GetStringInCurrentSection("File",xFilename))
			{
				Asura_Assertf(0,("Failed to find 'File' identifier in section '%s'",g_pxBatchIniFile->GetCurrentSectionName()));
				return false;
			}
		}

		//make sure there aren't any unknown identifiers
		List<Asura_String> xIdentifierList;
		g_pxBatchIniFile->ListIdentifiersInCurrentSection(xIdentifierList);

		while(xIdentifierList.size())
		{
			const Asura_String& xIdentifier = xIdentifierList.first_entry();

			//check against the list of known identifiers
			bool bOkay = false;
			if (strstr(xIdentifier.GetString(), "Project_"))
			{
				// (AW - 05.10.2004) - Allow project identifiers without checking.
				bOkay = true;
			}
			else if (strstr(xIdentifier.GetString(), "XBox"))
			{
				// (MH - 12.05.2008) legacy scripts reference Xbox so allow this.
				bOkay = true;
			}
			else
			{
                for( CLIF<const char*> xKnownIt(&xKnownIdentifiers); !xKnownIt.done(); xKnownIt.next() )
                {
					if( !stricmp(xIdentifier.GetString(), xKnownIt()) )
					{
						bOkay = true;
						break;				
					}
				}
			}

			if(!bOkay)
			{
				Asura_Assertf(0,("Unrecognized identifer '%s' found for '%s'.\nFile : %s.",xIdentifier.GetString(),g_pxBatchIniFile->GetCurrentSectionName(),g_pxBatchIniFile->GetFilename()));

				bFileOkay = false;
			}

			xIdentifierList.delete_first_entry();
		}
	}

	return bFileOkay;
}

bool Tools_BatchProcessing::GetNextValidBatchStage(bool bCheckCurrent)
{
	if(!bCheckCurrent)
	{
		s_iBatchIndex++;
	}	


	while(true)
	{
		if(!g_pxBatchIniFile->GetIniTextSectionByIndex(s_iBatchIndex))
		{
			//done all the entries in this file already
			//try the next file in the list if there is one
			s_iBatchFileIndex++;
			s_iBatchIndex = 0;
			if(s_iBatchFileIndex>=s_xIniFilenameList.size())
			{
				//no more files
				return false;
			}
			g_pxBatchIniFile->Reset();
			g_pxBatchIniFile->SetFilename(s_xIniFilenameList[s_iBatchFileIndex].GetString(),false);
			s_eBatchType = GetBatchFileType(s_xIniFilenameList[s_iBatchFileIndex].GetString());

			#if defined ASURA_ASSERT
				bool bLoaded =
			#endif
				g_pxBatchIniFile->LoadIniFile();

			Asura_Assertf(bLoaded,("Failed to load %s",g_pxBatchIniFile->GetFilename()));

		}
		else
		{
			const char* pszSectionName = g_pxBatchIniFile->GetCurrentSectionName();
			if(!stricmp("Default",pszSectionName) || !stricmp("Include",pszSectionName))
			{
				//skip the 'Default' and 'Include' sections
				s_iBatchIndex++;
			}
			else
			{
				return true;
			}
		}
	}
}

Tools_IniFile* Tools_BatchProcessing::GetIniFile()
{
	Asura_Assert(g_pxBatchIniFile,"Batch processing not initialised");
	return g_pxBatchIniFile;
}

void Tools_BatchProcessing::ExecuteCommandOnCompletion()
{
	if(s_xCommandOnCompletion.IsBlank()) return;
		
	STARTUPINFO xSI;
	ZeroMemory( &xSI, sizeof(STARTUPINFO) );
	xSI.cb = sizeof(STARTUPINFO);

	PROCESS_INFORMATION xPI;

	if( !CreateProcess(NULL, s_xCommandOnCompletion.GetString(), NULL, NULL, FALSE, 0, NULL, NULL, &xSI, &xPI) )
	{
	    Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Failed to spawn: %s\n(%s)\n", s_xCommandOnCompletion.GetString(), Tools_OS::GetLastSystemError() ));
	}
	else
	{
		CloseHandle( xPI.hProcess );
		CloseHandle( xPI.hThread );
	}

}
