// Included first and then again later to ensure that we're able to "light up" new functionality based off new includes
#include <wil/resource.h>

#include <wil/com.h>
#include <wil/stl.h>

// Headers to "light up" functionality in resource.h
#include <memory>
#include <roapi.h>
#include <winstring.h>

#include <wil/resource.h>
#include <wrl/implements.h>

#include "common.h"

TEST_CASE("ResourceTests::TestLastErrorContext", "[resource][last_error_context]")
{
    // Destructing the last_error_context restores the error.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
    }
    REQUIRE(GetLastError() == 42);

    // The context can be moved.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        {
            auto another_error42 = wil::last_error_context(std::move(error42));
            SetLastError(1);
        }
        REQUIRE(GetLastError() == 42);
        SetLastError(0);
        // error42 has been moved-from and should not do anything at destruction.
    }
    REQUIRE(GetLastError() == 0);

    // The context can be self-assigned, which has no effect.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        error42 = std::move(error42);
        SetLastError(1);
    }
    REQUIRE(GetLastError() == 42);

    // The context can be dismissed, which cause it to do nothing at destruction.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        error42.release();
        SetLastError(1);
    }
    REQUIRE(GetLastError() == 1);
}

TEST_CASE("ResourceTests::TestScopeExit", "[resource][scope_exit]")
{
    int count = 0;
    auto validate = [&](int expected) { REQUIRE(count == expected); count = 0; };

    {
        auto foo = wil::scope_exit([&] { count++; });
    }
    validate(1);

    {
        auto foo = wil::scope_exit([&] { count++; });
        foo.release();
        foo.reset();
    }
    validate(0);

    {
        auto foo = wil::scope_exit([&] { count++; });
        foo.reset();
        foo.reset();
        validate(1);
    }
    validate(0);

#ifdef WIL_ENABLE_EXCEPTIONS
    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { count++; THROW_HR(E_FAIL); });
    }
    validate(1);

    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { count++; THROW_HR(E_FAIL); });
        foo.release();
        foo.reset();
    }
    validate(0);

    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { count++; THROW_HR(E_FAIL); });
        foo.reset();
        foo.reset();
        validate(1);
    }
    validate(0);
#endif // WIL_ENABLE_EXCEPTIONS
}

interface __declspec(uuid("ececcc6a-5193-4d14-b38e-ed1460c20b00"))
ITest : public IUnknown
{
   STDMETHOD_(void, Test)() = 0;
};

class PointerTestObject : witest::AllocatedObject,
    public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>, ITest>
{
public:
    STDMETHOD_(void, Test)() {};
};

TEST_CASE("ResourceTests::TestOperationsOnGenericSmartPointerClasses", "[resource]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
    {
        // wil::unique_any_t example
        wil::unique_event ptr2(wil::EventOptions::ManualReset);
        // wil::com_ptr
        wil::com_ptr<PointerTestObject> ptr3 = Microsoft::WRL::Make<PointerTestObject>();
        // wil::shared_any_t example
        wil::shared_event ptr4(wil::EventOptions::ManualReset);
        // wistd::unique_ptr example
        auto ptr5 = wil::make_unique_failfast<POINT>();

        static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr2)>::pointer, HANDLE>::value, "type-mismatch");
        static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr3)>::pointer, PointerTestObject*>::value, "type-mismatch");

        auto p2 = wil::detach_from_smart_pointer(ptr2);
        auto p3 = wil::detach_from_smart_pointer(ptr3);
        // auto p4 = wil::detach_from_smart_pointer(ptr4); // wil::shared_any_t and std::shared_ptr do not support release().
        HANDLE p4{};
        auto p5 = wil::detach_from_smart_pointer(ptr5);

        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((p2 && p3));

        wil::attach_to_smart_pointer(ptr2, p2);
        wil::attach_to_smart_pointer(ptr3, p3);
        wil::attach_to_smart_pointer(ptr4, p4);
        wil::attach_to_smart_pointer(ptr5, p5);

        p2 = nullptr;
        p3 = nullptr;
        p4 = nullptr;
        p5 = nullptr;

        wil::detach_to_opt_param(&p2, ptr2);
        wil::detach_to_opt_param(&p3, ptr3);

        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((p2 && p3));

        wil::attach_to_smart_pointer(ptr2, p2);
        wil::attach_to_smart_pointer(ptr3, p3);
        p2 = nullptr;
        p3 = nullptr;

        wil::detach_to_opt_param(&p2, ptr2);
        wil::detach_to_opt_param(&p3, ptr3);
        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((p2 && p3));

        [&](decltype(p2)* ptr) { *ptr = p2; } (wil::out_param(ptr2));
        [&](decltype(p3)* ptr) { *ptr = p3; } (wil::out_param(ptr3));
        [&](decltype(p4)* ptr) { *ptr = p4; } (wil::out_param(ptr4));
        [&](decltype(p5)* ptr) { *ptr = p5; } (wil::out_param(ptr5));

        REQUIRE((ptr2 && ptr3));

        // Validate R-Value compilation
        wil::detach_to_opt_param(&p2, decltype(ptr2){});
        wil::detach_to_opt_param(&p3, decltype(ptr3){});
    }
