//
// Tools_FileSystem.cpp
//
// Created by Kevin Lea - 02/07/2001 11:42:36
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//


#include "Core/Asura.h"
#include <shlobj.h>
#include <float.h>
#include <Commdlg.h>

#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "Core/Asura_NameTree.h"
#include "Core/Asura_Maths.h"
#include "Core/Memory/Asura_Mem.h"
#include "FileAccess/Asura_FileAccess.h"
#include "PC/FileSystem/Tools_FileSystem.h"

#ifdef ASURA_MAIN
#include "Core/Asura_Windows.h"
#include "Core/Asura_PlatformBasics.h"

#include "Rendering/Asura_Direct3D.h"
#include "Core/Asura_UserInput.h"
#endif

#include "Debugging/Asura_Assert.h"

static bool g_bOneOff = true;
static const int iMAX_FILENAME_LENGTH = 65535;
static Asura_Char g_szFilenameStorage[iMAX_FILENAME_LENGTH+1];
static Asura_Char* g_szNextFilenameStart = NULL;

UINT_PTR CALLBACK OFNHookProc(
  HWND hdlg,      // handle to child dialog box
  UINT uiMsg,     // message identifier
  WPARAM wParam,  // message parameter
  LPARAM lParam   // message parameter
)
{
	switch(uiMsg)
	{
		case WM_INITDIALOG:
		{
			ShowWindow(GetParent(hdlg),SW_SHOWMAXIMIZED);
			PostMessage(GetParent(hdlg),WM_COMMAND,IDCANCEL,0);
			break;
		}
		
		default:
		{
		}
	}

	return 0;
}


//'Scoped' class for trying to restore the current directory after using the load/save dialog boxes
//RAR 25/04/2008
class Tools_DialogBoxCurrentDirectoryFix
{
public :
	Tools_DialogBoxCurrentDirectoryFix()
	{
		m_bOkay = GetCurrentDirectory(sizeof(m_szCurrentDirectory), m_szCurrentDirectory) != 0;
		Asura_Assert(m_bOkay, "Failed to get current directory");
	}

	~Tools_DialogBoxCurrentDirectoryFix()
	{
		//DLH's comment copied from elsewhere, since I guess it still applies :
		// DLH - 10 May 2006 12:24:54 - Added this to allow SOMETHING! to shut down correctly after the FileDialog. Else the working directory ends up being changed to the location of the loaded file.
		Sleep(100);

		//restore the directory now
		if(m_bOkay)
		{
			SetCurrentDirectory(m_szCurrentDirectory);
		}
	}


private :
	bool m_bOkay;
	char m_szCurrentDirectory[MAX_PATH];
};


Asura_Char* Tools_FileSystem::GetSaveFilename(Tools_FileSystem_DialogInfo& xInfo)
{
#ifdef ASURA_MAIN
	HWND hWnd = Asura_Windows::GetWindowHandle();
#else
	HWND hWnd = 0;
#endif

	Tools_DialogBoxCurrentDirectoryFix xRestoresDirectoryWhenExitingFunction;
	
	g_szNextFilenameStart = 0;
	
	if(xInfo.DefaultFilename)
	{
		Asura_PrintfToBfr(g_szFilenameStorage, "%s", iMAX_FILENAME_LENGTH, true, xInfo.DefaultFilename);
	}
	else
	{
		g_szFilenameStorage[0] = '\0';
	}

	OPENFILENAME xOFN;

	ZeroMemory(&xOFN, sizeof(OPENFILENAME));
	// Kevin Lea - 28/06/2001 17:11:14 - note - check if okay for pre Win2000
	xOFN.lStructSize = sizeof(OPENFILENAME); //OPENFILENAME_SIZE_VERSION_400
	xOFN.hwndOwner = hWnd;
	xOFN.lpstrFilter = xInfo.FileFilter;
	xOFN.lpstrFile = g_szFilenameStorage;
	xOFN.nMaxFile = iMAX_FILENAME_LENGTH;
	xOFN.lpstrTitle = xInfo.WindowTitle;
	xOFN.Flags=OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST|OFN_ENABLEHOOK |OFN_EXPLORER|OFN_ENABLESIZING|OFN_NOCHANGEDIR;
	xOFN.lpstrDefExt = xInfo.DefaultExtension;
	xOFN.lpfnHook = OFNHookProc;
	xOFN.lpstrInitialDir = xInfo.DefaultPath;

#ifdef ASURA_MAIN
	if(g_bOneOff)
	{
		if (Asura_Windows::RunningFullScreen())
		{
			GetSaveFileName(&xOFN);
		}
		g_bOneOff = false;
	}
#endif
	

	xOFN.Flags=OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST|OFN_EXPLORER|OFN_NOCHANGEDIR|OFN_NOREADONLYRETURN;
	xOFN.lpfnHook = 0;

	
	//Store the floating point precision , since the dialog boxes appear to change it
	//RAR 22/06/2005
	const int iFloatState = _controlfp(0,0);
	
	#ifdef ASURA_MAIN
	Asura_Windows::PreShowDialogBox();
	#endif

 	const BOOL bOkay = GetSaveFileName(&xOFN);

	#ifdef ASURA_MAIN
	Asura_Windows::PostShowDialogBox();
	#endif

	//restore floating point precision
	_controlfp(iFloatState, 0xffffffff);


#ifdef ASURA_MAIN
	Asura_UserInput::Reset();
#endif

	if (bOkay)
	{
		return g_szFilenameStorage;
	}
	else
	{
		return 0;
	}

	// Kevin Lea - 02/07/2001 12:52:35 - todo - check for OFN_EXTENSIONDIFFERENT
}


