//========= Copyright Valve Corporation ============//
#include <vrcore/strtools_public.h>
#include <vrcore/pathtools_public.h>

#if defined( _WIN32)
#include <windows.h>
#include <direct.h>
#include <shobjidl.h>
#include <knownfolders.h>
#include <shlobj.h>
#include <share.h>

#undef GetEnvironmentVariable
#else
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <alloca.h>
#endif

#if defined OSX
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include <mach-o/dyld.h>
#define _S_IFDIR S_IFDIR     // really from tier0/platform.h which we dont have yet
#endif

#include <sys/stat.h>

#include <algorithm>

/** Returns the path (including filename) to the current executable */
std::string Path_GetExecutablePath()
{
#if defined( _WIN32 )
	wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH];
	char *pchPath = new char[MAX_UNICODE_PATH_IN_UTF8];
	::GetModuleFileNameW( NULL, pwchPath, MAX_UNICODE_PATH );
	WideCharToMultiByte( CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL );
	delete[] pwchPath;

	std::string sPath = pchPath;
	delete[] pchPath;
	return sPath;
#elif defined( OSX )
	char rchPath[1024];
	uint32_t nBuff = sizeof( rchPath );
	bool bSuccess = _NSGetExecutablePath(rchPath, &nBuff) == 0;
	rchPath[nBuff-1] = '\0';
	if( bSuccess )
		return rchPath;
	else
		return "";
#elif defined LINUX
	char rchPath[1024];
	size_t nBuff = sizeof( rchPath );
	ssize_t nRead = readlink("/proc/self/exe", rchPath, nBuff-1 );
	if ( nRead != -1 )
	{
		rchPath[ nRead ] = 0;
		return rchPath;
	}
	else
	{
		return "";
	}
#else
	AssertMsg( false, "Implement Plat_GetExecutablePath" );
	return "";
#endif

}

/** Returns the filename (excluding directory and extension) of the current executable */
std::string Path_GetExecutableName()
{
	std::string sExeFilename = Path_StripExtension( Path_StripDirectory( Path_GetExecutablePath() ) );
#if defined( ANDROID )
	// We determine the App ID by looking at `/proc/self/cmdline`, as Zygote changes our process name to match.
	FILE *pCmdlineFile = fopen( "/proc/self/cmdline", "r" );
	if ( pCmdlineFile != NULL )
	{
		// Google Play says the maximum allowable length is 150 characters
		char rgchAndroidAppID[ 151 ] = { 0 };
		fgets( rgchAndroidAppID, sizeof( rgchAndroidAppID ), pCmdlineFile );
		fclose( pCmdlineFile );
		if ( rgchAndroidAppID[ 0 ] != '\0' )
		{
			sExeFilename = rgchAndroidAppID;
		}
	}
#endif
	return sExeFilename;
}

/** Returns the path of the current working directory */
std::string Path_GetWorkingDirectory()
{
	std::string sPath;
#if defined( _WIN32 )
	wchar_t buf[MAX_UNICODE_PATH];
	sPath = UTF16to8( _wgetcwd( buf, MAX_UNICODE_PATH ) );
#else
	char buf[ 1024 ];
	sPath = getcwd( buf, sizeof( buf ) );
#endif
	return sPath;
}

/** Sets the path of the current working directory. Returns true if this was successful. */
bool Path_SetWorkingDirectory( const std::string & sPath )
{
	bool bSuccess;
#if defined( _WIN32 )
	std::wstring wsPath = UTF8to16( sPath.c_str() );
	bSuccess = 0 == _wchdir( wsPath.c_str() );
#else
	bSuccess = 0 == chdir( sPath.c_str() );
#endif
	return bSuccess;
}

