#include "pch.h"
#include "System/IO/Directory.h"
#include "System/IO/Path.h"
#include "System/IOException.h"
#include "System/ArgumentException.h"
#include <filesystem>
#include <vector>
#include <regex>

#if defined(_WIN32)
#include "Win32Internal.h"
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
    }

    std::regex PatternToRegex(const DotNetDupe::System::String& sPattern) {
        std::string sPat = sPattern.GetRawString();
        std::string sRegexStr;
        for (char c : sPat) {
            if (c == '*') sRegexStr += ".*";
            else if (c == '?') sRegexStr += ".";
            else if (c == '.' || c == '\\' || c == '/' || c == '(' || c == ')' || c == '[' || c == ']' || c == '^' || c == '$' || c == '+' || c == '|' || c == '{' || c == '}') {
                sRegexStr += "\\";
                sRegexStr += c;
            } else {
                sRegexStr += c;
            }
        }
        return std::regex("^" + sRegexStr + "$", std::regex_constants::icase);
    }

    DotNetDupe::System::DateTimeOffset FileTimeToDateTimeOffset(fs::file_time_type ftime) {
        auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
            ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now()
        );
        auto durationSec = std::chrono::duration_cast<std::chrono::seconds>(sctp.time_since_epoch()).count();
        // 62135596800ULL is seconds between 0001-01-01 and 1970-01-01
        int64_t ticks = (durationSec + 62135596800LL) * 10000000LL;
        return DotNetDupe::System::DateTimeOffset(ticks);
    }
}

namespace DotNetDupe {
    namespace System {
        namespace IO {
            bool Directory::Exists(const String& sPath) {
                if (sPath.IsEmpty()) return false;
                std::error_code ec;
                return fs::is_directory(ToFsPath(sPath), ec);
            }

            void Directory::CreateDirectory(const String& sPath) {
                CreateDirectory(sPath, true);
            }

            void Directory::CreateDirectory(const String& sPath, bool bRecursive) {
                if (sPath.IsEmpty()) {
                    throw ArgumentException("Path cannot be empty.");
                }
                std::error_code ec;
                if (bRecursive) {
                    fs::create_directories(ToFsPath(sPath), ec);
                } else {
                    fs::create_directory(ToFsPath(sPath), ec);
                }
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
            }

            void Directory::Delete(const String& sPath) {
                Delete(sPath, false);
            }

            void Directory::Delete(const String& sPath, bool bRecursive) {
                if (sPath.IsEmpty()) {
                    throw ArgumentException("Path cannot be empty.");
                }
                fs::path p = ToFsPath(sPath);
                std::error_code ec;
                if (!fs::exists(p, ec)) {
                    throw IO::IOException("Directory does not exist.");
                }

                if (bRecursive) {
                    fs::remove_all(p, ec);
                } else {
                    fs::remove(p, ec);
                }
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
            }

            void Directory::Move(const String& sSourceDirName, const String& sDestDirName) {
                if (sSourceDirName.IsEmpty() || sDestDirName.IsEmpty()) {
                    throw ArgumentException("Source and destination paths cannot be empty.");
                }
                std::error_code ec;
                fs::rename(ToFsPath(sSourceDirName), ToFsPath(sDestDirName), ec);
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
            }

            Array<String> Directory::GetFiles(const String& sPath) {
                return GetFiles(sPath, "*");
            }

            Array<String> Directory::GetFiles(const String& sPath, const String& sSearchPattern) {
                if (!Exists(sPath)) {
                    throw IO::IOException("Directory does not exist.");
                }
                std::vector<String> results;
                std::regex reg = PatternToRegex(sSearchPattern);
                std::error_code ec;

                for (const auto& entry : fs::directory_iterator(ToFsPath(sPath), ec)) {
                    if (entry.is_regular_file(ec)) {
                        std::string filename = entry.path().filename().string();
                        if (std::regex_match(filename, reg)) {
                            results.push_back(String(entry.path().string().c_str()));
                        }
                    }
                }
                Array<String> arr((int)results.size());
                for (int iIdx = 0; iIdx < (int)results.size(); iIdx++) arr[iIdx] = results[iIdx];
                return arr;
            }

            Array<String> Directory::GetDirectories(const String& sPath) {
                return GetDirectories(sPath, "*");
            }

            Array<String> Directory::GetDirectories(const String& sPath, const String& sSearchPattern) {
                if (!Exists(sPath)) {
                    throw IO::IOException("Directory does not exist.");
                }
                std::vector<String> results;
                std::regex reg = PatternToRegex(sSearchPattern);
                std::error_code ec;

                for (const auto& entry : fs::directory_iterator(ToFsPath(sPath), ec)) {
                    if (entry.is_directory(ec)) {
                        std::string filename = entry.path().filename().string();
                        if (std::regex_match(filename, reg)) {
                            results.push_back(String(entry.path().string().c_str()));
                        }
                    }
                }
                Array<String> arr((int)results.size());
                for (int iIdx = 0; iIdx < (int)results.size(); iIdx++) arr[iIdx] = results[iIdx];
                return arr;
            }

            Array<String> Directory::GetFileSystemEntries(const String& sPath) {
                return GetFileSystemEntries(sPath, "*");
            }

            Array<String> Directory::GetFileSystemEntries(const String& sPath, const String& sSearchPattern) {
                if (!Exists(sPath)) {
                    throw IO::IOException("Directory does not exist.");
                }
                std::vector<String> results;
                std::regex reg = PatternToRegex(sSearchPattern);
                std::error_code ec;

                for (const auto& entry : fs::directory_iterator(ToFsPath(sPath), ec)) {
                    std::string filename = entry.path().filename().string();
                    if (std::regex_match(filename, reg)) {
                        results.push_back(String(entry.path().string().c_str()));
                    }
                }
                Array<String> arr((int)results.size());
                for (int iIdx = 0; iIdx < (int)results.size(); iIdx++) arr[iIdx] = results[iIdx];
                return arr;
            }

            String Directory::GetCurrentDirectory() {
                std::error_code ec;
                fs::path p = fs::current_path(ec);
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
                return String(p.string().c_str());
            }

            void Directory::SetCurrentDirectory(const String& sPath) {
                if (sPath.IsEmpty()) {
                    throw ArgumentException("Path cannot be empty.");
                }
                std::error_code ec;
                fs::current_path(ToFsPath(sPath), ec);
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
            }

            String Directory::GetDirectoryRoot(const String& sPath) {
                return Path::GetPathRoot(sPath);
            }

            DateTimeOffset Directory::GetCreationTime(const String& sPath) {
                return GetLastWriteTime(sPath);
            }

            DateTimeOffset Directory::GetLastWriteTime(const String& sPath) {
                std::error_code ec;
                auto ftime = fs::last_write_time(ToFsPath(sPath), ec);
                if (ec) {
                    throw IO::IOException(ec.message().c_str());
                }
                return FileTimeToDateTimeOffset(ftime);
            }

            DateTimeOffset Directory::GetLastAccessTime(const String& sPath) {
                return GetLastWriteTime(sPath);
            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)