// unit-test-io.cpp

#include "../../source/core/slang-io.h"
#include "unit-test/slang-unit-test.h"

#if SLANG_WINDOWS_FAMILY
#include <windows.h>
#include <winioctl.h>
#endif
#include <limits>

using namespace Slang;

static SlangResult _checkGenerateTemporary()
{
    /// Test temporary file functionality

    List<String> paths;

    for (Index i = 0; i < 10; ++i)
    {
        String path;
        SLANG_RETURN_ON_FAIL(File::generateTemporary(toSlice("slang-check"), path));

        // The path should exist
        SLANG_CHECK(File::exists(path));

        if (paths.contains(path))
        {
            return SLANG_FAIL;
        }

        paths.add(path);
    }

    // It should be possible to write to the temporary files
    for (auto& path : paths)
    {
        SLANG_RETURN_ON_FAIL(File::writeAllText(path, path));
    }
    // It should be possible to read from the temporary files

    for (auto& path : paths)
    {
        String contents;
        SLANG_RETURN_ON_FAIL(File::readAllText(path, contents))

        SLANG_CHECK(contents == path);
    }

    // Remove all the temporary files
    for (auto& path : paths)
    {
        SLANG_CHECK(File::exists(path));

        const auto removeResult = File::remove(path);
        SLANG_CHECK(SLANG_SUCCEEDED(removeResult));

        // Check remove worked
        SLANG_CHECK(!File::exists(path));
    }

    return SLANG_OK;
}

#if SLANG_WINDOWS_FAMILY
static SlangResult _setSparseFileSize(const String& path, Int64 size)
{
    HANDLE handle = ::CreateFileW(
        path.toWString(),
        GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        nullptr);
    if (handle == INVALID_HANDLE_VALUE)
    {
        return SLANG_FAIL;
    }

    SlangResult result = SLANG_OK;
    DWORD bytesReturned = 0;
    if (!::DeviceIoControl(
            handle,
            FSCTL_SET_SPARSE,
            nullptr,
            0,
            nullptr,
            0,
            &bytesReturned,
            nullptr))
    {
        result = SLANG_FAIL;
        goto cleanup;
    }

    LARGE_INTEGER offset;
    offset.QuadPart = size;
    if (!::SetFilePointerEx(handle, offset, nullptr, FILE_BEGIN))
    {
        result = SLANG_FAIL;
        goto cleanup;
    }
    if (!::SetEndOfFile(handle))
    {
        result = SLANG_FAIL;
        goto cleanup;
    }

cleanup:
    ::CloseHandle(handle);
    return result;
}

static SlangResult _checkLargeFileExists()
{
    String path;
    SLANG_RETURN_ON_FAIL(File::generateTemporary(toSlice("slang-large"), path));

    const Int64 kTwoGB = Int64(2) * 1024 * 1024 * 1024;
    const Int64 kOneKB = 1024;
    const Int64 kLargeFileSize = kTwoGB + kOneKB;
    const SlangPathType kInvalidPathType =
        static_cast<SlangPathType>(std::numeric_limits<SlangPathTypeIntegral>::max());
    SlangPathType pathType = kInvalidPathType;
    SlangResult pathTypeResult = SLANG_FAIL;
    SlangResult result = SLANG_OK;
    if (SLANG_FAILED(_setSparseFileSize(path, kLargeFileSize)))
    {
        result = SLANG_FAIL;
        goto cleanup;
    }

    SLANG_CHECK(File::exists(path));

    pathTypeResult = Path::getPathType(path, &pathType);
    if (SLANG_FAILED(pathTypeResult))
    {
        result = pathTypeResult;
        goto cleanup;
    }
    SLANG_CHECK(pathType == SLANG_PATH_TYPE_FILE);

cleanup:
    SlangResult removeResult = File::remove(path);
    if (SLANG_FAILED(removeResult))
    {
        return removeResult;
    }
    return result;
}
#endif

SLANG_UNIT_TEST(io)
{
    SLANG_CHECK(SLANG_SUCCEEDED(_checkGenerateTemporary()));
}

SLANG_UNIT_TEST(ioLargeFileExists)
{
#if SLANG_WINDOWS_FAMILY
    SLANG_CHECK(SLANG_SUCCEEDED(_checkLargeFileExists()));
#else
    SLANG_IGNORE_TEST
#endif
}

SLANG_UNIT_TEST(uriGetPathPercentDecode)
{
    SLANG_CHECK(URI::fromString(toSlice("file://path%20name")).getPath() == "path name");
    SLANG_CHECK(URI::fromString(toSlice("file://%20")).getPath() == " ");
    SLANG_CHECK(URI::fromString(toSlice("file://a%20?x=1")).getPath() == "a ");
    SLANG_CHECK(URI::fromString(toSlice("file://path%")).getPath() == "path%");
    SLANG_CHECK(URI::fromString(toSlice("file://path%2")).getPath() == "path%2");
    SLANG_CHECK(URI::fromString(toSlice("file://path%2g")).getPath() == "path%2g");
    SLANG_CHECK(URI::fromString(toSlice("file://path%g0")).getPath() == "path%g0");
    SLANG_CHECK(URI::fromString(toSlice("file://path%gg")).getPath() == "path%gg");
    SLANG_CHECK(URI::fromString(toSlice("file://path%2?x=1")).getPath() == "path%2");
    SLANG_CHECK(URI::fromLocalFilePath(toSlice("path\tname")).getPath() == "path\tname");

    const char highBitPath[] = {'h', 'i', (char)0x80, (char)0xff, 0};
    String highBitRoundTrip = URI::fromLocalFilePath(UnownedStringSlice(highBitPath)).getPath();
    SLANG_CHECK(highBitRoundTrip.getUnownedSlice() == UnownedStringSlice(highBitPath));
}