/** Gets the path to a temporary directory. */
std::string Path_GetTemporaryDirectory()
{
#if defined( _WIN32 )
	wchar_t buf[MAX_UNICODE_PATH];
	if ( GetTempPathW( MAX_UNICODE_PATH, buf ) == 0 )
		return Path_GetWorkingDirectory();
	return UTF16to8( buf );
#else
	const char *pchTmpDir = getenv( "TMPDIR" );
	if ( pchTmpDir == NULL )
	{
		return "/tmp";
	}
	return pchTmpDir;
#endif
}

/** Returns the specified path without its filename */
std::string Path_StripFilename( const std::string & sPath, char slash )
{
	if( slash == 0 )
		slash = Path_GetSlash();

	std::string::size_type n = sPath.find_last_of( slash );
	if( n == std::string::npos )
		return sPath;
	else
		return std::string( sPath.begin(), sPath.begin() + n );
}

/** returns just the filename from the provided full or relative path. */
std::string Path_StripDirectory( const std::string & sPath, char slash )
{
	if( slash == 0 )
		slash = Path_GetSlash();

	std::string::size_type n = sPath.find_last_of( slash );
	if( n == std::string::npos )
		return sPath;
	else
		return std::string( sPath.begin() + n + 1, sPath.end() );
}

/** returns just the filename with no extension of the provided filename. 
* If there is a path the path is left intact. */
std::string Path_StripExtension( const std::string & sPath )
{
	for( std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++ )
	{
		if( *i == '.' )
		{
			return std::string( sPath.begin(), i.base() - 1 );
		}

		// if we find a slash there is no extension
		if( *i == '\\' || *i == '/' )
			break;
	}

	// we didn't find an extension
	return sPath;
}

/** returns just extension of the provided filename (if any). */
std::string Path_GetExtension( const std::string & sPath )
{
	for ( std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++ )
	{
		if ( *i == '.' )
		{
			return std::string( i.base(), sPath.end() );
		}

		// if we find a slash there is no extension
		if ( *i == '\\' || *i == '/' )
			break;
	}

	// we didn't find an extension
	return "";
}

bool Path_IsAbsolute( const std::string & sPath )
{
	if( sPath.empty() )
		return false;

#if defined( WIN32 )
	if ( sPath.size() < 3 ) // must be c:\x or \\x at least
		return false;

	if ( sPath[1] == ':' ) // drive letter plus slash, but must test both slash cases
	{
		if ( sPath[2] == '\\' || sPath[2] == '/' )
			return true;
	}
	else if ( sPath[0] == '\\' && sPath[1] == '\\' ) // UNC path
		return true;
#else
	if( sPath[0] == '\\' || sPath[0] == '/' ) // any leading slash
		return true;
#endif

	return false;
}


/** Makes an absolute path from a relative path and a base path */
std::string Path_MakeAbsolute( const std::string & sRelativePath, const std::string & sBasePath )
{
	if( Path_IsAbsolute( sRelativePath ) )
		return Path_Compact( sRelativePath );
	else
	{
		if( !Path_IsAbsolute( sBasePath ) )
			return "";

		std::string sCompacted = Path_Compact( Path_Join( sBasePath, sRelativePath ) );
		if( Path_IsAbsolute( sCompacted ) )
			return sCompacted;
		else
			return "";
	}
}


/** Fixes the directory separators for the current platform */
std::string Path_FixSlashes( const std::string & sPath, char slash )
{
	if( slash == 0 )
		slash = Path_GetSlash();

	std::string sFixed = sPath;
	for( std::string::iterator i = sFixed.begin(); i != sFixed.end(); i++ )
	{
		if( *i == '/' || *i == '\\' )
			*i = slash;
	}

	return sFixed;
}


char Path_GetSlash()
{
#if defined(_WIN32)
	return '\\';
#else
	return '/';
#endif
}