#endif

    std::unique_ptr<int> ptr1(new int(1));
    Microsoft::WRL::ComPtr<PointerTestObject> ptr4 = Microsoft::WRL::Make<PointerTestObject>();

    static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr1)>::pointer, int*>::value, "type-mismatch");
    static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr4)>::pointer, PointerTestObject*>::value, "type-mismatch");

    auto p1 = wil::detach_from_smart_pointer(ptr1);
    auto p4 = wil::detach_from_smart_pointer(ptr4);

    REQUIRE((!ptr1 && !ptr4));
    REQUIRE((p1 && p4));

    wil::attach_to_smart_pointer(ptr1, p1);
    wil::attach_to_smart_pointer(ptr4, p4);

    REQUIRE((ptr1 && ptr4));

    p1 = nullptr;
    p4 = nullptr;

    int** pNull = nullptr;
    wil::detach_to_opt_param(pNull, ptr1);
    REQUIRE(ptr1);

    wil::detach_to_opt_param(&p1, ptr1);
    wil::detach_to_opt_param(&p4, ptr4);

    REQUIRE((!ptr1 && !ptr4));
    REQUIRE((p1 && p4));

    [&](decltype(p1)* ptr) { *ptr = p1; } (wil::out_param(ptr1));
    [&](decltype(p4)* ptr) { *ptr = p4; } (wil::out_param(ptr4));

    REQUIRE((ptr1 && ptr4));

    p1 = wil::detach_from_smart_pointer(ptr1);
    [&](int** ptr) { *ptr = p1; } (wil::out_param_ptr<int **>(ptr1));
    REQUIRE(ptr1);
}

// Compilation only test...
void StlAdlTest()
{
    // This test has exposed some Argument Dependent Lookup issues in wistd / stl.  Primarily we're
    // just looking for clean compilation.

    std::vector<wistd::unique_ptr<int>> v;
    v.emplace_back(new int{ 1 });
    v.emplace_back(new int{ 2 });
    v.emplace_back(new int{ 3 });
    std::rotate(begin(v), begin(v) + 1, end(v));

    REQUIRE(*v[0] == 1);
    REQUIRE(*v[1] == 3);
    REQUIRE(*v[2] == 2);

    decltype(v) v2;
    v2 = std::move(v);
    REQUIRE(*v2[0] == 1);
    REQUIRE(*v2[1] == 3);
    REQUIRE(*v2[2] == 2);

    decltype(v) v3;
    std::swap(v2, v3);
    REQUIRE(*v3[0] == 1);
    REQUIRE(*v3[1] == 3);
    REQUIRE(*v3[2] == 2);
}

// Compilation only test...
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
void UniqueProcessInfo()
{
    wil::unique_process_information process;
    CreateProcessW(nullptr, nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, nullptr, &process);
    ResumeThread(process.hThread);
    WaitForSingleObject(process.hProcess, INFINITE);
    wil::unique_process_information other(wistd::move(process));
}
#endif

struct FakeComInterface
{
    void AddRef()
    {
        refs++;
    }
    void Release()
    {
        refs--;
    }

    HRESULT __stdcall Close()
    {
        closes++;
        return S_OK;
    }

    size_t refs = 0;
    size_t closes = 0;

    bool called()
    {
        auto old = closes;
        closes = 0;
        return (old > 0);
    }