Asura_String Tools_FileSystem::GetLoadFilename(Tools_FileSystem_DialogInfo& xInfo)
{
#ifdef ASURA_MAIN
	HWND hWnd = Asura_Windows::GetWindowHandle();
#else
	HWND hWnd = 0;
#endif
	Tools_DialogBoxCurrentDirectoryFix xRestoresDirectoryWhenExitingFunction;

	g_szNextFilenameStart = 0;

	if(xInfo.DefaultFilename)
	{
		Asura_PrintfToBfr(g_szFilenameStorage, "%s", iMAX_FILENAME_LENGTH, true, xInfo.DefaultFilename);
	}
	else
	{
		g_szFilenameStorage[0] = '\0';
	}

	OPENFILENAME xOFN;

	ZeroMemory(& xOFN,sizeof(OPENFILENAME));
	// Kevin Lea - 28/06/2001 17:11:14 - note - check if okay for pre Win2000
	xOFN.lStructSize = sizeof(OPENFILENAME); //OPENFILENAME_SIZE_VERSION_400
	xOFN.hwndOwner = hWnd;
	xOFN.lpstrFilter = xInfo.FileFilter;
	xOFN.lpstrFile = g_szFilenameStorage;
	xOFN.nMaxFile = iMAX_FILENAME_LENGTH;
	xOFN.lpstrTitle = xInfo.WindowTitle;
	xOFN.Flags=OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST|OFN_ENABLEHOOK |OFN_EXPLORER|OFN_ENABLESIZING|OFN_NOCHANGEDIR;
	xOFN.lpstrDefExt = xInfo.DefaultExtension;
	xOFN.lpfnHook = OFNHookProc;
	xOFN.lpstrInitialDir = xInfo.DefaultPath;

	//Store the floating point precision , since the dialog boxes appear to change it
	//RAR 22/06/2005
	const int iFloatState = _controlfp(0,0);


#ifdef ASURA_MAIN
	if(g_bOneOff)
	{
		if (Asura_Windows::RunningFullScreen())
		{
			GetOpenFileName(&xOFN);
		}
		g_bOneOff = false;
	}
#endif
	

	xOFN.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_EXPLORER|OFN_NOCHANGEDIR|OFN_HIDEREADONLY;
	if(xInfo.AllowMultipleSelect)
	{
		xOFN.Flags |= OFN_ALLOWMULTISELECT;
	}
	xOFN.lpfnHook = 0;

	#ifdef ASURA_MAIN
	Asura_Windows::PreShowDialogBox();
	#endif

	const BOOL bOkay = GetOpenFileName(&xOFN);

	#ifdef ASURA_MAIN
	Asura_Windows::PostShowDialogBox();
	#endif


	//restore floating point precision
	_controlfp(iFloatState, 0xffffffff);

#ifdef ASURA_MAIN
	Asura_UserInput::Reset();
#endif		


	if (bOkay)
	{
		if(xInfo.AllowMultipleSelect)
		{
			g_szNextFilenameStart = g_szFilenameStorage + strlen(g_szFilenameStorage) + 1;
			if(*g_szNextFilenameStart)
			{
				//more than one file was selected , so use GetNextLoadFilename to attach the path and filename
				return GetNextLoadFilename();
			}
			//only one file selected , so just return the buffer as is
			g_szNextFilenameStart = 0;
		}
		
	   	return g_szFilenameStorage;
		
	}
	else
	{
		return 0;
	}

}

Asura_String Tools_FileSystem::GetNextLoadFilename()
{
	if(!g_szNextFilenameStart) return 0;
	
	if(!*g_szNextFilenameStart)
	{
		//no more filenames to extract
		g_szNextFilenameStart = 0;
		return 0;
	}
	
	//get the path
	Asura_String Filename = g_szFilenameStorage;

	Filename += "\\";
	//add on the filename
	Filename += g_szNextFilenameStart;

	//advance the nextfilename pointer
	g_szNextFilenameStart += strlen(g_szNextFilenameStart)+1;

	return Filename;
}
///////////////////////////////////////////////////////////////////////////////////////////////////


static BOOL GetItemListFromPath(const char* path,LPITEMIDLIST & pidl)
{
   LPSHELLFOLDER pDesktopFolder;
   char          szPath[MAX_PATH];
   OLECHAR       olePath[MAX_PATH];
   ULONG         chEaten;
   ULONG         dwAttributes;
   HRESULT       hr;

   //
   // Get the path to the file we need to convert.
   //
   strcpy(szPath,path);

   //
   // Get a pointer to the Desktop's IShellFolder interface.
   //
   if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder)))
   {
       //
       // IShellFolder::ParseDisplayName requires the file name be in
       // Unicode.
       //
       MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1,
                           olePath, MAX_PATH);

       //
       // Convert the path to an ITEMIDLIST.
       //
       hr = pDesktopFolder->ParseDisplayName(NULL,
                                           NULL,
                                           olePath,
                                           &chEaten,
                                           &pidl,
                                           &dwAttributes);
       if (FAILED(hr))
       {
           // Handle error.
		   return FALSE;
       }

       //
       // pidl now contains a pointer to an ITEMIDLIST for .\readme.txt.
       // This ITEMIDLIST needs to be freed using the IMalloc allocator
       // returned from SHGetMalloc().
       //

       //release the desktop folder object
        pDesktopFolder->Release();
		return TRUE;
   }
   return FALSE;
}

static void ReleaseItemIDList(LPITEMIDLIST item)
{
	LPMALLOC shell;

	if (SHGetMalloc(&shell) == NO_ERROR)
	{
		if (item)
		{
			shell->Free(item);
			shell->Release();
		}
	}
}