/** Jams two paths together with the right kind of slash */
std::string Path_Join( const std::string & first, const std::string & second, char slash )
{
	if( slash == 0 )
		slash = Path_GetSlash();

	// only insert a slash if we don't already have one
	std::string::size_type nLen = first.length();
	if( !nLen )
		return second;
#if defined(_WIN32)
	if( first.back() == '\\' || first.back() == '/' )
	    nLen--;
#else
	char last_char = first[first.length()-1];
	if (last_char == '\\' || last_char == '/')
	    nLen--;
#endif

	return first.substr( 0, nLen ) + std::string( 1, slash ) + second;
}


std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, char slash )
{
	return Path_Join( Path_Join( first, second, slash ), third, slash );
}

std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, const std::string &fourth, char slash )
{
	return Path_Join( Path_Join( Path_Join( first, second, slash ), third, slash ), fourth, slash );
}

std::string Path_Join( 
	const std::string & first, 
	const std::string & second, 
	const std::string & third, 
	const std::string & fourth, 
	const std::string & fifth, 
	char slash )
{
	return Path_Join( Path_Join( Path_Join( Path_Join( first, second, slash ), third, slash ), fourth, slash ), fifth, slash );
}


std::string Path_RemoveTrailingSlash( const std::string & sRawPath, char slash )
{
	if ( slash == 0 )
		slash = Path_GetSlash();

	std::string sPath = sRawPath;
	std::string::size_type nCurrent = sRawPath.length();
	if ( nCurrent == 0 )
		return sPath;

	int nLastFound = -1;
	nCurrent--;
	while( nCurrent != 0 )
	{
		if ( sRawPath[ nCurrent ] == slash )
		{
			nLastFound = (int)nCurrent;
			nCurrent--;
		}
		else
		{
			break;
		}
	}
		
	if ( nLastFound >= 0 )
	{
		sPath.erase( nLastFound, std::string::npos );
	}
	
	return sPath;
}


/** Removes redundant <dir>/.. elements in the path. Returns an empty path if the 
* specified path has a broken number of directories for its number of ..s */
std::string Path_Compact( const std::string & sRawPath, char slash )
{
	if( slash == 0 )
		slash = Path_GetSlash();

	std::string sPath = Path_FixSlashes( sRawPath, slash );
	std::string sSlashString( 1, slash );

	// strip out all /./
	for( std::string::size_type i = 0; (i + 3) < sPath.length();  )
	{
		if( sPath[ i ] == slash && sPath[ i+1 ] == '.' && sPath[ i+2 ] == slash )
		{
			sPath.replace( i, 3, sSlashString );
		}
		else
		{
			++i;
		}
	}


	// get rid of trailing /. but leave the path separator
	if( sPath.length() > 2 )
	{
		std::string::size_type len = sPath.length();
		if( sPath[ len-1 ] == '.'  && sPath[ len-2 ] == slash )
		{
			sPath.pop_back();
			//Not sure why the following line of code was used for a while.  It causes problems with strlen.
			//sPath[len-1] = 0;  // for now, at least 
		}
	}

	// get rid of leading ./ 
	if( sPath.length() > 2 )
	{
		if( sPath[ 0 ] == '.'  && sPath[ 1 ] == slash )
		{
			sPath.replace( 0, 2, "" );
		}
	}

	// each time we encounter .. back up until we've found the previous directory name
	// then get rid of both
	std::string::size_type i = 0;
	while( i < sPath.length() )
	{
		if( i > 0 && sPath.length() - i >= 2 
			&& sPath[i] == '.'
			&& sPath[i+1] == '.'
			&& ( i + 2 == sPath.length() || sPath[ i+2 ] == slash )
			&& sPath[ i-1 ] == slash )
		{
			// check if we've hit the start of the string and have a bogus path
			if( i == 1 )
				return "";
			
			// find the separator before i-1
			std::string::size_type iDirStart = i-2;
			while( iDirStart > 0 && sPath[ iDirStart - 1 ] != slash )
				--iDirStart;

			// remove everything from iDirStart to i+2
			sPath.replace( iDirStart, (i - iDirStart) + 3, "" );

			// start over
			i = 0;
		}
		else
		{
			++i;
		}
	}

	return sPath;
}


