#include "il2cpp-config.h"

#if IL2CPP_TARGET_WINDOWS

#include "WindowsHeaders.h"

#undef FindFirstFile
#undef FindNextFile

#include "os/Directory.h"
#include "os/ErrorCodes.h"
#include "utils/StringUtils.h"
#include "utils/PathUtils.h"

#if IL2CPP_TARGET_WINRT
#include "os/BrokeredFileSystem.h"
#endif

static inline int DirectoryWin32ErrorToErrorCode(DWORD win32ErrorCode)
{
    return win32ErrorCode;
}

using namespace il2cpp::utils::PathUtils;

namespace il2cpp
{
namespace os
{
    std::string Directory::GetCurrent(int *error)
    {
        UTF16String buf;
        int len, res_len;

        len = MAX_PATH + 1;
        buf.resize(len, 0);

        *error = ERROR_SUCCESS;

        res_len = ::GetCurrentDirectory(len, (LPWSTR)buf.c_str());
        if (res_len > len) /*buf is too small.*/
        {
            int old_res_len = res_len;
            buf.resize(res_len, 0);
            res_len = ::GetCurrentDirectory(res_len, (LPWSTR)buf.c_str()) == old_res_len;
        }

        std::string directory;

        if (res_len)
        {
            len = 0;
            while (buf[len])
                ++len;

            directory = il2cpp::utils::StringUtils::Utf16ToUtf8(buf.c_str());
        }
        else
        {
            *error = DirectoryWin32ErrorToErrorCode(::GetLastError());
        }

        return directory;
    }

    bool Directory::SetCurrent(const std::string& path, int* error)
    {
        *error = kErrorCodeSuccess;

        const UTF16String utf16Path(il2cpp::utils::StringUtils::Utf8ToUtf16(path.c_str()));
        if (::SetCurrentDirectory((LPWSTR)utf16Path.c_str()))
            return true;

        *error = DirectoryWin32ErrorToErrorCode(::GetLastError());
        return false;
    }

    bool Directory::Create(const std::string& path, int *error)
    {
        *error = kErrorCodeSuccess;

        const UTF16String utf16Path(il2cpp::utils::StringUtils::Utf8ToUtf16(path.c_str()));
        if (::CreateDirectory((LPWSTR)utf16Path.c_str(), NULL))
            return true;

        auto lastError = ::GetLastError();

#if IL2CPP_TARGET_WINRT
        if (lastError == ERROR_ACCESS_DENIED)
        {
            *error = BrokeredFileSystem::CreateDirectoryW(utf16Path);
            return *error == kErrorCodeSuccess;
        }
#endif

        *error = DirectoryWin32ErrorToErrorCode(lastError);
        return false;
    }

    bool Directory::Remove(const std::string& path, int *error)
    {
        *error = kErrorCodeSuccess;

        const UTF16String utf16Path(il2cpp::utils::StringUtils::Utf8ToUtf16(path.c_str()));
        if (::RemoveDirectory((LPWSTR)utf16Path.c_str()))
            return true;

        auto lastError = ::GetLastError();

#if IL2CPP_TARGET_WINRT
        if (lastError == ERROR_ACCESS_DENIED)
        {
            *error = BrokeredFileSystem::RemoveDirectoryW(utf16Path);
            return *error == kErrorCodeSuccess;
        }
#endif

        *error = DirectoryWin32ErrorToErrorCode(lastError);
        return false;
    }