    bool has_ref()
    {
        return (refs > 0);
    }
};

static void __stdcall CloseFakeComInterface(FakeComInterface* fake)
{
    fake->Close();
}

using unique_fakeclose_call = wil::unique_com_call<FakeComInterface, decltype(&CloseFakeComInterface), CloseFakeComInterface>;

TEST_CASE("ResourceTests::VerifyUniqueComCall", "[resource][unique_com_call]")
{
    unique_fakeclose_call call1;
    unique_fakeclose_call call2;

    // intentional compilation errors
    // unique_fakeclose_call call3 = call1;
    // call2 = call1;

    FakeComInterface fake1;
    unique_fakeclose_call call4(&fake1);
    REQUIRE(fake1.has_ref());

    unique_fakeclose_call call5(wistd::move(call4));
    REQUIRE(!call4);
    REQUIRE(call5);
    REQUIRE(fake1.has_ref());

    call4 = wistd::move(call5);
    REQUIRE(call4);
    REQUIRE(!call5);
    REQUIRE(fake1.has_ref());
    REQUIRE(!fake1.called());

    FakeComInterface fake2;
    {
        unique_fakeclose_call scoped(&fake2);
    }
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());

    call4.reset(&fake2);
    REQUIRE(fake1.called());
    REQUIRE(!fake1.has_ref());
    call4.reset();
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());

    call1.reset(&fake1);
    call2.swap(call1);
    REQUIRE((call2 && !call1));

    call2.release();
    REQUIRE(!fake1.called());
    REQUIRE(!fake1.has_ref());
    REQUIRE(!call2);

    REQUIRE(*call1.addressof() == nullptr);

    call1.reset(&fake1);
    fake2.closes = 0;
    fake2.refs = 1;
    *(&call1) = &fake2;
    REQUIRE(!fake1.has_ref());
    REQUIRE(fake1.called());
    REQUIRE(fake2.has_ref());

    call1.reset(&fake1);
    fake2.closes = 0;
    fake2.refs = 1;
    *call1.put() = &fake2;
    REQUIRE(!fake1.has_ref());
    REQUIRE(fake1.called());
    REQUIRE(fake2.has_ref());

    call1.reset();
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());
}

static bool g_called = false;
static bool called()
{
    auto call = g_called;
    g_called = false;
    return (call);
}

static void __stdcall FakeCall()
{
    g_called = true;
}

using unique_fake_call = wil::unique_call<decltype(&FakeCall), FakeCall>;