/** Returns true if these two paths are the same without respect for internal . or ..
* sequences, slash type, or case (on case-insensitive platforms). */
bool Path_IsSamePath( const std::string & sPath1, const std::string & sPath2 )
{
	std::string sCompact1 = Path_Compact( sPath1 );
	std::string sCompact2 = Path_Compact( sPath2 );
#if defined(WIN32)
	return !stricmp( sCompact1.c_str(), sCompact2.c_str() );
#else
	return !strcmp( sCompact1.c_str(), sCompact2.c_str() );
#endif
}


/** Returns the path to the current DLL or exe */
std::string Path_GetThisModulePath()
{
	// gets the path of vrclient.dll itself
#ifdef WIN32
	HMODULE hmodule = NULL;

	::GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCTSTR>(Path_GetThisModulePath), &hmodule );

	wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH];
	char *pchPath = new char[ MAX_UNICODE_PATH_IN_UTF8 ];
	::GetModuleFileNameW( hmodule, pwchPath, MAX_UNICODE_PATH );
	WideCharToMultiByte( CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL );
	delete[] pwchPath;

	std::string sPath = pchPath;
	delete [] pchPath;
	return sPath;

#elif defined( OSX ) || defined( LINUX )
	// get the addr of a function in vrclient.so and then ask the dlopen system about it
	Dl_info info;
	dladdr( (void *)Path_GetThisModulePath, &info );
	return info.dli_fname;
#endif

}


/** returns true if the specified path exists and is a directory */
bool Path_IsDirectory( const std::string & sPath )
{
	std::string sFixedPath = Path_FixSlashes( sPath );
	if( sFixedPath.empty() )
		return false;
	char cLast = sFixedPath[ sFixedPath.length() - 1 ];
	if( cLast == '/' || cLast == '\\' )
		sFixedPath.erase( sFixedPath.end() - 1, sFixedPath.end() );

	// see if the specified path actually exists.

#if defined(POSIX)
	struct	stat	buf;
	if ( stat( sFixedPath.c_str(), &buf ) == -1 )
	{
		return false;
	}

#if defined( LINUX ) || defined( OSX )
	return S_ISDIR( buf.st_mode );
#else
	return (buf.st_mode & _S_IFDIR) != 0;
#endif

#else
	struct	_stat	buf;
	std::wstring wsFixedPath = UTF8to16( sFixedPath.c_str() );
	if ( _wstat( wsFixedPath.c_str(), &buf ) == -1 )
	{
		return false;
	}

	return (buf.st_mode & _S_IFDIR) != 0;
#endif
}

/** returns true if the specified path represents an app bundle */
bool Path_IsAppBundle( const std::string & sPath )
{
#if defined(OSX)
	@autoreleasepool {
		NSBundle *bundle = [ NSBundle bundleWithPath: [ NSString stringWithUTF8String:sPath.c_str() ] ];
		bool bisAppBundle = ( nullptr != bundle );
		return bisAppBundle;
	}
#else
	return false;
#endif
}