#if 1
static int CALLBACK BrowseFolderCallback(
  HWND hdlg,      // handle to child dialog box
  UINT uiMsg,     // message identifier
  WPARAM wParam,  // message parameter
  LPARAM lParam   // message parameter
)
{
	switch(uiMsg)
	{
		case BFFM_INITIALIZED :
		{
			//If an initial directory was specified , select it now
			Tools_FileSystem_SelectFolderInfo* pxInfo = (Tools_FileSystem_SelectFolderInfo*) lParam;
			if(pxInfo->InitialSelection)
			{
				char szDir[MAX_PATH];
				if(strnicmp(pxInfo->InitialSelection,pxInfo->StartDirectory,strlen(pxInfo->StartDirectory)))
				{
					//need to convert to absolute path
					Asura_PrintfToBfr(szDir,"%s\\%s",MAX_PATH,true,pxInfo->StartDirectory,pxInfo->InitialSelection);
				}
				else
				{
					strncpy(szDir,pxInfo->InitialSelection,MAX_PATH);
				}
				
				SendMessage(hdlg,BFFM_SETSELECTION,TRUE,(LPARAM)szDir);
			}
			break;
		}
		
		default:
		{
		}
	}

	return 0;
}
#endif

bool Tools_FileSystem::SelectFolder(Tools_FileSystem_SelectFolderInfo& info)
{
#ifdef ASURA_MAIN
	HWND hWnd = Asura_Windows::GetWindowHandle();
#else
	HWND hWnd = 0;
#endif

	Tools_DialogBoxCurrentDirectoryFix xRestoresDirectoryWhenExitingFunction;

	//Store the floating point precision , since the dialog boxes appear to change it
	//RAR 22/06/2005
	int iFloatState = _controlfp(0,0);

	
	char displayname[MAX_PATH];

	info.Error = Tools_FileSystem_SelectFolderInfo::Error_None;
	
	//get the item list for the starting directory
	LPITEMIDLIST pidlRoot = NULL;
	if(!GetItemListFromPath(info.StartDirectory,pidlRoot))
	{
		info.Error = Tools_FileSystem_SelectFolderInfo::Error_DirectoryDoesntExist;
		return false;
	}

	BROWSEINFO browse = {hWnd,pidlRoot,displayname,info.DisplayText,BIF_VALIDATE|BIF_STATUSTEXT,(BFFCALLBACK)BrowseFolderCallback,(LPARAM)&info,0 };

	#ifdef ASURA_MAIN
	Asura_Windows::PreShowDialogBox();
	#endif
 	
	LPITEMIDLIST item = SHBrowseForFolder(&browse);

	#ifdef ASURA_MAIN
	Asura_Windows::PostShowDialogBox();
	#endif

	
	ShowWindow(hWnd,SW_SHOW);


	//restore floating point precision
	_controlfp(iFloatState,0xffffffff);


#ifdef ASURA_MAIN
	Asura_UserInput::Reset();
#endif

	ReleaseItemIDList(pidlRoot);


	if (!item) return false;

	SHGetPathFromIDList(item,info.SelectedFolder);
	ReleaseItemIDList(item);

	return true;

}

// Delete given file name
bool Tools_FileSystem::DeleteFileByName( const char* szFilename )
{
	return ( DeleteFile( szFilename ) != 0 );
}

bool Tools_FileSystem::DeleteFolderByName( const char* szFilename )
{
	return ( RemoveDirectory( szFilename ) != 0 );
}

// Only find files in the specified directory
void Tools_FileSystem::FindFiles( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags )
{
	FindFilesInternal( szSearchString, xOutputList, uFlags );
}

// Find files in the path and network directories
void Tools_FileSystem::FindFilesLocalAndNetwork( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags )
{
	const char* szRelSearchString = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szSearchString);

	//find local files
	Tools_FileSystem::FindFilesInternal(szRelSearchString, xOutputList, uFlags );

#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
	const char* szNetDir = Asura_FileAccess::PlatformGetNetworkPath();
	//find network files
	if(szNetDir && * szNetDir)
	{
		//add all the local filenames found to a name tree
		Asura_StringTree xStringTree;
		for(LIF<Asura_String> xOutputLIF(&xOutputList); !xOutputLIF.done(); xOutputLIF.next())
		{
			int iJunkIndex;
			xStringTree.FindNodeIndexByName(xOutputLIF().GetString(),true,iJunkIndex);
		}
		
		//get a list of files on the network
		char szNetSearch[MAX_PATH+20];
		Asura_PrintfToBfr(szNetSearch,"%s\\%s",sizeof(szNetSearch),true,szNetDir,szRelSearchString);

		List<Asura_String> xNetworkFileList;
		Tools_FileSystem::FindFilesInternal(szNetSearch, xNetworkFileList, uFlags );

		//go through the list of network filenames add any to the output list that haven't already been done
		for(LIF<Asura_String> xNetworkFileLIF(&xNetworkFileList); !xNetworkFileLIF.done(); xNetworkFileLIF.next())
		{
			const char* szNetFilename = xNetworkFileLIF().GetString();
			const char* szRelativeName = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szNetFilename);

			int iJunkIndex;
			if(xStringTree.FindNodeIndexByName(szRelativeName, true, iJunkIndex)) continue;

			xOutputList.add_entry(szRelativeName);
		}
	}
#endif //ASURA_PC_ALLOW_LOAD_FROM_NETWORK
}

