//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "core/strings/stringFunctions.h"
#include "util/tempAlloc.h"
#include "console/console.h"
#include "console/engineAPI.h"
#include "core/stringTable.h"

//-----------------------------------------------------------------------------

StringTableEntry Platform::getTemporaryDirectory()
{
   StringTableEntry path = osGetTemporaryDirectory();

   if(! Platform::isDirectory(path))
      path = Platform::getCurrentDirectory();

   return path;
}

DefineConsoleFunction( getTemporaryDirectory, const char *, (), ,
				"@brief Returns the OS temporary directory, \"C:/Users/Mich/AppData/Local/Temp\" for example\n\n"
				"@note This can be useful to adhering to OS standards and practices, "
				"but not really used in Torque 3D right now.\n"
				"@note Be very careful when getting into OS level File I/O."
				"@return String containing path to OS temp directory\n"
				"@note This is legacy function brought over from TGB, and does not appear "
				"to have much use. Possibly deprecate?\n"
				"@ingroup FileSystem\n"
				"@internal")
{
   return Platform::getTemporaryDirectory();
}

StringTableEntry Platform::getTemporaryFileName()
{
   char buf[512];
   StringTableEntry path = Platform::getTemporaryDirectory();

   dSprintf(buf, sizeof(buf), "%s/tgb.%08x.%02x.tmp", path, Platform::getRealMilliseconds(), U32(Platform::getRandom() * 255));

   // [tom, 9/7/2006] This shouldn't be needed, but just in case
   if(Platform::isFile(buf))
      return Platform::getTemporaryFileName();

   return StringTable->insert(buf);
}

DefineConsoleFunction( getTemporaryFileName, const char *, (), ,
				"@brief Creates a name and extension for a potential temporary file\n\n"
				"This does not create the actual file. It simply creates a random name "
				"for a file that does not exist.\n\n"
				"@note This is legacy function brought over from TGB, and does not appear "
				"to have much use. Possibly deprecate?\n"
				"@ingroup FileSystem\n"
				"@internal")
{
   return Platform::getTemporaryFileName();
}

//-----------------------------------------------------------------------------
static char filePathBuffer[1024];
static bool deleteDirectoryRecusrive(const char* pPath)
{
   // Sanity!
   AssertFatal(pPath != NULL, "Cannot delete directory that is NULL.");

   // Find directories.
   Vector<StringTableEntry> directories;
   if (!Platform::dumpDirectories(pPath, directories, 0))
   {
      // Warn.
      Con::warnf("Could not retrieve sub-directories of '%s'.", pPath);
      return false;
   }

   // Iterate directories.
   for (Vector<StringTableEntry>::iterator basePathItr = directories.begin(); basePathItr != directories.end(); ++basePathItr)
   {
      // Fetch base path.
      StringTableEntry basePath = *basePathItr;

      // Skip if the base path.
      if (basePathItr == directories.begin() && dStrcmp(pPath, basePath) == 0)
         continue;

      // Delete any directories recursively.
      if (!deleteDirectoryRecusrive(basePath))
         return false;
   }

   // Find files.
   Vector<Platform::FileInfo> files;
   if (!Platform::dumpPath(pPath, files, 0))
   {
      // Warn.
      Con::warnf("Could not retrieve files for directory '%s'.", pPath);
      return false;
   }

   // Iterate files.
   for (Vector<Platform::FileInfo>::iterator fileItr = files.begin(); fileItr != files.end(); ++fileItr)
   {
      // Format file.
      dSprintf(filePathBuffer, sizeof(filePathBuffer), "%s/%s", fileItr->pFullPath, fileItr->pFileName);

      // Delete file.
      if (!Platform::fileDelete(filePathBuffer))
      {
         // Warn.
         Con::warnf("Could not delete file '%s'.", filePathBuffer);
         return false;
      }
   }

   // Delete the directory.
   if (!Platform::fileDelete(pPath))
   {
      // Warn.
      Con::warnf("Could not delete directory '%s'.", pPath);
      return false;
   }

   return true;
}

//-----------------------------------------------------------------------------

bool Platform::deleteDirectory(const char* pPath)
{
   // Sanity!
   AssertFatal(pPath != NULL, "Cannot delete directory that is NULL.");

   // Is the path a file?
   if (Platform::isFile(pPath))
   {
      // Yes, so warn.
      Con::warnf("Cannot delete directory '%s' as it specifies a file.", pPath);
      return false;
   }

   // Expand module location.
   char pathBuffer[1024];
   Con::expandPath(pathBuffer, sizeof(pathBuffer), pPath, NULL, true);

   // Delete directory recursively.
   return deleteDirectoryRecusrive(pathBuffer);
}