    std::set<std::string> Directory::GetFileSystemEntries(const std::string& path, const std::string& pathWithPattern, int32_t attrs, int32_t mask, int* error)
    {
        *error = kErrorCodeSuccess;
        std::set<std::string> files;
        WIN32_FIND_DATA ffd;
        const UTF16String utf16Path(il2cpp::utils::StringUtils::Utf8ToUtf16(pathWithPattern));

        HANDLE handle = ::FindFirstFileExW((LPCWSTR)utf16Path.c_str(), FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);
        if (INVALID_HANDLE_VALUE == handle)
        {
            auto lastError = ::GetLastError();

#if IL2CPP_TARGET_WINRT
            if (lastError == ERROR_ACCESS_DENIED)
                return BrokeredFileSystem::GetFileSystemEntries(utils::StringUtils::Utf8ToUtf16(path), utf16Path, attrs, mask, error);
#endif

            // Following the Mono implementation, do not treat a directory with no files as an error.
            int errorCode = DirectoryWin32ErrorToErrorCode(lastError);
            if (errorCode != ERROR_FILE_NOT_FOUND)
                *error = errorCode;
            return files;
        }

        do
        {
            const std::string fileName(il2cpp::utils::StringUtils::Utf16ToUtf8(ffd.cFileName));

            if ((fileName.length() == 1 && fileName.at(0) == '.') ||
                (fileName.length() == 2 && fileName.at(0) == '.' && fileName.at(1) == '.'))
                continue;

            if ((ffd.dwFileAttributes & mask) == attrs)
            {
                files.insert(Combine(path, fileName));
            }
        }
        while (::FindNextFileW(handle, &ffd) != 0);

        ::FindClose(handle);

        return files;
    }

    Directory::FindHandle::FindHandle(const utils::StringView<Il2CppNativeChar>& searchPathWithPattern) :
        osHandle(INVALID_HANDLE_VALUE),
        handleFlags(os::kNoFindHandleFlags),
        directoryPath(il2cpp::utils::PathUtils::DirectoryName(searchPathWithPattern)),
        pattern(il2cpp::utils::PathUtils::Basename(searchPathWithPattern))
    {
    }

    Directory::FindHandle::~FindHandle()
    {
        IL2CPP_ASSERT(osHandle == INVALID_HANDLE_VALUE);
    }

    int32_t Directory::FindHandle::CloseOSHandle()
    {
        int32_t result = os::kErrorCodeSuccess;

        if (osHandle != INVALID_HANDLE_VALUE)
        {
#if IL2CPP_TARGET_WINRT
            if (handleFlags & kUseBrokeredFileSystem)
            {
                result = BrokeredFileSystem::FindClose(osHandle);
            }
            else
#endif
            {
                result = ::FindClose(osHandle);
            }

            osHandle = INVALID_HANDLE_VALUE;
        }

        return result;
    }

    os::ErrorCode Directory::FindFirstFile(FindHandle* findHandle, const utils::StringView<Il2CppNativeChar>& searchPathWithPattern, Il2CppNativeString* resultFileName, int32_t* resultAttributes)
    {
        WIN32_FIND_DATA findData;
        HANDLE handle = FindFirstFileExW(searchPathWithPattern.Str(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0);

        if (handle != INVALID_HANDLE_VALUE)
        {
            findHandle->SetOSHandle(handle);
            *resultFileName = findData.cFileName;
            *resultAttributes = findData.dwFileAttributes;
            return os::kErrorCodeSuccess;
        }
        else
        {
            auto lastError = GetLastError();

#if IL2CPP_TARGET_WINRT
            if (lastError == ERROR_ACCESS_DENIED)
                return BrokeredFileSystem::FindFirstFileW(findHandle, searchPathWithPattern, resultFileName, resultAttributes);
#endif

            return static_cast<os::ErrorCode>(lastError);
        }
    }

    os::ErrorCode Directory::FindNextFile(FindHandle* findHandle, Il2CppNativeString* resultFileName, int32_t* resultAttributes)
    {
#if IL2CPP_TARGET_WINRT
        if (findHandle->handleFlags & kUseBrokeredFileSystem)
            return BrokeredFileSystem::FindNextFileW(findHandle, resultFileName, resultAttributes);
#endif

        WIN32_FIND_DATA findData;
        if (FindNextFileW(findHandle->osHandle, &findData) == FALSE)
            return static_cast<os::ErrorCode>(GetLastError());

        *resultFileName = findData.cFileName;
        *resultAttributes = findData.dwFileAttributes;
        return os::kErrorCodeSuccess;
    }

    int32_t Directory::CloseOSFindHandleDirectly(intptr_t osHandle)
    {
        return ::FindClose((HANDLE)osHandle);
    }
}
}

#endif