void Tools_FileSystem::FindFilesInternal( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags )
{
    Asura_Assert( szSearchString && *szSearchString, "" );

    List<Asura_String> xDirList;

    const char* szWildcard;

    {
		const char* szSlash = NULL;

		// Locate the rightmost slash - this might be a forward slash even if the string contains
		// other backslashes
		{
			const char* const szBackSlash = strrchr( szSearchString, '\\' );
			const char* const szForwardSlash = strrchr( szSearchString, '/' );

			if( szBackSlash && szForwardSlash )
			{
				const ptrdiff_t iBackSlashPos = szBackSlash - szSearchString;
				const ptrdiff_t iForwardSlashPos = szForwardSlash - szSearchString;

				szSlash = iBackSlashPos > iForwardSlashPos ? szBackSlash : szForwardSlash;
			}
			else
			{
				szSlash = szBackSlash ? szBackSlash : szForwardSlash;
			}
		}

        char szTempBuffer[MAX_PATH];
        
        if( szSlash && szSlash > szSearchString )
        {
            const int iLen = int(szSlash - szSearchString);
            strncpy( szTempBuffer, szSearchString, iLen );
            szTempBuffer[iLen] = '\0';
            xDirList.add_entry( szTempBuffer );
        }
        else
        {
            xDirList.add_entry(".");
        }

        if( uFlags & TOOLS_FILEFIND_RECURSIVE )
        {
            const Asura_String& xRoot = xDirList[0];
            strcpy( szTempBuffer, xRoot.GetString() );
            strcpy( &szTempBuffer[xRoot.GetLength()], "\\*.*" );
            FindDirectories( szTempBuffer, xDirList, uFlags );
        }

        if( szSlash && *(szSlash + 1) )
        {
            szWildcard = szSlash + 1;
        }
        else
        {
            szWildcard = szSearchString;
        }
    }

//    const int iLengthOfWildcard = strlen( szWildcard );

	char szBuffer[MAX_PATH];

    for( CLIF<Asura_String> xIt(&xDirList); !xIt.done(); xIt.next() )
    {
        char* szFilenameStart;

        //copy the search string into a buffer for buidling the filenames , 
	    //and work out where the filename part should be copied
        {
            const Asura_String& xNext = xIt();
            const int iLen = xNext.GetLength();
            
            strcpy( szBuffer, xNext.GetString() );
            szBuffer[iLen] = '\\';
            strcpy( &szBuffer[iLen + 1], szWildcard );

            szFilenameStart = &szBuffer[iLen + 1];
        }

	    WIN32_FIND_DATA xFindData;
   	    HANDLE hFindFile = ::FindFirstFile( szBuffer, &xFindData );
	    if( INVALID_HANDLE_VALUE != hFindFile )
	    {
		    do
		    {	
			    if( !(xFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
			    {
                    // (MP - 17.03.2006) Make sure this really does match
                    // the wildcard. The file search stuff in Windows does
                    // some slightly odd stuff in specific cases. For example,
                    // *.asr will match *.asrBE, though bizarrely
                    // *.a will not match *.asr. I think it's related to
                    // 3 letter extensions somewhow.
                    if( Asura_StringUtil::CompareAgainstWildcardString(xFindData.cFileName, szWildcard, true) )
                    {
   				        strcpy( szFilenameStart, xFindData.cFileName );
   				        xOutputList.add_entry( szBuffer );
                    }
			    }
		    }
		    while( ::FindNextFile(hFindFile, &xFindData) );

		    ::FindClose( hFindFile );
	    }
    }
}

void Tools_FileSystem::FindDirectories( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags /*=0*/ )
{
	FindDirectoriesInternal( szSearchString, xOutputList, uFlags );
}

void Tools_FileSystem::FindDirectoriesLocalAndNetwork( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags )
{
	const char* szRelSearchString = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szSearchString);

	FindDirectoriesInternal( szRelSearchString, xOutputList, uFlags );

#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
	const char* szNetDir = Asura_FileAccess::PlatformGetNetworkPath();
	//find network directories
	if(szNetDir && *szNetDir)
	{
		//add all the local directories found to a name tree
		Asura_StringTree xStringTree;
		for(LIF<Asura_String> xOutputLIF(&xOutputList); !xOutputLIF.done(); xOutputLIF.next())
		{
			int iJunkIndex;
			xStringTree.FindNodeIndexByName(xOutputLIF().GetString(),true,iJunkIndex);
		}

		//get a list of directories on the network
		char szNetSearch[MAX_PATH+20];
		Asura_PrintfToBfr(szNetSearch,"%s\\%s",sizeof(szNetSearch),true,szNetDir,szRelSearchString);

		List<Asura_String> xNetworkDirectoryList;
		Tools_FileSystem::FindDirectoriesInternal(szNetSearch, xNetworkDirectoryList, uFlags );

		//go through the list of network directories add any to the output list that haven't already been done
		for(LIF<Asura_String> xNetworkFileLIF(&xNetworkDirectoryList); !xNetworkFileLIF.done(); xNetworkFileLIF.next())
		{
			const char* szNetDirName = xNetworkFileLIF().GetString();
			const char* szRelativeName = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szNetDirName);

			int iJunkIndex;
			if(xStringTree.FindNodeIndexByName(szRelativeName, true, iJunkIndex)) continue;

			xOutputList.add_entry(szRelativeName);
		}
	}
#endif //ASURA_PC_ALLOW_LOAD_FROM_NETWORK
}