//-----------------------------------------------------------------------------
// Purpose: returns true if the the path exists
//-----------------------------------------------------------------------------
bool Path_Exists( const std::string & sPath )
{
	std::string sFixedPath = Path_FixSlashes( sPath );
	if( sFixedPath.empty() )
		return false;

#if defined( WIN32 )
	struct	_stat	buf;
	std::wstring wsFixedPath = UTF8to16( sFixedPath.c_str() );
	if ( _wstat( wsFixedPath.c_str(), &buf ) == -1 )
	{
		return false;
	}
#else
	struct stat buf;
	if ( stat ( sFixedPath.c_str(), &buf ) == -1)
	{
		return false;
	}
#endif

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: helper to find a directory upstream from a given path
//-----------------------------------------------------------------------------
std::string Path_FindParentDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName )
{
	std::string strFoundPath = "";
	std::string strCurrentPath = Path_FixSlashes( strStartDirectory );
	if ( strCurrentPath.length() == 0 )
		return "";

	bool bExists = Path_Exists( strCurrentPath );
	std::string strCurrentDirectoryName = Path_StripDirectory( strCurrentPath );
	if ( bExists && stricmp( strCurrentDirectoryName.c_str(), strDirectoryName.c_str() ) == 0 )
		return strCurrentPath;

	while( bExists && strCurrentPath.length() != 0 )
	{
		strCurrentPath = Path_StripFilename( strCurrentPath );
		strCurrentDirectoryName = Path_StripDirectory( strCurrentPath );
		bExists = Path_Exists( strCurrentPath );
		if ( bExists && stricmp( strCurrentDirectoryName.c_str(), strDirectoryName.c_str() ) == 0 )
			return strCurrentPath;
	}

	return "";
}


//-----------------------------------------------------------------------------
// Purpose: helper to find a subdirectory upstream from a given path
//-----------------------------------------------------------------------------
std::string Path_FindParentSubDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName )
{
	std::string strFoundPath = "";
	std::string strCurrentPath = Path_FixSlashes( strStartDirectory );
	if ( strCurrentPath.length() == 0 )
		return "";

	bool bExists = Path_Exists( strCurrentPath );
	while( bExists && strCurrentPath.length() != 0 )
	{
		strCurrentPath = Path_StripFilename( strCurrentPath );
		bExists = Path_Exists( strCurrentPath );

		if( Path_Exists( Path_Join( strCurrentPath, strDirectoryName ) ) )
		{
			strFoundPath = Path_Join( strCurrentPath, strDirectoryName );
			break;
		}
	}
	return strFoundPath;
}


//-----------------------------------------------------------------------------
// Purpose: reading and writing files in the vortex directory
//-----------------------------------------------------------------------------
std::vector<uint8_t> Path_ReadBinaryFile( const std::string & strFilename )
{
	FILE *f;
#if defined( POSIX )
	f = fopen( strFilename.c_str(), "rb" );
#else
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );
	// the open operation needs to be sharable, therefore use of _wfsopen instead of _wfopen_s
	f = _wfsopen( wstrFilename.c_str(), L"rb", _SH_DENYNO );
#endif

	std::vector<uint8_t> vecFileContents;

	if ( f != NULL )
	{
		fseek( f, 0, SEEK_END );
		int size = ftell( f );
		if ( size > 0 )
		{
			fseek( f, 0, SEEK_SET );

			vecFileContents.resize( size );
			if ( fread( &vecFileContents[ 0 ], size, 1, f ) == 1 )
			{
			}
			else
			{
				vecFileContents.clear();
			}
		}

		fclose( f );
	}

	return vecFileContents ;
}


unsigned char * Path_ReadBinaryFile( const std::string &strFilename, int *pSize )
{
	FILE *f;
#if defined( POSIX )
	f = fopen( strFilename.c_str(), "rb" );
#else
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );
	// the open operation needs to be sharable, therefore use of _wfsopen instead of _wfopen_s
	f = _wfsopen( wstrFilename.c_str(), L"rb", _SH_DENYNO );
#endif
	
	unsigned char* buf = NULL;

	if ( f != NULL )
	{
		fseek(f, 0, SEEK_END);
		int size = ftell(f);
		if ( size > 0 )
		{
			fseek(f, 0, SEEK_SET);
			
			buf = new unsigned char[size];
			if (buf && fread(buf, size, 1, f) == 1)
			{
				if (pSize)
					*pSize = size;
			}
			else
			{
				delete[] buf;
				buf = 0;
			}
		}

		fclose(f);
	}

	return buf;
}

