#include "pch.h"
#include "System/IO/Path.h"
#include "System/IOException.h"
#include <filesystem>
#include <random>
#include <string>
#include <fstream>
#include <algorithm>
#include <initializer_list>
#include <vector>

#if defined(_WIN32)
#include <windows.h>
#include "Win32Internal.h"
#undef GetTempFileName
#undef GetTempPath
using namespace DotNetDupe::System::Internal;
#endif

namespace fs = std::filesystem;

namespace {
    fs::path ToFsPath(const DotNetDupe::System::String& sPath) {
#if defined(_WIN32)
        return fs::path(DotNetDupe::System::Internal::StringConvertInternal::Utf8ToWChar(sPath.GetRawString()));
#else
        return fs::path(sPath.GetRawString());
#endif
    }

    DotNetDupe::System::String FromFsPath(const fs::path& path) {
#if defined(_WIN32)
        return DotNetDupe::System::String(DotNetDupe::System::Internal::StringConvertInternal::WCharToUtf8(path.wstring().c_str()).c_str());
#else
        return DotNetDupe::System::String(path.string().c_str());
#endif
    }
}

namespace DotNetDupe {
    namespace System {
        namespace IO {
            String Path::ChangeExtension(const String& sFilePath, const String& sExtension) {
                if (sFilePath.GetLength() <= 0 || sExtension.GetLength() <= 0)
                    throw ArgumentException("Invalid argument");
                if (sFilePath.GetLength() <= sExtension.GetLength())
                    throw ArgumentException("Invalid argument");
                auto sTempFilePath = sFilePath;
                if (sFilePath [0] == '.')
                    sTempFilePath = sFilePath.Remove(0);

                fs::path path = ToFsPath(sTempFilePath);
#if defined(_WIN32)
                path.replace_extension(DotNetDupe::System::Internal::StringConvertInternal::Utf8ToWChar(sExtension.GetRawString()));
#else
                path.replace_extension(sExtension.GetRawString());
#endif
                return FromFsPath(path);
            }

            String Path::Combine(const std::initializer_list<String> sPaths) {
                auto indexedList = _init_list_with_indexer(sPaths);
                fs::path rootPath = ToFsPath(indexedList [0]);
                if (!rootPath.has_root_directory())
                    return indexedList [0];
                String sCombinedPath("");
                for (auto sPath : sPaths) {
                    sCombinedPath.Append(sPath);
                    sCombinedPath.Append(static_cast<char>(fs::path::preferred_separator));
                }
                return sCombinedPath.Remove(sCombinedPath.GetLength() - 1);
            }

            bool Path::EndsInDirectorySeparator(const String& sFilePath) {
                if (sFilePath.IsEmpty()) return false;
                char chLastChar = sFilePath[sFilePath.GetLength() - 1];
                return chLastChar == '\\' || chLastChar == '/';
            }

            bool Path::Exists(const String& sFilePath) {
                return fs::exists(ToFsPath(sFilePath));
            }

            String Path::GetDirectoryName(const String& sFilePath) {
                if (sFilePath.IsEmpty())
                    return "";

                fs::path path = ToFsPath(sFilePath);
                return FromFsPath(path.parent_path());
            }

            String Path::GetFileName(const String& sFilePath) {
                if (sFilePath.IsEmpty())
                    return "";
                fs::path path = ToFsPath(sFilePath);
                return FromFsPath(path.filename());
            }

            String Path::GetExtension(const String& sFilePath) {
                if (sFilePath.IsEmpty())
                    return "";

                fs::path path = ToFsPath(sFilePath);
                return FromFsPath(path.extension());
            }

            String Path::GetFileNameWithoutExtension(const String& sFilePath) {
                if (sFilePath.IsEmpty())
                    return "";

                fs::path path = ToFsPath(sFilePath);
                return FromFsPath(path.stem());
            }

            String Path::GetFullPath(const String& sPath) {
                if (sPath.IsEmpty())
                    return "";

                return FromFsPath(fs::absolute(ToFsPath(sPath)));
            }