void Tools_FileSystem::FindDirectoriesInternal( const char* szSearchString, List<Asura_String>& xOutputList, unsigned uFlags )
{
	Asura_Assert( szSearchString && *szSearchString, "" );

	LIF<Asura_String> xIt( &xOutputList );
	xIt.end();

	char szBuffer[MAX_PATH];
	WIN32_FIND_DATA xFindData;

	bool bRecursive = (uFlags & TOOLS_FILEFIND_RECURSIVE) ? true : false;
	const int iInitialNumEntries = xOutputList.size();
	for( int iDir = 0; iDir < 1 + (bRecursive ? (xOutputList.size() - iInitialNumEntries) : 0); ++iDir )
	{
		char* szFilenameStart;

		//copy the search string into a buffer for buidling the filenames , 
		//and work out where the filename part should be copied
		{
			if( iDir )
			{
				if(iDir==1 && iInitialNumEntries==0)
				{
					//If there were no entries in the list to start with , then the iterator will currently be
					//on the sentinel , so we need to restart to get to the first of the newly added entries.
					//If not , then the iterator will be on the last of the original entries , so using next()
					//will get us to the first newly added entry.RAR 17/12/03
					xIt.restart();
				}
				else
				{
					xIt.next();
				}

				const Asura_String& xNext = xIt();
				strcpy( szBuffer, xNext.GetString() );
				strcpy( &szBuffer[xNext.GetLength()], "\\*.*" );

			}
			else
			{
				strcpy( szBuffer, szSearchString );
			}

			char* szSlash = strrchr( szBuffer, '\\' );
			if( !szSlash )
			{
				szSlash = strrchr( szBuffer, '/' );
			}
			szFilenameStart = ( szSlash ? szSlash + 1 : szBuffer );
		}

		HANDLE hFindFile = ::FindFirstFile( szBuffer, &xFindData );
		if( INVALID_HANDLE_VALUE != hFindFile )
		{
			do
			{	
				if( xFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
				{

					// Ignore the "." and ".." directories.
					if( '.' == xFindData.cFileName[0] )
					{
						if( !xFindData.cFileName[1] || ('.' == xFindData.cFileName[1] && !xFindData.cFileName[2]) )
						{
							continue;
						}
					}

					if(uFlags & TOOLS_FILEFIND_IGNOREBACKUPDIR)
					{
						if(!stricmp(xFindData.cFileName,"Backup") || !stricmp(xFindData.cFileName,"Logfiles") || !stricmp(xFindData.cFileName,"Autosave"))
						{
							continue;
						}
					}

					strcpy( szFilenameStart, xFindData.cFileName );
					xOutputList.add_entry_end( szBuffer );
				}
			}
			while( ::FindNextFile(hFindFile, &xFindData) );

			::FindClose( hFindFile );
		}
	}
}

void Tools_FileSystem::LoadAsuraFiles(const char* szSearchString, unsigned uFlags /*= 0*/)
{
	List<Asura_String> xFileList;
	Tools_FileSystem::FindFilesLocalAndNetwork( szSearchString, xFileList, uFlags );

	Asura_NameTree xNameTree;

	//load the files in the list
	for(LIF<Asura_String> xFileLIF(&xFileList);!xFileLIF.done();xFileLIF.next())
	{
		//make sure this file hasn't already been dealt with
		const char* szName = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(xFileLIF().GetString());

		int iJunkIndex;
		if(xNameTree.FindNodeIndexByName(szName,true,iJunkIndex)) continue;

		LoadAsuraFile(xFileLIF().GetString());
	}
}

char* Tools_FileSystem::ReadAsuraFile(const char* szFilename, Tools_ReadAsuraFileOutput* pxOutput /* = 0 */,bool bIncludeFileHeader /* = false */,bool bFailureHandledExternally /* = false */)
{
	Asura_Assert(szFilename, "");

	if(pxOutput) Asura_Mem::Set(pxOutput, 0, sizeof(Tools_ReadAsuraFileOutput));

	Asura_Handle iHandle = Asura_FileAccess::Open(szFilename,false,bFailureHandledExternally);
	if(iHandle == ASURA_INVALID_HANDLE)
	{
		if(pxOutput) pxOutput->m_uErrorCode = TOOLS_READASURAFILE_FAILED_TO_OPEN_FILE;
		return 0;
	}

	//Parse the file header , this will confirm whether it is an asura file , and tell the file system
	//the compression being used
	if(!Asura_FileAccess::ProcessAsuraFileHeader(iHandle, bFailureHandledExternally))
	{
		Asura_FileAccess::Close(iHandle,NULL,bFailureHandledExternally);
		if(pxOutput) pxOutput->m_uErrorCode = TOOLS_READASURAFILE_NOT_ASURA_FILE;
		return 0;
	}

	if(pxOutput) pxOutput->m_uCompression = Asura_FileAccess::GetCompressionType(iHandle);

	//determine the size of the rest of the file
	u_int uFileSize = Asura_FileAccess::GetDecompressedFileSize(iHandle);
	u_int uDataSize = uFileSize - ASURA_FILE_ID_NUM_CHARS;
	if(!uDataSize)
	{
		//Should at least have some space for the terminator in a legal file
		Asura_FileAccess::Close(iHandle,NULL,bFailureHandledExternally);
		if(pxOutput) pxOutput->m_uErrorCode = TOOLS_READASURAFILE_NOT_ASURA_FILE;
		return 0;
	}

	u_int uBufferSize = bIncludeFileHeader ? uFileSize : uDataSize;

	char* pcBuffer = new char[uBufferSize];

	char* pcDest = pcBuffer;
	if(bIncludeFileHeader)
	{
		//add the header for uncompressed files
		strncpy(pcDest,ASURA_UNCOMPRESSED_FILE_ID,ASURA_FILE_ID_NUM_CHARS);
		pcDest += ASURA_FILE_ID_NUM_CHARS;
	}

	//and read it all in
	//do this using multiple reads if dealing with a large file , to avoid the suspiciously large read assert
	u_int uAmountLeft = uDataSize;
	char* pcPos = pcDest;
	bool bReadOkay = true;

	while(uAmountLeft && bReadOkay)
	{
		u_int uAmountToRead = Asura_Maths::Min(1024u*1024u, uAmountLeft);
		bReadOkay &= Asura_FileAccess::ReadData(iHandle, pcPos, uAmountToRead,bFailureHandledExternally);

		pcPos += uAmountToRead;
		uAmountLeft -= uAmountToRead;
	}

	Asura_FileAccess::Close(iHandle,NULL,bFailureHandledExternally);


	if(!bReadOkay)
	{
		Asura_Assert(bFailureHandledExternally,"Failed to read correct amount from file");
		delete [] pcBuffer;
		if(pxOutput) pxOutput->m_uErrorCode = TOOLS_READASURAFILE_ERROR_READING_FILE;
		return 0;
	}

	if(pxOutput)
	{
		pxOutput->m_uBufferSize= uBufferSize;
		pxOutput->m_uErrorCode = TOOLS_READASURAFILE_OK;
	}

	return pcBuffer;
}

void Tools_FileSystem::EnsureDirectoryExists(const char * n, bool bAllowDotInDirectoryName /* = false */)
{
	char const replace = '|';

	char * nn = new char[strlen(n)+3];
	strcpy(nn,n);

	char * np;

	while((np = strchr(nn,'\\'))!=0) *np=replace;
	while((np = strchr(nn,'/'))!=0) *np=replace;

	while ((np = strchr(nn,replace))!=0)
	{
		*np = 0;
		// reject "","x:"
		if (strlen(nn)>2 || strlen(nn) && nn[1] != ':') CreateDirectory(nn,0);
		*np = '\\';
	}
	if (!strchr(nn,'.') || bAllowDotInDirectoryName)
	{
		unsigned int nnlen = u_int( strlen(nn));
		// reject "","\","x:","x:\"
		if (nn[1] != ':' && (nnlen>1 || nnlen && nn[0] != '\\')	|| nnlen>3 || nnlen>2 && nn[2] != '\\')
			CreateDirectory(nn,0);
	}
	delete[] nn;
}

char const * Tools_FileSystem::StripPath(char const * pszPath)
{
	char const * pszRemainder = strrchr(pszPath,':');
	if (pszRemainder) pszPath = pszRemainder+1;
	pszRemainder = strrchr(pszPath,'/');
	if (pszRemainder) pszPath = pszRemainder+1;
	pszRemainder = strrchr(pszPath,'\\');
	if (pszRemainder) pszPath = pszRemainder+1;

	return pszPath;
}

char* Tools_FileSystem::StripPath(char* szPath)
{
	return const_cast<char*>(StripPath(const_cast<const char*>(szPath)));
}


bool Tools_FileSystem::IsReadOnly(const char* pszFilename)
{
	Asura_Assert(pszFilename,"");

	DWORD uAttributes = GetFileAttributes(pszFilename);
	
	if((uAttributes & FILE_ATTRIBUTE_READONLY) && uAttributes!=0xffffffff)
	{
		return true;
	}
	return false;
}

bool Tools_FileSystem::IsDirectory(const char* pszFilename)
{
	Asura_Assert(pszFilename,"");

	DWORD uAttributes = GetFileAttributes(pszFilename);
	
	if((uAttributes & FILE_ATTRIBUTE_DIRECTORY) && uAttributes!=0xffffffff)
	{
		return true;
	}
	return false;
}

bool Tools_FileSystem::FileExists( const char* szFilename )
{
    HANDLE hFile = CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL );
    if( INVALID_HANDLE_VALUE == hFile )
    {
        return false;
    }
    else
    {
        CloseHandle( hFile );
        return true;
    }
}

