//////////////////////////////////////////////////////////////////////////////
//
// Asura_PlatformHash.cpp
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
// Description:
//
//     PC implementation of Filename hashing functionality
//
// History:
//
//     (MH  - 18.10.2002) - Created.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#include "Core/Asura_Hash.h"

#ifdef ASURA_ENABLE_HASH_TO_FILENAME_LOOKUPS

#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_PlatformBasics.h"
#include "Core/Asura_NameList.h"
#include "Core/Asura_HashTree.h"
#include "Core/Asura_StrippedHashedFilename.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"



void Asura_HashedFilename_System::Platform_BuildHashToFilenameLookupTree()
{
	if(s_pxFullFilenameList->GetNumEntries())
	{
		//Already have some loaded values , so reset everything.
		//Take a backup copy of the name lists , in case something still
		//has hold of a pointer to one of the names
		Asura_Assert(!s_pxBackupFullFilenameList,"");
		Asura_Assert(!s_pxBackupStrippedFilenameTree,"");

		s_pxBackupFullFilenameList = s_pxFullFilenameList;
		s_pxBackupStrippedFilenameTree = s_pxStrippedFilenameTree;
		s_pxFullFilenameList = new Asura_NameList;
		s_pxStrippedFilenameTree = new Asura_TemplateHashTree<Asura_StrippedHashedFilename>;

		s_pxFullFilenameHashTree->Reset();

	
	    s_iNumDuplicateStrippedNames = 0;

	}

	for(u_int i=0;i<s_uNumProjectDirectories;i++)
	{
		if (s_apszProjectDirectoryList[i])
		{
			Asura_OutputToDebugger(( "begin build hash local lookup on '%s'...\n", s_apszProjectDirectoryList[i] ));
			Platform_AddFilesRecursive(s_apszProjectDirectoryList[i]);
			Asura_OutputToDebugger(("end build hash local lookup\n"));
			
#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
			const char* szNetworkPath = Asura_FileAccess::PlatformGetNetworkPath();
			if (szNetworkPath && strlen(szNetworkPath))
			{
				char* szNetworkRoot = new char[strlen(szNetworkPath) + 2];// 1 for terminator, 1 for '\\'
				strcpy(szNetworkRoot, szNetworkPath);
				strcat(szNetworkRoot, "\\");

				char szDir[MAX_PATH];
				Asura_PrintfToBfr(szDir,"%s\\%s",MAX_PATH,true,szNetworkPath,s_apszProjectDirectoryList[i]);

				Asura_OutputToDebugger(( "begin build hash network lookup on '%s'...\n", s_apszProjectDirectoryList[i] ));
				Platform_AddFilesRecursive(szDir, szNetworkRoot);   // add files from network dir, but strip out the root dir "\\\\<path>\\"
				Asura_OutputToDebugger(("end build hash network lookup\n"));

				delete [] szNetworkRoot;
			}
#endif
		}
		
	}
    
	s_bUsingSavedHashedFilenames = false;
	Platform_SaveHashedFilenames();
}

void Asura_HashedFilename_System::Platform_AddFilesRecursive(const char* szDirectory, const char* szStripPathFromName/* = NULL*/)
{
    // add files in this dir
	char szSearch[MAX_PATH];
    char szFilename[MAX_PATH];
    if (strlen(szDirectory))
    {
        sprintf(szSearch, "%s\\*.asr", szDirectory);
    }
    else
    {
        strcpy(szSearch, "*.asr");
    }
	WIN32_FIND_DATA wfd;
	HANDLE hFindFile = ::FindFirstFile(szSearch, &wfd);

    if (INVALID_HANDLE_VALUE != hFindFile)
    {
		do
		{	
			// not interested in directories
			if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue;
			
            if (strlen(szDirectory))
            {
                sprintf(szFilename, "%s\\%s", szDirectory, wfd.cFileName);
            }
            else
            {
                strcpy(szFilename, wfd.cFileName);
            }
            const char* szFilenameToAdd = szFilename;
            if (szStripPathFromName)
            {
                if (strstr(szFilename, szStripPathFromName))
                {
                    szFilenameToAdd += strlen(szStripPathFromName);
                }
                else
                {
                    Asura_Assert(false, "incorrect use of szStripPathFromName?");
                }
            }
            if (szFilenameToAdd)
            {
                AddFile(szFilenameToAdd);
            }
		}
		while (::FindNextFile(hFindFile,&wfd));
		::FindClose(hFindFile);
    }

    // recurse sub directories
    if (strlen(szDirectory))
    {
        sprintf(szSearch, "%s\\*.*", szDirectory);
    }
    else
    {
        strcpy(szSearch, "*.*");
    }
    hFindFile = ::FindFirstFile(szSearch, &wfd);
	if (INVALID_HANDLE_VALUE != hFindFile)
	{
		do
		{	
			// only interested in directories
			if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue;
			// ignore the '.' and '..' directories
			if (wfd.cFileName[0]=='.') continue;

            if (strlen(szDirectory))
            {
                sprintf(szFilename, "%s\\%s", szDirectory, wfd.cFileName);
            }
            else
            {
                strcpy(szFilename, wfd.cFileName);
            }
            Platform_AddFilesRecursive(szFilename, szStripPathFromName);
		}
		while (::FindNextFile(hFindFile,&wfd));
		::FindClose(hFindFile);
    }   
}

const char* g_pszHashedFilenameFile = "ToolsIniData\\HashedFilenames.dat";
const int g_iHashedFilenameVersion = 1;