//-----------------------------------------------------------------------------

static StringTableEntry sgMainCSDir = NULL;

StringTableEntry Platform::getMainDotCsDir()
{
   if(sgMainCSDir == NULL)
      sgMainCSDir = Platform::getExecutablePath();

   return sgMainCSDir;
}

void Platform::setMainDotCsDir(const char *dir)
{
   sgMainCSDir = StringTable->insert(dir);
}

//-----------------------------------------------------------------------------

typedef Vector<char*> CharVector;
static CharVector gPlatformDirectoryExcludeList( __FILE__, __LINE__ );

void Platform::addExcludedDirectory(const char *pDir)
{
   gPlatformDirectoryExcludeList.push_back(dStrdup(pDir));
}

void Platform::clearExcludedDirectories()
{
   while(gPlatformDirectoryExcludeList.size())
   {
      dFree(gPlatformDirectoryExcludeList.last());
      gPlatformDirectoryExcludeList.pop_back();
   }
}

bool Platform::isExcludedDirectory(const char *pDir)
{
   for(CharVector::iterator i=gPlatformDirectoryExcludeList.begin(); i!=gPlatformDirectoryExcludeList.end(); i++)
      if(!dStrcmp(pDir, *i))
         return true;

   return false;
}

//-----------------------------------------------------------------------------

inline void catPath(char *dst, const char *src, U32 len)
{
   if(*dst != '/')
   {
      ++dst; --len;
      *dst = '/';
   }

   ++dst; --len;

   dStrncpy(dst, src, len);
   dst[len - 1] = 0;
}

// converts the posix root path "/" to "c:/" for win32
// FIXME: this is not ideal. the c: drive is not guaranteed to exist.
#if defined(TORQUE_OS_WIN)
static inline void _resolveLeadingSlash(char* buf, U32 size)
{
   if(buf[0] != '/')
      return;

   AssertFatal(dStrlen(buf) + 2 < size, "Expanded path would be too long");
   dMemmove(buf + 2, buf, dStrlen(buf));
   buf[0] = 'c';
   buf[1] = ':';
}
#endif

static void makeCleanPathInPlace( char* path )
{
   U32 pathDepth = 0;
   char* fromPtr = path;
   char* toPtr = path;

   bool isAbsolute = false;
   if( *fromPtr == '/' )
   {
      fromPtr ++;
      toPtr ++;
      isAbsolute = true;
   }
   else if( fromPtr[ 0 ] != '\0' && fromPtr[ 1 ] == ':' )
   {
      toPtr += 3;
      fromPtr += 3;
      isAbsolute = true;
   }

   while( *fromPtr )
   {
      if( fromPtr[ 0 ] == '.' && fromPtr[ 1 ] == '.' && fromPtr[ 2 ] == '/' )
      {
         // Back up from '../'

         if( pathDepth > 0 )
         {
            pathDepth --;
            toPtr -= 2;
            while( toPtr >= path && *toPtr != '/' )
               toPtr --;
            toPtr ++;
         }
         else if( !isAbsolute )
         {
            dMemcpy( toPtr, fromPtr, 3 );
            toPtr += 3;
         }

         fromPtr += 3;
      }
      else if( fromPtr[ 0 ] == '.' && fromPtr[ 1 ] == '/' )
      {
         // Ignore.
         fromPtr += 2;
      }
      else
      {
         if( fromPtr[ 0 ] == '/' )
            pathDepth ++;

         *toPtr ++ = *fromPtr ++;
      }
   }

   *toPtr = '\0';
}