            Array<char> Path::GetInvalidFileNameChars() {
                static const std::vector<char> tempInvalidChars = {
                    '"', '<', '>', '|', '\0',
                    '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
                    '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F',
                    ':', '*', '?', '\\', '/' };
                Array<char> invalidChars((int)tempInvalidChars.size());
                for (int iIndex = 0; iIndex < (int)tempInvalidChars.size(); iIndex++) invalidChars[iIndex] = tempInvalidChars[iIndex];
                return invalidChars;
            }

            Array<char> Path::GetInvalidPathChars() {
                static const std::vector<char> tempInvalidChars = {
                    '|', '\0',
                    '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
                    '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F' };
                Array<char> invalidChars((int)tempInvalidChars.size());
                for (int iIndex = 0; iIndex < (int)tempInvalidChars.size(); iIndex++) invalidChars[iIndex] = tempInvalidChars[iIndex];
                return invalidChars;
            }

            String Path::GetRandomFileName()
            {
                const char chChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
                const int nCharsLen = sizeof(chChars) / sizeof(char) - 1;

                std::random_device rd;
                std::mt19937 generator(rd());
                std::uniform_int_distribution<> distribution(0, nCharsLen - 1);

                char chRandomString[13];

                for (int iIndex = 0; iIndex < 8; ++iIndex) {
                    chRandomString[iIndex] = chChars[distribution(generator)];
                }
                chRandomString[8] = '.';
                for (int iIndex = 0; iIndex < 3; ++iIndex) {
                    chRandomString[9 + iIndex] = chChars[distribution(generator)];
                }
                chRandomString[12] = '\0';

                return String(chRandomString);
            }

            String Path::GetPathRoot(const String& sPath)
            {
                if (sPath.IsEmpty())
                    return "";

                if (sPath.StartsWith("\\", false))
                {
                    // UNC path like \\server\share
                    int nLen = sPath.GetLength();
                    if (nLen > 2)
                    {
                        int iIdx = sPath.IndexOf("\\", 2, false);
                        if (iIdx != -1)
                        {
                            iIdx = sPath.IndexOf("\\", iIdx + 1, false);
                            if (iIdx != -1)
                                return sPath.Substring(0, iIdx);
                        }
                    }
                    return sPath;
                }

                fs::path p = ToFsPath(sPath);
                return FromFsPath(p.root_path());
            }

            String Path::GetRelativePath(const String& sRelativeTo, const String& sPath) {
                const fs::path fsRelativeTo = ToFsPath(GetFullPath(sRelativeTo));
                const fs::path fsPath = ToFsPath(GetFullPath(sPath));

#if defined(_WIN32)
                if (fsRelativeTo.root_name() != fsPath.root_name()) {
                    return sPath;
                }
#endif

                return FromFsPath(fs::relative(fsPath, fsRelativeTo));
            }

            static String GenerateRandomTempFileName(const String& sTempDir) {
                std::random_device rd;
                std::mt19937 generator(rd());
                std::uniform_int_distribution<int> dist(0, 35);
                const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789";
                for (int i = 0; i < 100; ++i) {
                    char name[9];
                    for (int j = 0; j < 8; ++j) name[j] = alphabet[dist(generator)];
                    name[8] = '\0';
                    fs::path fp = ToFsPath(sTempDir) / (String(name) + ".tmp").GetRawString();
                    if (!fs::exists(fp)) {
                        std::ofstream ofs(fp);
                        if (ofs) return FromFsPath(fp);
                    }
                }
                throw IOException("Could not create a unique temporary file.");
            }

            String Path::GetTempFileName() {
                return GenerateRandomTempFileName(GetTempPath());
            }