bool Tools_FileSystem::FileExists_CheckNetwork( const char* szFilename )
{
	if(FileExists(szFilename)) return true;

	#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
	char szNetFilename[MAX_PATH];
	Asura_FileAccess::PlatformGetNetworkFilename(szFilename,szNetFilename,MAX_PATH);
	return FileExists(szNetFilename);
	#else
	return false;
	#endif
}

bool Tools_FileSystem::FileExists_CheckNetworkOnly( const char* szFilename )
{
	#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
	Asura_String xNetFilename( Asura_FileAccess::PlatformGetNetworkPath() );
	if ( xNetFilename.IsBlank() ) { return false; }
	xNetFilename += "\\";
	xNetFilename += szFilename;
	return FileExists( xNetFilename.GetString() );
	#else
	return false;
	#endif
}

bool Tools_FileSystem::MakeWriteable(const char* pszFilename)
{
	Asura_Assert(pszFilename,"");
	DWORD uAttributes = GetFileAttributes(pszFilename);

	if(uAttributes == 0xffffffff) return false;//file doesn't exist
	if(uAttributes & FILE_ATTRIBUTE_READONLY)
	{
		return SetFileAttributes(pszFilename,uAttributes &~ FILE_ATTRIBUTE_READONLY) ? true : false;
	}
	return true;//already writeable
}

bool Tools_FileSystem::DoModifiedTimesMatch(const char* pszSourceFilename,const char* pszDestFilename,float fTolerance /*= 5.0f*/)
{
	FILETIME xSourceTime;
	FILETIME xDestTime;

	{
		WIN32_FILE_ATTRIBUTE_DATA xFileAttribs = { 0};
		BOOL bAttribStatus = GetFileAttributesEx( pszSourceFilename, GetFileExInfoStandard, &xFileAttribs);
		if( 0 == bAttribStatus)
		{
			return( false);
		}
		xSourceTime = xFileAttribs.ftLastWriteTime;
	}
	{
		WIN32_FILE_ATTRIBUTE_DATA xFileAttribs = { 0};
		BOOL bAttribStatus = GetFileAttributesEx( pszDestFilename, GetFileExInfoStandard, &xFileAttribs);
		if( 0 == bAttribStatus)
		{
			return( false);
		}
		xDestTime = xFileAttribs.ftLastWriteTime;
	}

	return ApproxCompareFileTime(&xSourceTime,&xDestTime,fTolerance)==0;
}