uint32_t  Path_ReadBinaryFile( const std::string &strFilename, unsigned char *pBuffer, uint32_t unSize )
{
	FILE *f;
#if defined( POSIX )
	f = fopen( strFilename.c_str(), "rb" );
#else
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );
	errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"rb" );
	if ( err != 0 )
	{
		f = NULL;
	}
#endif

	uint32_t unSizeToReturn = 0;

	if ( f != NULL )
	{
		fseek( f, 0, SEEK_END );
		int nSize = ftell( f );
		if ( nSize > 0 )
		{
			uint32_t size = (uint32_t) nSize;
			fseek( f, 0, SEEK_SET );

			if ( size > unSize || !pBuffer )
			{
				unSizeToReturn = size;
			}
			else
			{
				if ( fread( pBuffer, size, 1, f ) == 1 )
				{
					unSizeToReturn = size;
				}
			}
		}

		fclose( f );
	}

	return unSizeToReturn;
}

bool Path_WriteBinaryFile(const std::string &strFilename, unsigned char *pData, unsigned nSize)
{
	FILE *f;
#if defined( POSIX )
	f = fopen(strFilename.c_str(), "wb");
#else
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );
	errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"wb" );
	if (err != 0)
	{
		f = NULL;
	}
#endif

	size_t written = 0;
	if (f != NULL) {
		written = fwrite(pData, sizeof(unsigned char), nSize, f);
		fclose(f);
	}

	return written == nSize ? true : false;
}

std::string Path_ReadTextFile( const std::string &strFilename )
{
	// doing it this way seems backwards, but I don't
	// see an easy way to do this with C/C++ style IO
	// that isn't worse...
	int size;
	unsigned char* buf = Path_ReadBinaryFile( strFilename, &size );
	if (!buf)
		return "";

	int i = 1; /* start working at byte 1 (in-place) */
	size_t outsize = 1;

	// remove UTF8 BOM
	if ( size >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF )
	{
		i = 3;
		outsize = 0;
	}

	// convert CRLF -> LF
	for ( ; i < size; i++ )
	{
		if (buf[i] == '\n' && buf[i-1] == '\r') // CRLF
			buf[outsize-1] = '\n'; // ->LF
		else
			buf[outsize++] = buf[i]; // just copy
	}

	std::string ret((char *)buf, outsize);
	delete[] buf;
	return ret;
}


bool Path_MakeWritable( const std::string &strFilename )
{
#if defined ( _WIN32 )
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );

	DWORD dwAttrs = GetFileAttributesW( wstrFilename.c_str() );
	if ( dwAttrs != INVALID_FILE_ATTRIBUTES && ( dwAttrs & FILE_ATTRIBUTE_READONLY ) )
	{
		return SetFileAttributesW( wstrFilename.c_str(), dwAttrs & ~FILE_ATTRIBUTE_READONLY );
	}
#else
	struct stat sb;

	if ( stat( strFilename.c_str(), &sb ) == 0 && !( sb.st_mode & S_IWUSR ) )
	{
		return ( chmod( strFilename.c_str(), sb.st_mode | S_IWUSR ) == 0 );
	}
#endif

	return true;
}

bool Path_WriteStringToTextFile( const std::string &strFilename, const char *pchData )
{
	FILE *f;
#if defined( POSIX )
	f = fopen( strFilename.c_str(), "w" );
#else
	std::wstring wstrFilename = UTF8to16( strFilename.c_str() );
	errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"w" );
	if ( err != 0 )
	{
		f = NULL;
	}
#endif
	
	bool ok = false;

	if ( f != NULL )
	{
		ok = fputs( pchData, f) >= 0;
		fclose(f);
	}

	return ok;
}