TEST_CASE("ResourceTests::VerifyUniqueCall", "[resource][unique_call]")
{
    unique_fake_call call1;
    unique_fake_call call2;

    // intentional compilation errors
    // unique_fake_call call3 = call1;
    // call2 = call1;

    unique_fake_call call4;
    REQUIRE(!called());

    unique_fake_call call5(wistd::move(call4));
    REQUIRE(!call4);
    REQUIRE(call5);

    call4 = wistd::move(call5);
    REQUIRE(call4);
    REQUIRE(!call5);
    REQUIRE(!called());

    {
        unique_fake_call scoped;
    }
    REQUIRE(called());

    call4.reset();
    REQUIRE(called());
    call4.reset();
    REQUIRE(!called());

    call1.release();
    REQUIRE((!call1 && call2));
    call2.swap(call1);
    REQUIRE((call1 && !call2));

    call2.release();
    REQUIRE(!called());
    REQUIRE(!call2);

#ifdef __WIL__ROAPI_H_APPEXCEPTIONAL
    {
        auto call = wil::RoInitialize();
    }
#endif
#ifdef __WIL__ROAPI_H_APP
    {
        wil::unique_rouninitialize_call uninit;
        uninit.release();

        auto call = wil::RoInitialize_failfast();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_APPEXCEPTIONAL
    {
        auto call = wil::CoInitializeEx();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_APP
    {
        wil::unique_couninitialize_call uninit;
        uninit.release();

        auto call = wil::CoInitializeEx_failfast();
    }
#endif
}

void UniqueCallCompilationTest()
{
#ifdef __WIL__COMBASEAPI_H_EXCEPTIONAL
    {
        auto call = wil::CoImpersonateClient();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_
    {
        wil::unique_coreverttoself_call uninit;
        uninit.release();

        auto call = wil::CoImpersonateClient_failfast();
    }
#endif
}

template<typename StringType, typename VerifyContents>
static void TestStringMaker(VerifyContents&& verifyContents)
{
    PCWSTR values[] =
    {
        L"",
        L"value",
        // 300 chars
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
    };

    for (const auto& value : values)
    {
        auto const valueLength = wcslen(value);

        // Direct construction case.
        wil::details::string_maker<StringType> maker;
        THROW_IF_FAILED(maker.make(value, valueLength));
        auto result = maker.release();
        verifyContents(value, valueLength, result);

        // Two phase construction case.
        THROW_IF_FAILED(maker.make(nullptr, valueLength));
        REQUIRE(maker.buffer() != nullptr);
        // In the case of the wil::unique_hstring and the empty string the buffer is in a read only
        // section and can't be written to, so StringCchCopy(maker.buffer(), valueLength + 1, value) will fault adding the nul terminator.
        // Use memcpy_s specifying exact size that will be zero in this case instead.
        memcpy_s(maker.buffer(), valueLength * sizeof(*value), value, valueLength * sizeof(*value));
        result = maker.release();
        verifyContents(value, valueLength, result);

        {
            // no promote, ensure no leaks (not tested here, inspect in the debugger)
            wil::details::string_maker<StringType> maker2;
            THROW_IF_FAILED(maker2.make(value, valueLength));
        }
    }
}

#ifdef WIL_ENABLE_EXCEPTIONS
template <typename StringType>
static void VerifyMakeUniqueString(bool nullValueSupported = true)
{
    if (nullValueSupported)
    {
        auto value0 = wil::make_unique_string<StringType>(nullptr, 5);
    }

    struct
    {
        PCWSTR expectedValue;
        PCWSTR testValue;
        // this is an optional parameter
        size_t testLength = static_cast<size_t>(-1);
    }
    const testCaseEntries[] =
    {
        { L"value", L"value", 5 },
        { L"value", L"value" },
        { L"va", L"va\0ue", 5 },
        { L"v", L"value", 1 },
        { L"\0", L"", 5 },
        { L"\0", nullptr, 5 },
    };

    using maker = wil::details::string_maker<StringType>;
    for (auto const &entry : testCaseEntries)
    {
        bool shouldSkipNullString = ((wcscmp(entry.expectedValue, L"\0") == 0) && !nullValueSupported);
        if (!shouldSkipNullString)
        {
            auto desiredValue = wil::make_unique_string<StringType>(entry.expectedValue);
            auto stringValue = wil::make_unique_string<StringType>(entry.testValue, entry.testLength);
            auto stringValueNoThrow = wil::make_unique_string_nothrow<StringType>(entry.testValue, entry.testLength);
            auto stringValueFailFast = wil::make_unique_string_failfast<StringType>(entry.testValue, entry.testLength);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValue)) == 0);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValueNoThrow)) == 0);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValueFailFast)) == 0);
        }
    }
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerCoTaskMem", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_cotaskmem_string>();
    TestStringMaker<wil::unique_cotaskmem_string>(
        [](PCWSTR value, size_t /*valueLength*/, const wil::unique_cotaskmem_string& result)
    {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerLocalAlloc", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_hlocal_string>();
    TestStringMaker<wil::unique_hlocal_string>(
        [](PCWSTR value, size_t /*valueLength*/, const wil::unique_hlocal_string& result)
    {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerGlobalAlloc", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_hglobal_string>();
    TestStringMaker<wil::unique_hglobal_string>(
        [](PCWSTR value, size_t /*valueLength*/, const wil::unique_hglobal_string& result)
    {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerProcessHeap", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_process_heap_string>();
    TestStringMaker<wil::unique_process_heap_string>(
        [](PCWSTR value, size_t /*valueLength*/, const wil::unique_process_heap_string& result)
    {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}
#endif

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerMidl", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_midl_string>();
    TestStringMaker<wil::unique_midl_string>(
        [](PCWSTR value, size_t /*valueLength*/, const wil::unique_midl_string& result)
        {
            REQUIRE(wcscmp(value, result.get()) == 0);
        });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerHString", "[resource][string_maker]")
{
    wil::unique_hstring value;
    value.reset(static_cast<HSTRING>(nullptr));

    VerifyMakeUniqueString<wil::unique_hstring>(false);

    TestStringMaker<wil::unique_hstring>(
        [](PCWSTR value, size_t valueLength, const wil::unique_hstring& result)
    {
        UINT32 length;
        REQUIRE(wcscmp(value, WindowsGetStringRawBuffer(result.get(), &length)) == 0);
        REQUIRE(valueLength == length);
    });
}

#ifdef WIL_ENABLE_EXCEPTIONS
TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerStdWString", "[resource][string_maker]")
{
    std::string s;
    wil::details::string_maker<std::wstring> maker;

    TestStringMaker<std::wstring>(
        [](PCWSTR value, size_t valueLength, const std::wstring& result)
    {
        REQUIRE(wcscmp(value, result.c_str()) == 0);
        REQUIRE(result == value);
        REQUIRE(result.size() == valueLength);
    });
}
#endif

TEST_CASE("UniqueStringAndStringMakerTests::VerifyLegacySTringMakers", "[resource][string_maker]")
{
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
    auto l = wil::make_hlocal_string(L"value");
    l = wil::make_hlocal_string_nothrow(L"value");
    l = wil::make_hlocal_string_failfast(L"value");

    auto p = wil::make_process_heap_string(L"value");
    p = wil::make_process_heap_string_nothrow(L"value");
    p = wil::make_process_heap_string_failfast(L"value");
#endif
    auto c = wil::make_cotaskmem_string(L"value");
    c = wil::make_cotaskmem_string_nothrow(L"value");
    c = wil::make_cotaskmem_string_failfast(L"value");
}
#endif

_Use_decl_annotations_ void* __RPC_USER MIDL_user_allocate(size_t size)
{
    return ::HeapAlloc(GetProcessHeap(), 0, size);
}

_Use_decl_annotations_ void __RPC_USER MIDL_user_free(void* p)
{
    ::HeapFree(GetProcessHeap(), 0, p);
}

TEST_CASE("UniqueMidlStringTests", "[resource][rpc]")
{
    wil::unique_midl_ptr<int[]> intArray{ reinterpret_cast<int*>(::MIDL_user_allocate(sizeof(int) * 10)) };
    intArray[2] = 1;

    wil::unique_midl_ptr<int> intSingle{ reinterpret_cast<int*>(::MIDL_user_allocate(sizeof(int) * 1)) };
}

TEST_CASE("UniqueEnvironmentStrings", "[resource][win32]")
{
    wil::unique_environstrings_ptr env{ ::GetEnvironmentStringsW() };
    const wchar_t* nextVar = env.get();
    while (nextVar &&* nextVar)
    {
        // consume 'nextVar'
        nextVar += wcslen(nextVar) + 1;
    }

    wil::unique_environansistrings_ptr envAnsi{ ::GetEnvironmentStringsA() };
    const char* nextVarAnsi = envAnsi.get();
    while (nextVarAnsi && *nextVarAnsi)
    {
        // consume 'nextVar'
        nextVarAnsi += strlen(nextVarAnsi) + 1;
    }
}

TEST_CASE("UniqueVariant", "[resource][com]")
{
    wil::unique_variant var;
    var.vt = VT_BSTR;
    var.bstrVal = ::SysAllocString(L"25");
    REQUIRE(var.bstrVal != nullptr);

    auto call = [](const VARIANT&) {};
    call(var);

    VARIANT weakVar = var;
    (void)weakVar;

    wil::unique_variant var2;
    REQUIRE_SUCCEEDED(VariantChangeType(&var2, &var, 0, VT_UI4));
    REQUIRE(var2.vt == VT_UI4);
    REQUIRE(var2.uiVal == 25);
}

TEST_CASE("DefaultTemplateParamCompiles", "[resource]")
{
    wil::unique_process_heap_ptr<> a;
    wil::unique_virtualalloc_ptr<> b;

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
    wil::unique_hlocal_ptr<> c;
    wil::unique_hlocal_secure_ptr<> d;
    wil::unique_hglobal_ptr<> e;
    wil::unique_cotaskmem_secure_ptr<> f;
#endif

    wil::unique_midl_ptr<> g;
    wil::unique_cotaskmem_ptr<> h;
}