int Tools_FileSystem::ApproxCompareFileTime(const FILETIME* pxTime1,const FILETIME* pxTime2,float fTolerance /*= 5.0f*/)
{
	if(fTolerance)
	{
		//see if the times lie within the tolerance level
		LONGLONG xDiff = *(const LONGLONG*)pxTime1 - *(const LONGLONG*)pxTime2;
		if(xDiff < 0) xDiff = -xDiff;
		//If the difference is large , then subtract an hour from it to allow for times
		//that are just out as a result of daylight saving time differences.
		//RAR 09/11/2006
		const LONGLONG xHour = (LONGLONG)3600*10000000;
		if(xDiff > xHour/2)
		{
			xDiff -= xHour;
			if(xDiff < 0) xDiff = -xDiff;
		}
		LONGLONG xTol = (LONGLONG) fTolerance*10000000;//FILETIMEs are measured in 10ths of a microsecond
		if(xDiff<xTol) return 0;
	}

	return CompareFileTime(pxTime1,pxTime2);
	
}

void Tools_FileSystem::CopyFileTimes(const char* pszSourceFilename,const char* pszDestFilename)
{
	
	//get the time for the source file
	WIN32_FILE_ATTRIBUTE_DATA xFileAttribs = { 0};
	BOOL bAttribStatus = GetFileAttributesEx( pszSourceFilename, GetFileExInfoStandard, &xFileAttribs);
	if(!bAttribStatus) return;

	//and set on the destinatiom
	HANDLE hand= CreateFile(pszDestFilename,GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);
	if(hand == INVALID_HANDLE_VALUE) return;
	SetFileTime(hand, &xFileAttribs.ftCreationTime, &xFileAttribs.ftLastAccessTime, &xFileAttribs.ftLastWriteTime);
	CloseHandle(hand);
}

bool Tools_FileSystem::GetModifiedTime(const char* pszFilename,FILETIME* pxModifiedTime)
{
	WIN32_FILE_ATTRIBUTE_DATA xFileAttribs = { 0};
	BOOL bAttribStatus = GetFileAttributesEx( pszFilename, GetFileExInfoStandard, &xFileAttribs);
	if( 0 == bAttribStatus)
	{
		return( false);
	}
	*pxModifiedTime = xFileAttribs.ftLastWriteTime;

	return true;
	
}

void Tools_FileSystem::GenerateFilenameForConvertedFile(const char* pszOriginalFilename,const char* pszSubdirectoryName,const char* pszExtension,Asura_String& xOutput)
{
	char szOutput[MAX_PATH];

	Asura_Assert(pszOriginalFilename,"");
	//Convert to a relative filename if we can (this will also have a side effect of making sure the
	//filename is local , rather than on alienbrain)
	pszOriginalFilename = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(pszOriginalFilename);


	Asura_String xFullFilename = pszOriginalFilename;
	Asura_String xPath,xName,xExtension;

	xFullFilename.SplitFilename(&xPath,&xName,&xExtension);

	
	char* pszOutput = szOutput;
	//add the original path (if any)
	if(!xPath.IsBlank())
	{
		strcpy(pszOutput,xPath.GetString());
		pszOutput += strlen(pszOutput);
		*pszOutput++ = '\\';
	}

	//if there is a subdirectory name , add that
	if(pszSubdirectoryName)
	{
		strcpy(pszOutput,pszSubdirectoryName);
		pszOutput += strlen(pszOutput);
		*pszOutput++ = '\\';
	}

	//add the filename
	strcpy(pszOutput,xName.GetString());
	pszOutput += strlen(pszOutput);
	*pszOutput++ = '.';

	//add the extension
	strcpy(pszOutput,pszExtension ? pszExtension : xExtension.GetString());

	xOutput = szOutput;
}


bool Tools_FileSystem::GetShadowConversionFilename(const char* pszLocalFilename,Asura_String* pxOutputFilename, Asura_Hash_ID uDataHash /*=0*/)
{
	static char s_szShadowPath[MAX_PATH] = "";
	static bool s_bFirstTime = true;

	if(s_bFirstTime)
	{
		s_bFirstTime = false;
		//need to get the shadow path from file
		HANDLE pFile = CreateFile( "ConvertedFileShadowPath.cfg", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,FILE_FLAG_RANDOM_ACCESS, 0 );
		if(pFile == INVALID_HANDLE_VALUE) 
	    {
	        return false;
	    }

		//read the contents of the file into a buffer
		int iFileSize = GetFileSize(pFile,0);
	 	unsigned long uBytesRead;
		char* pcBuffer = new char[iFileSize+1];
		pcBuffer[iFileSize] = 0;
		ReadFile(pFile,pcBuffer,iFileSize,&uBytesRead,0);
		CloseHandle(pFile);

		if((int)uBytesRead == iFileSize)
		{
			//scan for the start of the path
			char* pcPos = pcBuffer;
			while(*pcPos == ' ' || *pcPos == '\t' || *pcPos == '\r' || *pcPos == '\n') pcPos++;

			//copy the rest of the string
			strncpy(s_szShadowPath,pcPos,MAX_PATH);

			//search for the last non-space character on the line to put in a terminator
			//(also remove trailing slashes)
			pcPos = s_szShadowPath;
			char* pcLastNonSpace = pcPos;

			while(*pcPos && *pcPos != '\t' && *pcPos != '\r' && *pcPos != '\n')
			{
				if(*pcPos != ' ' && *pcPos != '\\' && *pcPos != '/') pcLastNonSpace = pcPos;
				pcPos++;
			}
			*(pcLastNonSpace+1) = 0;
		}

		delete [] pcBuffer;
	}

	if(!*s_szShadowPath) return false;//no shadow path
	
	Asura_Assert(pxOutputFilename,"");

	char szPath[MAX_PATH];

	Asura_PrintfToBfr(szPath,"%s\\%s",MAX_PATH-10,true,s_szShadowPath,pszLocalFilename);
	//If there is a hash value than add it to the filename , just before the extension
	if(uDataHash)
	{
		//get to the end of the path , removing the extension
		char* pcEndOfPath = strrchr(szPath,'.');
		if(pcEndOfPath)
		{
			*pcEndOfPath = 0;
		}
		else
		{
			pcEndOfPath = szPath + strlen(szPath);
		}

		//tack on the hash value 
		Asura_PrintfToBfr(pcEndOfPath,"_%08x",10,true,uDataHash);

		//and add the extension back
		const char* pcExtension = strrchr(pszLocalFilename, '.');
		if(pcExtension)
		{
			pcEndOfPath += strlen(pcEndOfPath);
			u_int uSpaceLeft = MAX_PATH-(pcEndOfPath-szPath);

			Asura_PrintfToBfr(pcEndOfPath,"%s",uSpaceLeft,true,pcExtension);
		}

	}

	*pxOutputFilename = szPath;
	

	return true;
}