bool Path_WriteStringToTextFileAtomic( const std::string &strFilename, const char *pchData )
{
	std::string strTmpFilename = strFilename + ".tmp";

	if ( !Path_WriteStringToTextFile( strTmpFilename, pchData ) )
		return false;

	// Platform specific atomic file replacement
#if defined( _WIN32 )
	std::wstring wsFilename = UTF8to16( strFilename.c_str() );
	std::wstring wsTmpFilename = UTF8to16( strTmpFilename.c_str() );
	if ( !::ReplaceFileW( wsFilename.c_str(), wsTmpFilename.c_str(), nullptr, 0, 0, 0 ) )
	{
		// if we couldn't ReplaceFile, try a non-atomic write as a fallback
		if ( !Path_WriteStringToTextFile( strFilename, pchData ) )
			return false;
	}
#elif defined( POSIX )
	if ( rename( strTmpFilename.c_str(), strFilename.c_str() ) == -1 )
		return false;
#else
#error Do not know how to write atomic file
#endif

	return true;
}


#if defined(WIN32)
#define FILE_URL_PREFIX "file:///"
#else
#define FILE_URL_PREFIX "file://"
#endif

// ----------------------------------------------------------------------------------------------------------------------------
// Purpose: Turns a path to a file on disk into a URL (or just returns the value if it's already a URL)
// ----------------------------------------------------------------------------------------------------------------------------
std::string Path_FilePathToUrl( const std::string & sRelativePath, const std::string & sBasePath )
{
	if ( StringHasPrefix( sRelativePath, "http://" )
		|| StringHasPrefix( sRelativePath, "https://" )
		|| StringHasPrefix( sRelativePath, "vr-input-workshop://" )
		|| StringHasPrefix( sRelativePath, "file://" )
	   )
	{
		return sRelativePath;
	}
	else
	{
		std::string sAbsolute = Path_MakeAbsolute( sRelativePath, sBasePath );
		if ( sAbsolute.empty() )
			return sAbsolute;
		sAbsolute = Path_FixSlashes( sAbsolute, '/' );

		size_t unBufferSize = sAbsolute.length() * 3;
		char *pchBuffer = (char *)alloca( unBufferSize );
		V_URLEncodeFullPath( pchBuffer, (int)unBufferSize, sAbsolute.c_str(), (int)sAbsolute.length() );

		return std::string( FILE_URL_PREFIX ) + pchBuffer;
	}
}

// -----------------------------------------------------------------------------------------------------
// Purpose: Strips off file:// off a URL and returns the path. For other kinds of URLs an empty string is returned
// -----------------------------------------------------------------------------------------------------
std::string Path_UrlToFilePath( const std::string & sFileUrl )
{
	if ( !strnicmp( sFileUrl.c_str(), FILE_URL_PREFIX, strlen( FILE_URL_PREFIX ) ) )
	{
		char *pchBuffer = (char *)alloca( sFileUrl.length() );
		V_URLDecodeNoPlusForSpace( pchBuffer, (int)sFileUrl.length(), 
			sFileUrl.c_str() + strlen( FILE_URL_PREFIX ), (int)( sFileUrl.length() - strlen( FILE_URL_PREFIX ) ) );

		return Path_FixSlashes( pchBuffer );
	}
	else
	{
		return "";
	}
}


// -----------------------------------------------------------------------------------------------------
// Purpose: Returns the root of the directory the system wants us to store user documents in
// -----------------------------------------------------------------------------------------------------
std::string GetUserDocumentsPath()
{
#if defined( WIN32 )
	WCHAR rwchPath[MAX_PATH];

	if ( !SUCCEEDED( SHGetFolderPathW( NULL, CSIDL_MYDOCUMENTS | CSIDL_FLAG_CREATE, NULL, 0, rwchPath ) ) )
	{
		return "";
	}

	// Convert the path to UTF-8 and store in the output
	std::string sUserPath = UTF16to8( rwchPath );

	return sUserPath;
#elif defined( OSX )
	@autoreleasepool {
		NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES );
		if ( [paths count] == 0 )
		{
			return "";
		}
		
		return [[paths objectAtIndex:0] UTF8String];
	}