char * Platform::makeFullPathName(const char *path, char *buffer, U32 size, const char *cwd /* = NULL */)
{
   char bspath[1024];
   dStrncpy(bspath, path, sizeof(bspath));
   bspath[sizeof(bspath)-1] = 0;
   
   for(S32 i = 0;i < dStrlen(bspath);++i)
   {
      if(bspath[i] == '\\')
         bspath[i] = '/';
   }

   if(Platform::isFullPath(bspath))
   {
      // Already a full path
      #if defined(TORQUE_OS_WIN)
         _resolveLeadingSlash(bspath, sizeof(bspath));
      #endif
      dStrncpy(buffer, bspath, size);
      buffer[size-1] = 0;
      return buffer;
   }

   // [rene, 05/05/2008] Based on overall file handling in Torque, it does not seem to make
   //    that much sense to me to base things off the current working directory here.

   if(cwd == NULL)
      cwd = Con::isCurrentScriptToolScript() ? Platform::getMainDotCsDir() : Platform::getCurrentDirectory();

   dStrncpy(buffer, cwd, size);
   buffer[size-1] = 0;

   const char* defaultDir = Con::getVariable("defaultGame");

   char *ptr = bspath;
   char *slash = NULL;
   char *endptr = buffer + dStrlen(buffer) - 1;

   do
   {
      slash = dStrchr(ptr, '/');
      if(slash)
      {
         *slash = 0;

         // Directory

         if(dStrcmp(ptr, "..") == 0)
         {
            // Parent
            endptr = dStrrchr(buffer, '/');
            if (endptr)
               *endptr-- = 0;
         }
         else if(dStrcmp(ptr, ".") == 0)
         {
            // Current dir
         }
         else if(dStrcmp(ptr, "~") == 0)
         {
            catPath(endptr, defaultDir, size - (endptr - buffer));
            endptr += dStrlen(endptr) - 1;
         }
         else if(endptr)
         {
            catPath(endptr, ptr, size - (endptr - buffer));
            endptr += dStrlen(endptr) - 1;
         }
         
         ptr = slash + 1;
      }
      else if(endptr)
      {
         // File

         catPath(endptr, ptr, size - (endptr - buffer));
         endptr += dStrlen(endptr) - 1;
      }

   } while(slash);

   return buffer;
}

bool Platform::isFullPath(const char *path)
{
   // Quick way out
   if(path[0] == '/' || path[1] == ':')
      return true;

   return false;
}

//-----------------------------------------------------------------------------

/// Return "fileName" stripped of its extension.  Only extensions contained
/// in "validExtensions" will be stripped from the filename.
///
/// @note Extensions in "validExtension" should include the dot.
String Platform::stripExtension( String fileName, Vector< String >& validExtensions )
{
   // See if we have a valid extension to strip off
   String ext;
   S32 dotPos = fileName.find( '.', 0, String::Right );
   if( dotPos != String::NPos )
      ext = fileName.substr( dotPos );

   U32 numValidExt = validExtensions.size();
   if( ext.isNotEmpty() && numValidExt )
   {
      bool validExt = false;
      for( U32 i = 0; i < numValidExt; i++ )
      {
         if( ext.equal( validExtensions[ i ], String::NoCase ) )
         {
            validExt = true;
            break;
         }
      }

      if( !validExt )
         ext = String::EmptyString;
   }

   if( ext.isEmpty() )
      return fileName;
   else
      return fileName.substr( 0, fileName.length() - ext.length() );
}