bool Asura_HashedFilename_System::Platform_LoadHashedFilenames()
{
	//Make writeable in case the file has been added to alienbrain by mistake.
	DWORD uAttributes = GetFileAttributes(g_pszHashedFilenameFile);
	if(uAttributes != 0xffffffff)
	{
		if(uAttributes & FILE_ATTRIBUTE_READONLY)
		{
			return SetFileAttributes(g_pszHashedFilenameFile,uAttributes &~ FILE_ATTRIBUTE_READONLY) ? true : false;
		}
	}
    	
	Asura_Handle iHandle = Asura_FileAccess::Open(g_pszHashedFilenameFile);
	if(iHandle == ASURA_INVALID_HANDLE) return false;

	Asura_Chunk_File_Stream	xStream(iHandle);


	xStream.Skip(8);//skip 'ASURA   '
	
	//check the version number
	int iVersion;
	xStream >> iVersion;
	if(iVersion != g_iHashedFilenameVersion)
	{
		Asura_FileAccess::Close(iHandle);
		return false;
	}

	//check number of platforms in Asura_StrippedHashedFilename hasn't changed
	int iNumPlatforms = 0;
	#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
	iNumPlatforms = TOOLS_PLATFORM_NUM_PLATFORMS;
	#endif
	int iPlatformsFromFile;
	xStream >> iPlatformsFromFile;
	if(iPlatformsFromFile != iNumPlatforms)
	{
		Asura_FileAccess::Close(iHandle);
		return false;
	}


	//load full filename hash values
	int iFullFilenameNodes;
	xStream >> iFullFilenameNodes;
	for(int i=0;i<iFullFilenameNodes;i++)
	{
		Asura_Hash_ID uHash;
		int iJunkIndex;
		xStream >> uHash;
		s_pxFullFilenameHashTree->FindNodeIndexByHash(uHash,true,iJunkIndex);
	}
	
	//load stripped filename data
	int iStrippedFilenameNodes;
	xStream >> iStrippedFilenameNodes;
	for(int i=0;i<iStrippedFilenameNodes;i++)
	{
		Asura_Hash_ID uHash;
		xStream >> uHash;

		Asura_StrippedHashedFilename* pxStrippedEntry = s_pxStrippedFilenameTree->AddNode(uHash);
		Asura_Assert(pxStrippedEntry,"Failed to add node to stripped filename tree");

		xStream >> pxStrippedEntry->m_xStrippedFilename;
		xStream >> pxStrippedEntry->m_iFullIndex;
		#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
		for(int j=0;j<TOOLS_PLATFORM_NUM_PLATFORMS;j++)
		{
			xStream >> pxStrippedEntry->m_aiPlatformIndex[j];
			if(pxStrippedEntry->m_aiPlatformIndex[j] != -1) pxStrippedEntry->m_bHasPlatformVersions = true;
		}
		#endif
		

	}

	//load name lists
	s_pxFullFilenameList->LoadFromChunk(iHandle);

	Asura_FileAccess::Close(iHandle);
	return true;
}


void Asura_HashedFilename_System::Platform_SaveHashedFilenames()
{
	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_SaveBlock_Stream xStream(&xSaveBlock);

	//Make sure the platform is set to PC in Tools_Level_Export so that no endian swapping happens when saving the name lists
	//RAR 07/11/2006
	Tools_Level_Export::EXPORT_TYPE eInitialPlatform = Tools_Level_Export::s_eExportType;
	Tools_Level_Export::s_eExportType = Tools_Level_Export::Export_PC;
	
	xStream.SetSwapEndian(false);

	//save a version number
	xStream << g_iHashedFilenameVersion;

	//save number of platforms in Asura_StrippedHashedFilename;
	int iNumPlatforms = 0;
	#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
	iNumPlatforms = TOOLS_PLATFORM_NUM_PLATFORMS;
	#endif
	xStream << iNumPlatforms;


	//save full filename hash values
	int iNumNodes = s_pxFullFilenameHashTree->GetNumberOfNodes();
	Asura_Hash_ID* puHashList = new Asura_Hash_ID[iNumNodes];
	s_pxFullFilenameHashTree->MakeHashList(puHashList);
	
	xStream << iNumNodes;
	for(int i=0;i<iNumNodes;i++)
	{
		xStream << puHashList[i];
	}

	delete [] puHashList;
	

	//save stripped filename tree
	iNumNodes = s_pxStrippedFilenameTree->GetNumberOfNodes();
	xStream << iNumNodes;
	
	if(iNumNodes)
	{
		for(Asura_TemplateHashTree_SaveIterator xIt(iNumNodes);!xIt.Done();xIt.Next())
		{
			Asura_StrippedHashedFilename* pxStrippedEntry = s_pxStrippedFilenameTree->GetDataByIndex(xIt.GetCurrent());

			xStream << s_pxStrippedFilenameTree->GetHashByIndex(xIt.GetCurrent());
			xStream << pxStrippedEntry->m_xStrippedFilename;
			xStream << pxStrippedEntry->m_iFullIndex;
			#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
			for(int i=0;i<TOOLS_PLATFORM_NUM_PLATFORMS;i++)
			{
				xStream << pxStrippedEntry->m_aiPlatformIndex[i];
			}
			#endif
		}
	}
	

	//save name lists
	s_pxFullFilenameList->WriteToChunkStream(xStream);

	xSaveBlock.SaveFile(g_pszHashedFilenameFile,false);

	//Restore the export platform
	Tools_Level_Export::s_eExportType = eInitialPlatform;	
}


#endif // #ifdef ASURA_ENABLE_HASH_TO_FILENAME_LOOKUPS
