////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   fileutil.cpp
//  Version:     v1.00
//  Created:     13/9/2002 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "FileUtil.h"
#include "Settings.h"
#include "CheckOutDialog.h"
#include "SrcSafeSettingsDialog.h"
#include "ISourceControl.h"
#include "SoundBrowserDialog.h"

#include "CustomFileDialog.h"
#include <CryFile.h>
#include <io.h>

CAutoRestoreMasterCDRoot::~CAutoRestoreMasterCDRoot()
{
	SetCurrentDirectoryW(GetIEditor()->GetMasterCDFolder());
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::CompileLuaFile( const char *luaFilename )
{
	CString luaFile = luaFilename;
	// Check if this file is in Archive.
	{
		CCryFile file;
		if (file.Open( luaFilename,"rb" ))
		{
			// Check if in pack.
			if (file.IsInPak())
				return true;
			luaFile = file.GetAdjustedFilename();
		}
	}
	// First try compiling script and see if it have any errors.
	CString LuaCompiler;
	CString CompilerOutput;

	// Create the filepath of the lua compiler
	 char szExeFileName[_MAX_PATH];
	// Get the path of the executable
	GetModuleFileName( GetModuleHandle(NULL), szExeFileName, sizeof(szExeFileName));
	CString exePath = Path::GetPath( szExeFileName );

	LuaCompiler = CString("\"") + Path::AddBackslash(exePath) + "LuaCompiler.exe\" ";

	luaFile.Replace( '/','\\' );
	// Add the name of the Lua file
	CString cmdLine = LuaCompiler + luaFile;

	// Execute the compiler and capture the output
	if (!GetIEditor()->ExecuteConsoleApp( cmdLine,CompilerOutput ))
	{
		AfxMessageBox("Error while executing 'LuaCompiler.exe', make sure the file is in" \
			" your Master CD folder !");
		return false;
	}

	// Check return string
	if (strlen(CompilerOutput) > 0)
	{
		// Errors while compiling file.

		// Show output from Lua compiler
		if (MessageBox( NULL,(CString("Error output from Lua compiler:\r\n") + CompilerOutput +
			CString("\r\nDo you want to edit the file ?")),"Lua Compiler", MB_ICONERROR | MB_YESNO) == IDYES)
		{
			int line = 0;
			CString cmdLine = luaFile;
			int index = CompilerOutput.Find( "at line" );
			if (index >= 0)
			{
				const char *str = CompilerOutput;
				sscanf( &str[index],"at line %d",&line );
			}
			// Open the Lua file for editing
			EditTextFile( luaFile,line );
			return false;
		}
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
void CFileUtil::EditTextFile( const char *txtFile,int line,ETextFileType fileType, bool bUseGameFolder )
{
	CString file = txtFile;

	if (bUseGameFolder)
	{
		string sGameFolder=PathUtil::GetGameFolder();
		int nLen=sGameFolder.length();
		if (strnicmp(txtFile,sGameFolder.c_str(),nLen)!=0)
		{
			file=sGameFolder+string("\\")+string(txtFile);
		}
	}

	file.Replace( '/','\\' );

	// Check if this file is in Archive.
	{
		CCryFile cryfile;
		if (cryfile.Open( file,"rb" ))
		{
			// Check if in pack.
			if (cryfile.IsInPak())
			{
				const char *sPakName = cryfile.GetPakPath();
				// Cannot edit file in pack, suggest to extract it for editing.
				CString msg;
				msg.Format( _T("File %s is inside Pak file %s\r\nDo you want it to be extracted for Editing?"),(const char*)file,sPakName );
				if (AfxMessageBox( msg,MB_YESNO|MB_ICONEXCLAMATION) == IDNO)
				{
					return;
				}
				CFileUtil::CreateDirectory( Path::GetPath(file) );
				// Extract it from Pak file.
				CFile diskFile;
				if (diskFile.Open( file,CFile::modeCreate|CFile::modeWrite))
				{
					// Copy data from packed file to disk file.
					char *data = new char[cryfile.GetLength()];
					cryfile.ReadRaw( data,cryfile.GetLength() );
					diskFile.Write( data,cryfile.GetLength() );
					delete []data;
				}
				else
				{
					Warning( "Failed to create file %s on disk",(const char*)file );
				}
			}
			else
			{
				file = cryfile.GetAdjustedFilename();
			}
		}
	}

	file.Replace( '/','\\' );
	CString cmd;
	if (line != 0)
	{
		cmd.Format( "%s/%d/0",(const char*)file,line );
	}
	else
	{
		cmd = file;
	}

	CString TextEditor = gSettings.textEditorForScript;
	if (fileType == FILE_TYPE_SHADER)
	{
		TextEditor = gSettings.textEditorForShaders;
	}

	HINSTANCE hInst = ShellExecute( NULL, "open", TextEditor, cmd,	NULL, SW_SHOWNORMAL );
	if ((DWORD_PTR)hInst < 32)
	{
		// Failed.
		file = file.SpanExcluding( "/" );
		// Try standart open.
		ShellExecute( NULL, "open", file, NULL, NULL, SW_SHOWNORMAL );
	}
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::SelectFile( const CString &fileSpec,const CString &searchFolder,CString &fullFileName )
{
	CString filter;
	filter.Format( "%s|%s||",(const char*)fileSpec,(const char*)fileSpec );

	CAutoDirectoryRestoreFileDialog dlg(TRUE, NULL,NULL, OFN_ENABLESIZING|OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_NOCHANGEDIR, filter );
	dlg.m_ofn.lpstrInitialDir = searchFolder;

	if (dlg.DoModal() == IDOK)
	{
		fullFileName = dlg.GetPathName();
		return true;
		/*
		if (!fileName.IsEmpty())
		{
			relativeFileName = Path::GetRelativePath( fileName );
			if (!relativeFileName.IsEmpty())
			{
				return true;
			}
			else
			{
				Warning( "You must select files from %s folder",(const char*)GetIEditor()->GetMasterCDFolder(); );
			}
		}
		*/
	}

//	CSelectFileDlg cDialog;
//	bool res = cDialog.SelectFileName( &fileName,&relativeFileName,fileSpec,searchFolder );
	return false;
}

bool CFileUtil::SelectFiles( const CString &fileSpec,const CString &searchFolder,std::vector<CString> &files )
{
	CString filter;
	filter.Format( "%s|%s||",(const char*)fileSpec,(const char*)fileSpec );

	char filesStr[16768];
	memset( filesStr,0,sizeof(filesStr) );

	CAutoDirectoryRestoreFileDialog dlg(TRUE, NULL,NULL, OFN_ALLOWMULTISELECT|OFN_ENABLESIZING|OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_NOCHANGEDIR, filter );
	dlg.m_ofn.lpstrInitialDir = searchFolder;
	dlg.m_ofn.lpstrFile = filesStr;
	dlg.m_ofn.nMaxFile = sizeof(filesStr);

	files.clear();
	if (dlg.DoModal())
	{
		POSITION pos = dlg.GetStartPosition();
		while (pos != NULL)
		{
			CString fileName = dlg.GetNextPathName(pos);
			if (fileName.IsEmpty())
				continue;
			files.push_back( fileName );
		}
	}

	if (!files.empty())
		return true;

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::SelectSaveFile( const CString &fileFilter,const CString &defaulExtension,const CString &startFolder,CString &fileName )
{
	CAutoDirectoryRestoreFileDialog dlg(FALSE, defaulExtension, fileName, OFN_ENABLESIZING|OFN_EXPLORER|OFN_PATHMUSTEXIST|OFN_NOCHANGEDIR|OFN_OVERWRITEPROMPT, fileFilter );
	CString initialPath = Path::MakeFullPath(startFolder);
	dlg.m_ofn.lpstrInitialDir = initialPath;

	if (dlg.DoModal() == IDOK)
	{
		fileName = dlg.GetPathName();
		if (!fileName.IsEmpty())
		{
			return true;
		}
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::SelectSingleFile( ECustomFileType fileType,CString &outputFile,const CString &filter,const CString &initialDir )
{
	CAutoRestoreMasterCDRoot adr;

	if(fileType==EFILE_TYPE_SOUND)
	{
		CSoundBrowserDialog dlg;
		dlg.Init(outputFile);
		if (dlg.DoModal() == IDOK)
		{
			if(!dlg.IsBrowse())
			{
				outputFile = dlg.GetString();
				if(strlen(outputFile)==0)
					return false;
				return true;
			}
		}
		else
			return false;
	}

	CCustomFileDialog::OpenParams op;
	op.bMultiSelection = false;
	op.filetype = fileType;
	op.filter = filter;
	op.initialDir = initialDir;
	op.initialFile = outputFile;
	CCustomFileDialog dlg( op );

	if (dlg.DoModal() == IDOK)
	{
		outputFile = dlg.GetFilePath();
		return true;
	}
	return false;
}

//! Display OpenFile dialog and allow to select multiple files.
bool CFileUtil::SelectMultipleFiles( ECustomFileType fileType,std::vector<CString> &files,const CString &filter,const CString &initialDir )
{
	CAutoRestoreMasterCDRoot adr;

	CCustomFileDialog::OpenParams op;
	op.bMultiSelection = true;
	op.filetype = fileType;
	op.filter = filter;
	op.initialDir = initialDir;
	CCustomFileDialog dlg( op );

	files.clear();
	if (dlg.DoModal() == IDOK)
	{
		for (int i = 0; i < dlg.GetSelectedCount(); i++)
		{
			files.push_back( dlg.GetSelectedFile(i) );
		}
	}
	return !files.empty();
}

//////////////////////////////////////////////////////////////////////////
// Get directory contents.
//////////////////////////////////////////////////////////////////////////
inline bool ScanDirectoryFiles( const CString &root,const CString &path,const CString &fileSpec,std::vector<CFileUtil::FileDesc> &files )
{
	bool anyFound = false;
	CString dir = Path::AddBackslash(root + path);

	CString findFilter = Path::Make(dir,fileSpec);
	ICryPak *pIPak = GetIEditor()->GetSystem()->GetIPak();
	
	// Add all directories.
	_finddata_t fd;
	intptr_t fhandle;

	fhandle = pIPak->FindFirst( findFilter,&fd );
	if (fhandle != -1)
	{
		do {
			// Skip back folders.
			if (fd.name[0] == '.')
				continue;
			if (fd.attrib & _A_SUBDIR) // skip sub directories.
				continue;

			anyFound = true;

			CFileUtil::FileDesc file;
			file.filename = path + fd.name;
			file.attrib = fd.attrib;
			file.size = fd.size;
			file.time_access = fd.time_access;
			file.time_create = fd.time_create;
			file.time_write = fd.time_write;

			files.push_back( file );
		} while (pIPak->FindNext( fhandle,&fd ) == 0);
		pIPak->FindClose(fhandle);
	}

	/*
	CFileFind finder;
	BOOL bWorking = finder.FindFile( Path::Make(dir,fileSpec) );
	while (bWorking)
	{
		bWorking = finder.FindNextFile();

		if (finder.IsDots())
			continue;

		if (!finder.IsDirectory())
		{
			anyFound = true;

			CFileUtil::FileDesc fd;
			fd.filename = dir + finder.GetFileName();
			fd.nFileSize = finder.GetLength();

			finder.GetCreationTime( &fd.ftCreationTime );
			finder.GetLastAccessTime( &fd.ftLastAccessTime );
			finder.GetLastWriteTime( &fd.ftLastWriteTime );

			fd.dwFileAttributes = 0;
			if (finder.IsArchived())
				fd.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
			if (finder.IsCompressed())
				fd.dwFileAttributes |= FILE_ATTRIBUTE_COMPRESSED;
			if (finder.IsNormal())
				fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
			if (finder.IsHidden())
				fd.dwFileAttributes = FILE_ATTRIBUTE_HIDDEN;
			if (finder.IsReadOnly())
				fd.dwFileAttributes = FILE_ATTRIBUTE_READONLY;
			if (finder.IsSystem())
				fd.dwFileAttributes = FILE_ATTRIBUTE_SYSTEM;
			if (finder.IsTemporary())
				fd.dwFileAttributes = FILE_ATTRIBUTE_TEMPORARY;

			files.push_back(fd);
		}
	}
	*/

	return anyFound;
}

//////////////////////////////////////////////////////////////////////////
// Get directory contents.
//////////////////////////////////////////////////////////////////////////
inline bool ScanDirectoryRecursive( const CString &root,const CString &path,const CString &fileSpec,std::vector<CFileUtil::FileDesc> &files, bool recursive )
{
	bool anyFound = false;
	CString dir = Path::AddBackslash(root + path);

	if (ScanDirectoryFiles( root,Path::AddBackslash(path),fileSpec,files ))
		anyFound = true;

	if (recursive)
	{
		/*
		CFileFind finder;
		BOOL bWorking = finder.FindFile( Path::Make(dir,"*.*") );
		while (bWorking)
		{
			bWorking = finder.FindNextFile();

			if (finder.IsDots())
				continue;

			if (finder.IsDirectory())
			{
				// Scan directory.
				if (ScanDirectoryRecursive( root,Path::AddBackslash(path+finder.GetFileName()),fileSpec,files,recursive ))
					anyFound = true;
			}
		}
		*/

		ICryPak *pIPak = GetIEditor()->GetSystem()->GetIPak();

		// Add all directories.
		_finddata_t fd;
		intptr_t fhandle;

		fhandle = pIPak->FindFirst( Path::Make(dir,"*.*"),&fd );
		if (fhandle != -1)
		{
			do {
				// Skip back folders.
				if (fd.name[0] == '.')
					continue;
				if (!(fd.attrib & _A_SUBDIR)) // skip not directories.
					continue;

				// Scan directory.
				if (ScanDirectoryRecursive( root,Path::AddBackslash(path + fd.name),fileSpec,files,recursive ))
					anyFound = true;

			} while (pIPak->FindNext( fhandle,&fd ) == 0);
			pIPak->FindClose(fhandle);
		}
	}

	return anyFound;
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::ScanDirectory( const CString &path,const CString &file,std::vector<FileDesc> &files, bool recursive )
{
	CString fileSpec = Path::GetFile(file);
	CString localPath = Path::GetPath(file);
	return ScanDirectoryRecursive(Path::AddBackslash(path),localPath,fileSpec,files,recursive );
}

/*
bool CFileUtil::ScanDirectory( const CString &startDirectory,const CString &searchPath,const CString &fileSpec,FileArray &files, bool recursive=true )
{
	return ScanDirectoryRecursive(startDirectory,SearchPath,file,files,recursive );
}
*/

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::OverwriteFile( const char *filename )
{
	// check if file exist.
	FILE *file = fopen( filename,"rb" );
	if (!file)
		return true;
	fclose(file);
	
	int res = (GetFileAttributes(filename)&FILE_ATTRIBUTE_READONLY);
	if (res == INVALID_FILE_ATTRIBUTES)
	{
		Warning( "Cannot Save File %s",filename );
		return false;
	}
	if (res != 0)
	{
		CCheckOutDialog dlg( filename,AfxGetMainWnd() );
		if (dlg.DoModal() == IDCANCEL)
		{
			return false;
		}
		if (dlg.GetResult() == CCheckOutDialog::CHECKOUT)
		{
			return CheckoutFile( filename );
		}
		SetFileAttributes( filename,FILE_ATTRIBUTE_NORMAL );
	}
	return true;
}

/*
static bool CheckOutFile( const char *filename )
{
	CString ssafeExe = "C:\\Program Files\\Microsoft Visual Studio\\VSS\\win32\\ss.exe";
	SetEnvironmentVariable( "ssuser","timur" );
	SetEnvironmentVariable( "ssdir","\\\\Server2\\XISLE\\ArtworkVss" );
	//CString SSafeArtwork = "\\\\Server2\\XISLE\\ArtworkVss\\win32\\ss.exe";
	//CString SSafeArtworkProject = "$/MASTERCD";

	CString cmd = ssafeExe + " " + " checkout cg.dll";

	char currDirectory[MAX_PATH];
	GetCurrentDirectory( sizeof(currDirectory),currDirectory  );
	char cmdLine[MAX_PATH];
	strcpy( cmdLine,cmd );

	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	memset( &si,0,sizeof(si) );
	si.cb = sizeof(si);
	memset( &pi,0,sizeof(pi) );
	if (CreateProcess( NULL,cmdLine,NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,currDirectory,&si,&pi ))
	{
		// Wait until child process exits.
		WaitForSingleObject( pi.hProcess, INFINITE );

		// Close process and thread handles. 
		CloseHandle( pi.hProcess );
		CloseHandle( pi.hThread );
	}
}
*/

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::CheckoutFile( const char *filename )
{
	/*
	if (gSettings.ssafeParams.user.IsEmpty())
	{
		AfxMessageBox( _T("Source Safe login user name must be configured."),MB_OK|MB_ICONEXCLAMATION );

		// Source safe not configured.
		CSrcSafeSettingsDialog dlg;
		if (dlg.DoModal() != IDOK)
		{
			AfxMessageBox( _T("Checkout canceled"),MB_OK|MB_ICONEXCLAMATION );
			return false;
		}
	}
	SetEnvironmentVariable( "ssuser",gSettings.ssafeParams.user );
	SetEnvironmentVariable( "ssdir",gSettings.ssafeParams.databasePath );

	CString relFile = Path::GetRelativePath(filename);
	if (relFile.IsEmpty())
		relFile = filename;

	CString cmd = gSettings.ssafeParams.exeFile + " checkout " + relFile;

	char currDirectory[MAX_PATH];
	GetCurrentDirectory( sizeof(currDirectory),currDirectory  );
	char cmdLine[MAX_PATH];
	strcpy( cmdLine,cmd );

	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	memset( &si,0,sizeof(si) );
	si.cb = sizeof(si);
	memset( &pi,0,sizeof(pi) );
	if (CreateProcess( NULL,cmdLine,NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,currDirectory,&si,&pi ))
	{
		// Wait until child process exits.
		WaitForSingleObject( pi.hProcess, INFINITE );

		// Close process and thread handles. 
		CloseHandle( pi.hProcess );
		CloseHandle( pi.hThread );
		return true;
	}
	*/

	uint32 attr = GetIEditor()->GetSourceControl()->GetFileAttributes( filename );
	if (attr & SCC_FILE_ATTRIBUTE_MANAGED)
	{
		if (attr & SCC_FILE_ATTRIBUTE_CHECKEDOUT)
		{
			return true;
		}
		else
		{
			return GetIEditor()->GetSourceControl()->CheckOut( filename );
		}
	}
	else
	{
		// Files from pak should be easily openable.
		if (attr & SCC_FILE_ATTRIBUTE_INPAK)
			return true;

		if (attr & SCC_FILE_ATTRIBUTE_READONLY)
		{
			CString str;
			str.Format( "File %s is Read-Only, Overrite?", (const char*)filename );
			if (AfxMessageBox( str,MB_YESNO ) == IDYES)
			{
				SetFileAttributes( filename,FILE_ATTRIBUTE_NORMAL );
				return false;
			}
		}
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CFileUtil::CheckinFile( const char *filename )
{
	/*
	SetEnvironmentVariable( "ssuser",gSettings.ssafeParams.user );
	SetEnvironmentVariable( "ssdir",gSettings.ssafeParams.databasePath );

	CString relFile = Path::GetRelativePath(filename);
	if (relFile.IsEmpty())
		relFile = filename;

	CString cmd = gSettings.ssafeParams.exeFile + " checkout " + relFile;

	char currDirectory[MAX_PATH];
	GetCurrentDirectory( sizeof(currDirectory),currDirectory  );
	char cmdLine[MAX_PATH];
	strcpy( cmdLine,cmd );

	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	memset( &si,0,sizeof(si) );
	si.cb = sizeof(si);
	memset( &pi,0,sizeof(pi) );
	if (CreateProcess( NULL,cmdLine,NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,currDirectory,&si,&pi ))
	{
		// Wait until child process exits.
		WaitForSingleObject( pi.hProcess, INFINITE );

		// Close process and thread handles. 
		CloseHandle( pi.hProcess );
		CloseHandle( pi.hThread );
		return true;
	}
	*/

	uint32 attr = GetIEditor()->GetSourceControl()->GetFileAttributes( filename );
	if (attr & SCC_FILE_ATTRIBUTE_MANAGED)
	{
		if (attr & SCC_FILE_ATTRIBUTE_CHECKEDOUT)
		{
			return GetIEditor()->GetSourceControl()->CheckIn( filename );
		}
	}

	return true;
}

// Create new directory, check if directory already exist.
static bool CheckAndCreateDirectory( const char *path )
{
	WIN32_FIND_DATA FindFileData;

	HANDLE hFind = FindFirstFile( path,&FindFileData );
	if (hFind == INVALID_HANDLE_VALUE) {
		return ::CreateDirectory( path,NULL ) == TRUE;
	} else {
		DWORD attr = FindFileData.dwFileAttributes;
		FindClose(hFind);
		if (attr & FILE_ATTRIBUTE_DIRECTORY) {
			return true;
		}
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
void CFileUtil::CreateDirectory( const char *directory )
{
	CString path = directory;
	CString dir;
	bool res = CheckAndCreateDirectory( path );
	if (!res)
	{
		int iStart = 0;
		CString token = TokenizeString(path,"\\/",iStart );
		dir = token;
		while (token != "")
		{
			CheckAndCreateDirectory( dir );
			token = TokenizeString(path,"\\/",iStart );
			dir += CString("\\") + token;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CFileUtil::BackupFile( const char *filename )
{
	// Make a backup of previous file.
	bool makeBackup = true;

	CString bakFilename = Path::ReplaceExtension( filename,"bak" );

	{
		// Check if backup needed.
		CFile bak;
		if (bak.Open( filename,CFile::modeRead ))
		{
			if (bak.GetLength() <= 0)
				makeBackup = false;
		}
		else
			makeBackup = false;
	}
	// Backup the backup..
	if (makeBackup)
	{
		MoveFileEx( bakFilename,Path::ReplaceExtension( bakFilename,"bak2" ),MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH );
		SetFileAttributes( filename,FILE_ATTRIBUTE_NORMAL );
		MoveFileEx( filename,bakFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH );
	}
}