bool Tools_FileSystem::CopyBetweenConvertedFileShadow(const char* pszSourceFilename,const char* pszDestFilename)
{
	Tools_FileSystem::EnsureDirectoryExists(pszDestFilename);

	//Get the .dat filename to copy from
	char szSourceDat[MAX_PATH];
	strcpy(szSourceDat,pszSourceFilename);
	char* pcDot = strrchr(szSourceDat,'.');
	if(pcDot)
	{
		strcpy(pcDot,".dat");

		//does the dat file to copy from actually exist?
		if(Tools_FileSystem::FileExists(szSourceDat))
		{
			//Get the .dat filename to copy to
			char szDestDat[MAX_PATH];
			strcpy(szDestDat,pszDestFilename);
			pcDot = strrchr(szDestDat,'.');
			if(!pcDot) return false;
			strcpy(pcDot,".dat");

			//Check the version numbers of the dat files (the first int)
			//If the destination has a higher version number , leave the files alone
			if(Tools_FileSystem::FileExists(szDestDat))
			{
				int iSourceVersion,iDestVersion;
				unsigned long uBytesTransferred;
				
				HANDLE handle = CreateFile(szSourceDat,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
				if(handle==INVALID_HANDLE_VALUE) return false;
				ReadFile(handle,&iSourceVersion,sizeof(int),&uBytesTransferred,0);
				CloseHandle(handle);

				handle = CreateFile(szDestDat,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
				if(handle==INVALID_HANDLE_VALUE) return false;
				ReadFile(handle,&iDestVersion,sizeof(int),&uBytesTransferred,0);
				CloseHandle(handle);
					
				if(iSourceVersion<iDestVersion)
				{
					return false;//destination is from a more recent version of tools
				}
			}
		
			//and copy the files
			Tools_FileSystem::MakeWriteable(szDestDat);
			if(!CopyFile(szSourceDat,szDestDat,FALSE)) return false;
		}
	}


	//copy the file across using the given filename
	Tools_FileSystem::MakeWriteable(pszDestFilename);
	if(!CopyFile(pszSourceFilename,pszDestFilename,FALSE))
	{
		return false;
	}

	return true;
}

bool Tools_FileSystem::CopyToConvertedFileShadow(const char* szOriginalFile, const char* pszLocalFilename, Asura_Hash_ID uDataHash /*=0*/)
{
	
	#ifdef ASURA_PC_ALLOW_LOAD_FROM_NETWORK
	//If the network directory has been disabled ,then don't attempt to copy anything to the network shadows
	const char* szNetPath = Asura_FileAccess::PlatformGetNetworkPath();
	if(!szNetPath || !*szNetPath) return false;

	//Only want to copy to the network shadows if using a file that is equivalent to the one on the network.
	//In theory the source file should match the network file unless we are using a local writeable copy.
	if(Tools_FileSystem::IsWriteableLocalFile(szOriginalFile)) return false;

	Asura_String xShadowFilename;
	if(!GetShadowConversionFilename(pszLocalFilename,&xShadowFilename, uDataHash))
	{
		return false;
	}

	return CopyBetweenConvertedFileShadow(pszLocalFilename,xShadowFilename.GetString());
	#else
	//If loading from the network is disabled , then don't copy the converted files to the network shadow
	//paths , since we can't be sure that the the local file matches the one on the network.
	//RAR 28/09/2007
	return false;
	#endif
}

bool Tools_FileSystem::CopyFromConvertedFileShadow(const char* pszLocalFilename, Asura_Hash_ID uDataHash /*=0*/)
{
	Asura_String xShadowFilename;
	if(!GetShadowConversionFilename(pszLocalFilename,&xShadowFilename,uDataHash))
	{
		return false;
	}

	return CopyBetweenConvertedFileShadow(xShadowFilename.GetString(),pszLocalFilename);
}

bool Tools_FileSystem::IsWriteableLocalFile(const char* pszFilename)
{
	DWORD uAttributes = GetFileAttributes(pszFilename);
	if(uAttributes & FILE_ATTRIBUTE_READONLY)
	{
		//read only  , or doesn't exist (if attributes are 0xffffffff)	
		return false;
	}

	//network file?
	return !Asura_FileAccess::PlatformUsingNetworkFile(pszFilename);

}

bool Tools_FileSystem::GetSizeOfFile(const char* szFilename, u_int& uOutput)
{
	uOutput = 0;
	HANDLE hFile = CreateFile(szFilename,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_FLAG_RANDOM_ACCESS,0);
	if(hFile == INVALID_HANDLE_VALUE) return false;

	uOutput = GetFileSize(hFile,0);

	CloseHandle(hFile);

	return true;
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FileSystem::Open_CheckNetwork
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Asura_Handle Tools_FileSystem::Open_CheckNetwork(const char* szFilename)
{
	char	szFullFilename[260];

	Asura_FileAccess::PlatformGetNetworkFilenameOrGetLocalCopy(szFilename, szFullFilename, 260);
	return(Asura_FileAccess::Open(szFullFilename));
}

/// End of function 'Tools_FileSystem::Open_CheckNetwork'. ///


/// End of file. ///