            String Path::GetTempPath() {
#if defined(_WIN32)
                wchar_t buffer [MAX_PATH];
                ::GetTempPathW(MAX_PATH, buffer);
                String sPath(WCharToUtf8(buffer).c_str());
                if (!sPath.EndsWith('\\', false)) sPath.Append('\\');
                return sPath;
#else
                const char* pTmpDir = getenv("TMPDIR");
                String sPath = pTmpDir ? String(pTmpDir) : String("/tmp/");
                if (!sPath.EndsWith('/', false)) sPath.Append('/');
                return sPath;
#endif
            }

            bool Path::HasExtension(const String& sPath) {
                if (sPath.IsEmpty()) return false;
                String sFilenameStr = GetFileName(sPath);
                if (sFilenameStr.IsEmpty() || sFilenameStr == "." || sFilenameStr == "..") return false;
                std::string sFilename((const char*)sFilenameStr);
                auto nDotPos = sFilename.rfind('.');
                return (nDotPos != std::string::npos && nDotPos != sFilename.length() - 1);
            }

            bool Path::IsPathFullyQualified(const String& sPath) {
                if (sPath.IsEmpty()) return false;
                return ToFsPath(sPath).is_absolute();
            }

            bool Path::IsPathRooted(const String& sPath) {
                if (sPath.IsEmpty()) return false;
                int nLen = sPath.GetLength();
                if (sPath[0] == '\\' || sPath[0] == '/') return true;
                if (nLen >= 2 && sPath[1] == ':' && std::isalpha(static_cast<unsigned char>(sPath[0]))) {
                    return (nLen >= 3 && (sPath[2] == '\\' || sPath[2] == '/'));
                }
                return false;
            }

            String Path::Join(const std::initializer_list<String> sPaths) {
                String sResult("");
                if (TryJoin(sPaths, sResult)) return sResult;
                throw ArgumentException("Invalid character in path.");
            }

            static bool ValidatePathChars(const std::initializer_list<String>& sPaths) {
                auto invalidPathChars = Path::GetInvalidPathChars();
                for (const auto& sPath : sPaths) {
                    for (auto ch : invalidPathChars) {
                        if (sPath.Contains(ch)) return false;
                    }
                }
                return true;
            }

            static void AppendPathSegment(String& sJoined, const String& sPath) {
                if (sPath.IsEmpty()) return;
                if (sJoined.IsEmpty()) {
                    sJoined = sPath;
                } else {
                    char chLast = sJoined[sJoined.GetLength() - 1];
                    if (chLast != '\\' && chLast != '/') sJoined.Append(static_cast<char>(fs::path::preferred_separator));
                    if (sPath[0] == '\\' || sPath[0] == '/') {
                        sJoined.Append(sPath.Substring(1, sPath.GetLength() - 1));
                    } else {
                        sJoined.Append(sPath);
                    }
                }
            }

            bool Path::TryJoin(const std::initializer_list<String> sPaths, String& sResult) {
                if (!ValidatePathChars(sPaths)) {
                    sResult = String("");
                    return false;
                }
                String sJoinedPath("");
                for (const auto& sPath : sPaths) AppendPathSegment(sJoinedPath, sPath);
                sResult = sJoinedPath;
                return true;
            }

            String Path::TrimEndingDirectorySeparator(const String& sPath) {
                if (sPath.IsEmpty()) return sPath;
                if (Path::IsPathRooted(sPath) && sPath.GetLength() <= 3) return sPath;

                char chLastChar = sPath[sPath.GetLength() - 1];
                if (chLastChar == '\\' || chLastChar == '/') {
                    return sPath.Substring(0, sPath.GetLength() - 1);
                }
                return sPath;
            }

            char Path::GetDirectorySeparatorChar() {
                return static_cast<char>(fs::path::preferred_separator);
            }

            char Path::GetAltDirectorySeparatorChar() {
                return (fs::path::preferred_separator == '/') ? '\\' : '/';
            }

            char Path::GetVolumeSeparatorChar() {
                return ':';
            }

            char Path::GetPathSeparator() {
#if defined(_WIN32)
                return ';';
#else
                return ':';
#endif
            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)