//-----------------------------------------------------------------------------
// 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/util/path.h"


namespace Torque
{

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

void Path::_split(String name)
{
   S32   pos = 0;
   S32   idx = 0;

   // Make sure we have platform separators
   name = PathToPlatform(name);

   // root:
   idx = name.find(':');
   if (idx >= 0)
   {
      mRoot = name.substr(0,idx);
      pos = idx + 1;
   }
   else if( name[ 0 ] == '/' )
   {
      mRoot = "/";
   }
   else
   {
      mRoot = "";
   }

   // Extract path and strip trailing '/'
   idx = name.find('/', 0, String::Right);
   if (idx >= pos)
   {
      int len = idx - pos;
      mPath = name.substr(pos,len? len: 1);
      mPath = Path::CleanSeparators(mPath);
      pos = idx + 1;
   }
   else
   {
      mPath = "";
   }

   // file.ext
   idx = name.find('.', 0, String::Right);
   if (idx >= pos)
   {
      mFile = name.substr(pos,idx - pos);
      mExt = name.substr(idx + 1,name.length() - idx - 1);
   }
   else
   {
      mFile = name.substr(pos,name.length() - pos);
      mExt = "";
   }
}

String Path::_join() const
{
   String name;
   if( mRoot != '/' )
      name = Path::Join(mRoot, ':', mPath);
   else
      name = mPath;
   name = Path::Join(name, '/', mFile);
   name = Path::Join(name, '.', mExt);
   return name;
}

String Path::CleanSeparators(String path)
{
   return path.replace( '\\', '/' );
}

String Path::CompressPath(String path)
{
   // Remove "./" and "../" references. A ".." will also result in the
   // removal of the proceeding directory.
   // Also cleans separators as in CleanSeparators().
   // Assumes there are no trailing "/"

   // Start by cleaning separators
   path = Path::CleanSeparators(path);

   U32   src = 0;
   U32   dst = 0;

   while (path[src])
   {
      if (path[src] == '/' && path[src + 1] == '/')
      {
         src += 1;
         continue;
      }
      else if (path[src] == '.')
      {
         if (path[src + 1] == 0)
         {
            if (dst && path[dst - 1] == '/')
               dst--;
            src++;
            break;
         }
         else if (path[src + 1] == '/')
         {
            src += 2;
            continue;
         }
         else if (path[src + 1] == '.')
         {
            if (path[src + 2] == 0)
            {
               if (dst && path[dst - 1] == '/')
                  dst = path.find('/', dst - 1, String::Right);
               src += 2;
               break;
            }
            if (dst && path[dst - 1] == '/')
               dst = path.find('/', dst - 1, String::Right) + 1;
            else
               dst += 3;

            src += 3;
            continue;
         }
      }

      if (dst != src)
      {
         String   end = path.substr(src, path.length() - src);
         if (dst > 0 && end[(String::SizeType)0] == '/' && path[dst-1] == '/')
            end = end.substr(1, end.length() - 1);
         path.replace(dst, path.length() - dst, end);
         src = dst;
      }
      else
      {
         src++;
         dst++;
      }
   }

   if (src - dst)
      path.erase(dst, src - dst);

   return path;
}

Torque::Path Path::MakeRelativePath( const Path &makeRelative, const Path &relativeTo, U32 mode )
{
   // We need to find the part of makeRelative that starts to diverge from
   // relativeTo. We only need to check up to the end of makeRelative or realtiveTo
   // (whichever comes first).
   U32 minDirCount = getMin(makeRelative.getDirectoryCount(), relativeTo.getDirectoryCount());

   // Store the index of the directory where we diverge. If we never diverge this
   // will end up being the same as the number of directories in makeRelative
   U32 divergeDirIdx = 0;

   for (divergeDirIdx = 0; divergeDirIdx < minDirCount; divergeDirIdx++)
   {
      // If our directories don't match then this is the diverge directory
      if (!makeRelative.getDirectory(divergeDirIdx).equal(relativeTo.getDirectory(divergeDirIdx), mode))
         break;
   }

   // Get the part of makeRelative's path after it diverged from relativeTo's path
   String uniquePath;

   // If we never diverged then divergeDirIdx will be equal to the number of
   // directories in makeRelative and this loop will immediately exit
   for (U32 i = divergeDirIdx; i < makeRelative.getDirectoryCount(); i++)
      uniquePath += makeRelative.getDirectory(i) + "/";

   // Go ahead and add the full file name
   uniquePath += makeRelative.getFullFileName();

   // Now calculate the relative offset
   String offsetPath;

   U32 numOffset = relativeTo.getDirectoryCount() - divergeDirIdx;

   // Push back an "up" for each directory we are offset
   for (U32 i = 0; i < numOffset; i++)
      offsetPath += "../";

   return offsetPath + uniquePath;
}

String Path::Join(const String& a,String::ValueType s,const String& b)
{
   switch (s)
   {
      case '/':
      {
         if (b.isEmpty() || (b.length() == 1 && (b.c_str()[0] == '/')))
            return a;

         if (a.isEmpty())
            return b;

         String::ValueType c = a[a.length()-1];

         if (c == ':' || ((c == '/') ^ (b.c_str()[0] == '/')))
            return a + b;

         if (c == '/' && b.c_str()[0] == '/')
            return a.substr(0,a.length() - 1) + b;
         break;
      }

      case ':':
      {
         if (a.isEmpty())
            return b;

         if (b.isEmpty())
            return a + ':';
         break;
      }

      case '.':
      {
         if (b.isEmpty())
            return a;

         if (a.isEmpty())
            return '.' + b;
         break;
      }

      default:
         break;
   }

   return a + s + b;
}

bool Path::appendPath( const Path &p )
{
   mPath = CompressPath(Join(mPath,'/',p.getPath()));
   mIsDirtyPath = true;
   return true;
}

const String &Path::getFullFileName() const
{
   if ( mIsDirtyFileName )
   {
      mFullFileName = mFile;

      if (mExt.isNotEmpty())
         mFullFileName += '.' + mExt;
      mIsDirtyFileName = false;
   }

   return mFullFileName;

}

const String& Path::getFullPath() const
{
   if ( mIsDirtyPath )
   {
      mFullPath = _join();
      mIsDirtyPath = false;
   }

   return mFullPath;
}

String Path::getFullPathWithoutRoot() const
{
   return Torque::Path::Join(getPath(), '/', getFullFileName());
}

String Path::getRootAndPath() const
{
   if( mRoot != '/' )
      return Path::Join(mRoot, ':', mPath);
   else
      return mPath;
}

const String& Path::setRoot(const String &s)
{
   if ( mRoot != s )
   {
      mIsDirtyPath = true;
      mRoot = s;
   }

   return mRoot;
}

const String& Path::setPath(const String &s)
{
   String   clean = CleanSeparators(s);
   
   if ( mPath != clean )
   {
      mIsDirtyPath = true;
      mPath = clean;
   }

   return mPath;
}

const String& Path::setFileName(const String &s)
{
   if ( mFile != s )
   {
      mIsDirtyPath = true;
      mIsDirtyFileName = true;
      mFile = s;
   }

   return mFile;
}

const String& Path::setExtension(const String &s)
{
   if ( mExt != s )
   {
      mIsDirtyPath = true;
      mIsDirtyFileName = true;
      mExt = s;
   }

   return mExt; 
}

bool Path::isDirectory() const
{
   return mFile.isEmpty() && mExt.isEmpty();
}

bool Path::isRelative() const
{
   return (mPath.isEmpty() || mPath.c_str()[0] != '/');
}

bool Path::isAbsolute() const
{
   return (!mPath.isEmpty() && mPath.c_str()[0] == '/');
}

U32 Path::getDirectoryCount() const
{
   if (mPath.isEmpty())
      return 0;

   U32   count = 0;
   U32   offset = 0;

   if (mPath.c_str()[0] == '/')
      offset++;

   while (offset < mPath.length())
   {
      if (mPath[offset++] == '/')
         count++;
   }

   return count + 1;
}

String Path::getDirectory(U32 count) const
{
   if (mPath.isEmpty())
      return String();

   U32 offset = 0;

   if (mPath.c_str()[0] == '/')
      offset++;

   while (count && offset < mPath.length())
   {
      if (mPath[offset++] == '/')
         count--;
   }

   U32 end = offset;

   while (mPath[end] != '/' && end < mPath.length())
      end++;

   return mPath.substr(offset,end - offset);
}

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

String PathToPlatform(String file)
{
   if (Path::OsSeparator != '/')
      file.replace( Path::OsSeparator, '/' );

   return file;
}

String PathToOS(String file)
{
   if (Path::OsSeparator != '/')
      file.replace( '/', Path::OsSeparator );

   return file;
}

} // Namespace