#elif defined( LINUX )
	// @todo: not solved/changed as part of OSX - still not real - just removed old class based steam cut and paste
	const char *pchHome = getenv( "HOME" );
	if ( pchHome == NULL )
	{
		return "";
	}
	return pchHome;
#endif
}


// -----------------------------------------------------------------------------------------------------
// Purpose: deletes / unlinks a single file
// -----------------------------------------------------------------------------------------------------
bool Path_UnlinkFile( const std::string &strFilename )
{
#if defined( WIN32 )
	std::wstring wsFilename = UTF8to16( strFilename.c_str() );
	return ( 0 != DeleteFileW( wsFilename.c_str() ) );
#else
	return ( 0 == unlink( strFilename.c_str() ) );
#endif
}


// -----------------------------------------------------------------------------------------------------
// Limits the set of characters that are allowed in filenames
// -----------------------------------------------------------------------------------------------------
std::string Path_SanitizeFilename( const std::string& sFilename )
{
	std::string sFixed = sFilename;
	std::string::iterator iLastDot = sFixed.end();
	for ( std::string::iterator i = sFixed.begin(); i != sFixed.end(); i++ )
	{
		if ( *i == '.' )
		{
			iLastDot = i;
		}

		// path-related characters are forbidden (except the last period)
		switch ( *i )
		{
			case '\0':
			case '.':
			case '\\':
			case '/':
			case ':':
			case '|':
			case '?':
			case '>':
			case '<':
			case '&':
			case '%':
			case '@':
			case '$':
			case '*':
			case '\"':
				*i = '_';
				break;

			default:
				if ( *i < 32 )
				{
					*i = '_';
				}
				break;
		}
	}

	if ( iLastDot != sFixed.end() && iLastDot != sFixed.begin() 
		&& iLastDot+1 != sFixed.end() )
	{
		*iLastDot = '.';
	}

	return sFixed;
}


//-----------------------------------------------------------------------------
// Purpose: deletes a directory and any contents, including subdirectories.
//-----------------------------------------------------------------------------
bool Path_DeleteDirectory( const std::string &sDirectoryPath, bool bDeleteSubdirectories )
{
#if defined( _WIN32 )
	std::wstring wsDirectoryPath = UTF8to16( sDirectoryPath.c_str() );
	std::wstring wsDirectoryPattern = wsDirectoryPath + L"\\*.*";
	WIN32_FIND_DATAW findData;
	HANDLE hFindFile = FindFirstFileW( wsDirectoryPattern.c_str(), &findData );
	if ( hFindFile == INVALID_HANDLE_VALUE )
		return false;

	do
	{
		if ( findData.cFileName[ 0 ] == '.' )
			continue;

		std::wstring wsFilePath = wsDirectoryPath + L"\\" + findData.cFileName;

		if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
		{
			if ( !bDeleteSubdirectories )
			{
				FindClose( hFindFile );
				return false;
			}
			else if ( !Path_DeleteDirectory( UTF16to8( wsFilePath ), bDeleteSubdirectories ) )
			{
				FindClose( hFindFile );
				return false;
			}
		}

		if ( !SetFileAttributesW( wsFilePath.c_str(), FILE_ATTRIBUTE_NORMAL ) )
		{
			FindClose( hFindFile );
			return false;
		}

		if ( !DeleteFileW( wsFilePath.c_str() ) )
		{
			FindClose( hFindFile );
			return false;
		}
	} while ( FindNextFileW( hFindFile, &findData ) );

	if ( !FindClose( hFindFile ) )
		return false;

	if ( !SetFileAttributesW( wsDirectoryPath.c_str(), FILE_ATTRIBUTE_NORMAL ) )
		return false;

	if ( !RemoveDirectoryW( wsDirectoryPath.c_str() ) )
		return false;

	return true;
#else
	return false;
#endif
}

