/** 
 * @file    kPath.cpp
 *
 * @internal
 * Copyright (C) 2008-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#define K_PLATFORM
#include <kApi/Io/kPath.h>
#include <kApi/Io/kDirectory.h>
#include <stdio.h>

kBeginStaticClassEx(k, kPath)
kEndStaticClassEx()

kFx(kStatus) xkPath_InitStatic()
{
    //set the default path separator if a user-supplied value has not been provided
    if (kApiLib_NativeSeparator() == 0)
    {
        kCheck(kApiLib_SetNativeSeparator(xkPATH_DEFAULT_NATIVE_SEPARATOR)); 
    }
   
    return kOK;
}

kFx(kStatus) xkPath_ReleaseStatic()
{
    return kOK; 
}

kFx(kChar) kPath_Separator()
{
    return xkPATH_SEPARATOR; 
}

kFx(kChar) xkPath_AltSeparator()
{
    return xkPATH_ALT_SEPARATOR; 
}

kFx(kChar) xkPath_NativeSeparator()
{
    return kApiLib_NativeSeparator();
}

kFx(kBool) kPath_IsSeparator(kChar ch)
{
    return (ch == kPath_Separator()); 
}

kFx(kChar) xkPath_VolumeSeparator()
{
    return xkPATH_VOLUME_SEPARATOR; 
}

kFx(kStatus) kPath_Combine(const kChar* segment1, const kChar* segment2, kChar* path, kSize capacity)
{
    kChar segment1Norm[kPATH_MAX]; 
    kChar segment2Norm[kPATH_MAX]; 
    kSize length1 = kStrLength(segment1); 

    //normalize inputs
    kCheck(kPath_ToNormal(segment1, segment1Norm, kCountOf(segment1Norm))); 
    kCheck(kPath_ToNormal(segment2, segment2Norm, kCountOf(segment2Norm))); 

    if (kStrEquals(segment2Norm, ""))
    {
        kCheck(kStrCopy(path, capacity, segment1Norm)); 
    }
    else if (kStrEquals(segment1Norm, ""))
    {
        kCheck(kStrCopy(path, capacity, segment2Norm)); 
    }
    else if (kPath_IsSeparator(segment1Norm[length1-1]) || kPath_IsSeparator(segment2Norm[0]))
    {
        kCheck(kStrPrintf(path, capacity, "%s%s", segment1Norm, segment2Norm)); 
    }
    else
    {
        kCheck(kStrPrintf(path, capacity, "%s%c%s", segment1Norm, kPath_Separator(), segment2Norm)); 
    }

    return kOK;
}

kFx(kStatus) kPath_Directory(const kChar* path, kChar* directory, kSize capacity)
{
    kChar pathNorm[kPATH_MAX]; 
    kSize pathLength = kStrLength(path); 
    kChar* division = kNULL; 
    
    //normalize inputs
    kCheck(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm))); 
    
    //find the last separator
    division = strrchr(pathNorm, kPath_Separator()); 
    
    if (kIsNull(division) || ((kSize)(division - pathNorm + 1) == pathLength))
    {
        //if there is no separator, or if the separator is at the end of the path (since the path is
        //in normal form, this can only mean root/windows-drive/unc-root), indicate that a parent 
        //directory was not found
        return kERROR_NOT_FOUND; 
    }
    else
    {
        kSize outputCapacity = (kSize)(division - pathNorm) + 2;         //+1 for separator, +1 for null

        if (outputCapacity > capacity) 
        {
            return kERROR_INCOMPLETE; 
        }

        //take the directory portion of the path
        kStrCopy(directory, outputCapacity, pathNorm); 
    
        //remove trailing separator, if appropriate
        kCheck(xkPath_RemoveTrailingSeparator(directory)); 

        return kOK;
    }
}

kFx(kStatus) kPath_BaseName(const kChar* path, kChar* baseName, kSize capacity)
{
    kChar pathNorm[kPATH_MAX]; 
    kChar* division = kNULL; 
    
    //normalize inputs
    kCheck(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm))); 

    //find the last separator
    division = strrchr(pathNorm, kPath_Separator()); 

    if (kIsNull(division))
    {
        if (kStrEquals(pathNorm, ""))
        {
            //if the string is empty, report that the file portion of the path was not found.
            return kERROR_NOT_FOUND; 
        }
        else
        {
            //if no separator was found, but the path contains characters, assume that it represents a file name
            kCheck(kStrCopy(baseName, capacity, pathNorm)); 
        }
    }
    else
    {
        //take the file portion of the path
        kCheck(kStrCopy(baseName, capacity, division+1)); 
    }

    return kOK;
}

//bad name, because the returned portion may as well be a directory: deprecate?
kFx(kStatus) kPath_FileName(const kChar* path, kChar* fileName, kSize capacity)
{
    return kPath_BaseName(path, fileName, capacity);
}

kFx(kStatus) kPath_Extension(const kChar* path, kChar* extension, kSize capacity)
{
    kChar name[kPATH_MAX]; 
    const kChar* division = kNULL; 
    
    kCheck(kPath_FileName(path, name, kCountOf(name))); 
   
    if (kIsNull(division = strrchr(name, '.')))
    {
        return kERROR_NOT_FOUND; 
    }
   
    kCheck(kStrCopy(extension, capacity, division+1)); 

    return kOK; 
}

kFx(kStatus) kPath_StripExtension(const kChar* path, kChar* strippedPath, kSize capacity)
{
    kChar normPath[kPATH_MAX]; 
    const kChar* division = kNULL; 
    kSize charCount;

    //normalize inputs
    kCheck(kPath_ToNormal(path, normPath, kCountOf(normPath))); 

    if (kStrEquals(normPath, ""))
    {
        //if the string is empty, report that the file portion of the path was not found.
        return kERROR_NOT_FOUND; 
    }

    //use portion behind the last separator
    division = strrchr(normPath, kPath_Separator()); 
    division = kIsNull(division) ? normPath : division+1;

    if (kIsNull(division = strrchr(division, '.')))
    {
        //no extension
        return kStrCopy(strippedPath, capacity, normPath); 
    }

    if ((charCount = (kSize)(division-normPath)) >= capacity)
    {
        return kERROR_MEMORY;
    }

    //copy portion of path without extension
    kItemCopy(strippedPath, normPath, charCount);
    strippedPath[charCount] = 0;

    return kOK; 
}

kFx(kBool) kPath_IsAbsolute(const kChar* path)
{
    kChar pathNorm[kPATH_MAX]; 
    kChar separator = kPath_Separator(); 
    
    //normalize inputs
    if (kSuccess(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm))))
    {
        kSize length = kStrLength(path); 
        kBool isRoot = (length > 0) && (pathNorm[0] == separator);                          // "/", "//"
        kBool isVolumeRoot = (length > 2) && (pathNorm[1] == xkPath_VolumeSeparator());      // "c:/"

        return isRoot || isVolumeRoot; 
    }

    return kFALSE; 
}

kFx(kSize) xkPath_CommonLength(const kChar* pathA, const kChar* pathB)
{
    kChar separator = kPath_Separator(); 
    kSize segment = 0; 
    kSize i = 0; 

    while ((pathA[i] != 0) && (pathB[i] != 0) && (xkPath_CompareChar(pathA[i], pathB[i])))
    {
        if (pathA[i] == separator)
        {
            segment = i; 
        }
        i++; 
    }

    if (((pathA[i] == 0) && ((pathB[i] == 0) || (pathB[i] == separator))) ||
        ((pathB[i] == 0) && ((pathA[i] == 0) || (pathA[i] == separator))))
    {
        segment = i; 
    }

    return segment;  
}

kFx(kStatus) kPath_ToRelative(const kChar* pathA, const kChar* pathB, kChar* bRelativeToA, kSize capacity)
{
    kChar pathANorm[kPATH_MAX]; 
    kChar pathBNorm[kPATH_MAX]; 
    kSize commonLength = 0; 
    kSize i; 

    if (!kIsNull(pathA))
    {
        kCheckArgs(kPath_IsAbsolute(pathA));
        kCheck(kPath_ToCanonical(pathA, pathANorm, kCountOf(pathANorm)));
    }
    else
    {
        kCheck(kDirectory_Current(pathANorm, sizeof(pathANorm)));
    }

    kCheckArgs(kPath_IsAbsolute(pathB));
    kCheck(kPath_ToCanonical(pathB, pathBNorm, kCountOf(pathBNorm))); 

    if (kStrCompareLower(pathANorm, pathBNorm) == 0)
    {
        //paths are identical
        return kStrCopy(bRelativeToA, capacity, "."); 
    }

    //absolute paths are guaranteed to have at least 1 character
    if (kChar_ToLower(pathANorm[0]) != kChar_ToLower(pathBNorm[0]))
    {
        //paths have no common component (possible with Windows volumes)
        return kStrCopy(bRelativeToA, capacity, pathBNorm); 
    }
    
    kCheck(kStrCopy(bRelativeToA, capacity, "")); 

    commonLength = xkPath_CommonLength(pathANorm, pathBNorm);

    //for each topmost directory in the reference path that is *not* in the target path, 
    //add an instance of ".." to the relative path
    i = commonLength; 
    while ((pathANorm[i] != 0))
    {
        if (pathANorm[i++] == kPath_Separator())
        {
            kCheck(kPath_Combine(bRelativeToA, "..", bRelativeToA, capacity)); 
        }
    }

    if (pathBNorm[commonLength] != 0)
    {
        kCheck(kPath_Combine(bRelativeToA, &pathBNorm[commonLength+1], bRelativeToA, capacity)); 
    }

    return kOK;
}

kFx(kStatus) kPath_ToAbsolute(const kChar* pathA, const kChar* bRelativeToA, kChar* pathB, kSize capacity)
{
    kChar pathANorm[kPATH_MAX]; 
    kChar bRelativeToANorm[kPATH_MAX]; 
    kSize i = 0; 

    if (!kIsNull(pathA))
    {
        kCheckArgs(kPath_IsAbsolute(pathA)); 

        kCheck(kPath_ToNormal(pathA, pathANorm, kCountOf(pathANorm)));
    }
    else
    {
        kCheck(kDirectory_Current(pathANorm, sizeof(pathANorm))); 
    }

    kCheck(kPath_ToNormal(bRelativeToA, bRelativeToANorm, kCountOf(bRelativeToANorm))); 

    if (kStrEquals(bRelativeToA, "."))
    {
        //relative path is same as absolute path
        return kStrCopy(pathB, capacity, pathA); 
    }

    if (kPath_IsAbsolute(bRelativeToA))
    {
        //input path was already in absolute form
        return kStrCopy(pathB, capacity, bRelativeToANorm); 
    }   

    kCheck(kStrCopy(pathB, capacity, pathANorm)); 

    //pop the topmost directory from the absolute reference path for each instance of ".." in the relative path
    while ((bRelativeToANorm[i] == '.') && (bRelativeToANorm[i+1] == '.'))
    {
        kCheck(kPath_Directory(pathB, pathB, capacity));  

        i += (bRelativeToANorm[i+2] == kPath_Separator()) ? 3 : 2; 
    }

    if (bRelativeToANorm[i] != 0)
    {
        kCheck(kPath_Combine(pathB, &bRelativeToANorm[i], pathB, capacity)); 
    }
        
    return kOK;
}

kFx(kStatus) kPath_ToNormal(const kChar* path, kChar* normalized, kSize capacity)
{
    kChar separator = kPath_Separator(); 
    kChar altSeparator = xkPath_AltSeparator();
    kSize length = kStrLength(path); 
    kSize i = 0; 
    
    kCheckArgs(capacity >= (length+1)); 
    
    //convert separators
    for (i = 0; i < length+1; ++i)
    {
        normalized[i] = (path[i] == altSeparator) ? separator : path[i]; 
    }

    kCheck(xkPath_RemoveTrailingSeparator(normalized)); 
    
    return kOK; 
}

kFx(kStatus) kPath_ToNative(const kChar* path, kChar* native, kSize capacity)
{    
    kChar separator = kPath_Separator(); 
    kChar altSeparator = xkPath_AltSeparator();
    kChar nativeSeparator = xkPath_NativeSeparator(); 
    kSize length = kStrLength(path); 
    kSize i = 0; 
    
    kCheckArgs(capacity >= (length+1)); 
    
    //convert separators
    for (i = 0; i < length+1; ++i)
    {
        native[i] = ((path[i] == separator) || (path[i] == altSeparator)) ? nativeSeparator : path[i]; 
    }
    
    return kOK; 
}

//original from newlib realpath.c by Werner Almesberger
kFx(kStatus) kPath_ToCanonical(const kChar* path, kChar* normalized, kSize capacity)
{
    kChar buffer[kPATH_MAX]; 
    kChar* temp = buffer;
    kChar* sepPos = kNULL;
    kChar* rootSep = normalized;
    kChar* pos = normalized;

    kCheckArgs(capacity <= kCountOf(buffer));

    kCheck(kPath_ToNormal(path, buffer, kCountOf(buffer)));

    if (!kIsNull(sepPos = strchr(temp, '/')) && (sepPos == temp || *(sepPos-1) == ':'))
    {
        rootSep = normalized + (sepPos-temp);

        while (pos != rootSep+1)
        {
            *pos++ = *temp++;
        }
    }

    *pos = 0;

    if (!*temp)
    {
        return kOK;
    }

    do
    {
        sepPos = *temp ? strchr(temp, '/') : kNULL;
        if (sepPos) *sepPos = 0;

        if (!temp[0] || (temp[0] == '.' &&
            (!temp[1] || (temp[1] == '.' && !temp[2]))))
        {
            pos--;

            if (temp[0] && temp[1])
            {
                while (pos != rootSep && *--pos != '/');
            }
        }
        else
        {
            strcpy(pos, temp);

            pos = strchr(normalized, 0);
        }

        if (!kIsNull(sepPos) || pos == rootSep)
        {
            *pos++ = '/';
            temp = sepPos+1;
        }

        *pos = 0;
    }
    while (!kIsNull(sepPos));

    return kOK;
}

kFx(kStatus) xkPath_RemoveTrailingSeparator(kChar* path)
{
    kSize length = kStrLength(path); 
    
    //if trailing slash is present...
    if ((length > 0) && (path[length-1] == kPath_Separator()))
    {
        kBool isStandardRoot = (length == 1);                                           // "/"
        kBool isUncRoot = (length == 2) && (path[0] == kPath_Separator());              // "//"    
        kBool isVolumeRoot = (length == 3) && (path[1] == xkPath_VolumeSeparator());     // "c:/"

        //if trailing slash is not a root...
        if (!isStandardRoot && !isUncRoot && !isVolumeRoot)
        {
            //remove trailing slash
            path[length-1] = 0; 
        }
    }

    return kOK; 
}

kFx(kStatus) kPath_LibraryName(const kChar* libraryName, kChar* fileName, kSize capacity)
{
    kChar tempName[kPATH_MAX]; 

    kCheck(kStrCopy(tempName, kCountOf(tempName), libraryName)); 

    return kStrPrintf(fileName, capacity, "%s.%s", tempName, xkPATH_LIBRARY_EXTENSION); 
}

kFx(kStatus) kPath_Plugin(const kChar* pluginName, kChar* pluginPath, kSize capacity)
{
    kChar fileName[kPATH_MAX]; 

    kCheck(kPath_LibraryName(pluginName, fileName, capacity)); 

    kCheck(kDirectory_Plugin(pluginPath, capacity)); 
    kCheck(kPath_Combine(pluginPath, fileName, pluginPath, capacity)); 

    return kOK;
}

kFx(kStatus) kPath_ApplicationName(const kChar* appName, kChar* fileName, kSize capacity)
{
    return kStrPrintf(fileName, capacity, "%s%s", appName, xkPATH_EXECUTABLE_EXTENSION);
}

kFx(kStatus) kPath_Application(const kChar* appName, kChar* appPath, kSize capacity)
{
    kChar fileName[kPATH_MAX];

    kCheck(kPath_ApplicationName(appName, fileName, kCountOf(fileName)));
    kCheck(kDirectory_Application(appPath, capacity));
    kCheck(kPath_Combine(appPath, fileName, appPath, capacity));

    return kOK;
}

kFx(kBool) kPath_IsFileNameValid(const kChar* fileName)
{
    return (strcspn(fileName, "\\/:*?\"<>|") == strlen(fileName)); 
}

kFx(kStatus) xkPath_FromVirtual(const kChar* vPath, kChar* path, kSize capacity)
{
    return kApiLib_DirectoryHandlers()->fromVirtual(vPath, path, capacity); 
}

kFx(kStatus) xkPath_ToVirtual(const kChar* path, kChar* vPath, kSize capacity)
{
    return kApiLib_DirectoryHandlers()->toVirtual(path, vPath, capacity); 
}

#if defined(K_WINDOWS)

kFx(kStatus) xkPath_NormalizedToNativeWideWin(const kChar* path, WCHAR* wpath, kSize capacity)
{
    kChar nativePath[kPATH_MAX]; 

    kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath))); 
    kCheck(kPath_ToNative(nativePath, nativePath, kCountOf(nativePath))); 

    if (MultiByteToWideChar(CP_UTF8, 0, nativePath, -1, wpath, (int)capacity) == 0)
    {
        return kERROR_PARAMETER; 
    }

    return kOK; 
}

kFx(kStatus) xkPath_NativeWideToNormalizedWin(const WCHAR* wpath, kChar* path, kSize capacity)
{
    kChar nativePath[kPATH_MAX]; 
   
    if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, nativePath, kCountOf(nativePath), kNULL, kNULL) == 0)
    {
        return kERROR_PARAMETER; 
    }

    kCheck(kPath_ToNormal(nativePath, nativePath, capacity)); 
    kCheck(xkPath_ToVirtual(nativePath, path, capacity)); 

    return kOK; 
}

#endif