//-----------------------------------------------------------------------------
// TODO: wow really shouldn't be adding everything to the string table, use the string class!
StringTableEntry Platform::makeRelativePathName(const char *path, const char *to)
{
   // Make sure 'to' is a proper absolute path terminated with a forward slash.

   char buffer[ 2048 ];
   if( !to )
   {
      dSprintf( buffer, sizeof( buffer ), "%s/", Platform::getMainDotCsDir() );
      to = buffer;
   }
   else if( !Platform::isFullPath( to ) )
   {
      dSprintf( buffer, sizeof( buffer ), "%s/%s/", Platform::getMainDotCsDir(), to );
      makeCleanPathInPlace( buffer );
      to = buffer;
   }
   else if( to[ dStrlen( to ) - 1 ] != '/' )
   {
      U32 length = getMin( (U32)dStrlen( to ), sizeof( buffer ) - 2 );
      dMemcpy( buffer, to, length );
      buffer[ length ] = '/';
      buffer[ length + 1 ] = '\0';
      to = buffer;
   }

   // If 'path' isn't absolute, make it now.  Let's us use a single
   // absolute/absolute merge path from here on.

   char buffer2[ 1024 ];
   if( !Platform::isFullPath( path ) )
   {
      dSprintf( buffer2, sizeof( buffer2 ), "%s/%s", Platform::getMainDotCsDir(), path );
      makeCleanPathInPlace( buffer2 );
      path = buffer2;
   }

   // First, find the common prefix and see where 'path' branches off from 'to'.

   const char *pathPtr, *toPtr, *branch = path;
   for(pathPtr = path, toPtr = to;*pathPtr && *toPtr && dTolower(*pathPtr) == dTolower(*toPtr);++pathPtr, ++toPtr)
   {
      if(*pathPtr == '/')
         branch = pathPtr;
   }

   // If there's no common part, the two paths are on different drives and
   // there's nothing we can do.

   if( pathPtr == path )
      return StringTable->insert( path );

   // If 'path' and 'to' are identical (minus trailing slash or so), we can just return './'.

   else if((*pathPtr == 0 || (*pathPtr == '/' && *(pathPtr + 1) == 0)) &&
      (*toPtr == 0 || (*toPtr == '/' && *(toPtr + 1) == 0)))
   {
      char* bufPtr = buffer;
      *bufPtr ++ = '.';

      if(*pathPtr == '/' || *(pathPtr - 1) == '/')
         *bufPtr++ = '/';

      *bufPtr = 0;
      return StringTable->insert(buffer);
   }

   // If 'to' is a proper prefix of 'path', the remainder of 'path' is our relative path.

   else if( *toPtr == '\0' && toPtr[ -1 ] == '/' )
      return StringTable->insert( pathPtr );

   // Otherwise have to step up the remaining directories in 'to' and then
   // append the remainder of 'path'.

   else
   {
      // FIXME: This condition is clearly wrong
      if((*pathPtr == 0 && *toPtr == '/') || (*toPtr == '/' && *pathPtr == 0))
         branch = pathPtr;

      // Allocate a new temp so we aren't prone to buffer overruns.

      TempAlloc< char > temp( dStrlen( toPtr ) + dStrlen( branch ) + 1 );
      char* bufPtr = temp;

      // Figure out parent dirs

      for(toPtr = to + (branch - path);*toPtr;++toPtr)
      {
         if(*toPtr == '/' && *(toPtr + 1) != 0)
         {
            *bufPtr++ = '.';
            *bufPtr++ = '.';
            *bufPtr++ = '/';
         }
      }
      *bufPtr = 0;

      // Copy the rest
      if(*branch)
         dStrcpy(bufPtr, branch + 1);
      else
         *--bufPtr = 0;

      return StringTable->insert( temp );
   }
}

//-----------------------------------------------------------------------------

static StringTableEntry tryStripBasePath(const char *path, const char *base)
{
   U32 len = dStrlen(base);
   if(dStrnicmp(path, base, len) == 0)
   {
      if(*(path + len) == '/') ++len;
      return StringTable->insert(path + len, true);
   }
   return NULL;
}

StringTableEntry Platform::stripBasePath(const char *path)
{
   if(path == NULL)
      return NULL;

   StringTableEntry str = tryStripBasePath(path, Platform::getMainDotCsDir());
   
   if(str != NULL )
      return str;

   str = tryStripBasePath(path, Platform::getCurrentDirectory());
   if(str != NULL )
      return str;

   str = tryStripBasePath(path, Platform::getPrefsPath());
   if(str != NULL )
      return str;

   return path;
}

//-----------------------------------------------------------------------------

StringTableEntry Platform::getPrefsPath(const char *file /* = NULL */)
{
#ifndef TORQUE2D_TOOLS_FIXME
   return StringTable->insert(file ? file : "");
#else
   char buf[1024];
   const char *company = Con::getVariable("$Game::CompanyName");
   if(company == NULL || *company == 0)
      company = "GarageGames";

   const char *appName = Con::getVariable("$Game::GameName");
   if(appName == NULL || *appName == 0)
      appName = TORQUE_APP_NAME;

   if(file)
   {
      if(dStrstr(file, ".."))
      {
         Con::errorf("getPrefsPath - filename cannot be relative");
         return NULL;
      }

      dSprintf(buf, sizeof(buf), "%s/%s/%s/%s", Platform::getUserDataDirectory(), company, appName, file);
   }
   else
      dSprintf(buf, sizeof(buf), "%s/%s/%s", Platform::getUserDataDirectory(), company, appName);

   return StringTable->insert(buf, true);
#endif
}

//-----------------------------------------------------------------------------

DefineConsoleFunction( getUserDataDirectory, const char *, (), , "getUserDataDirectory()")
{
   return Platform::getUserDataDirectory();
}

DefineConsoleFunction( getUserHomeDirectory, const char *, (), , "getUserHomeDirectory()")
{
   return Platform::getUserHomeDirectory();
}
