#include "adapters/windows/Hooking.h"

#include <algorithm>
#include <vector>

#include "foundation/diagnostics/Logger.h"
#include "external/detours/detours.h"

namespace ta::adapters::windows::hooks
{
    namespace
    {
        auto hook_log = std::vector<hook_data>();

        bool same_hook(const hook_data& entry, PVOID hook, PVOID* original) noexcept
        {
            return entry.hook == hook && entry.original == original;
        }
    }

    bool hook(PVOID target, PVOID hook_proc, PVOID* original)
    {
        if (!target || !hook_proc || !original)
        {
            ta::foundation::logger::warn("ta::adapters::windows::hooks::hook skipped: invalid argument");
            return false;
        }

        if (std::any_of(hook_log.begin(), hook_log.end(), [&](const hook_data& entry)
        {
            return same_hook(entry, hook_proc, original);
        }))
        {
            return true;
        }

        *original = target;

        LONG result = DetourTransactionBegin();
        if (result != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourTransactionBegin failed");
            *original = nullptr;
            return false;
        }

        result = DetourUpdateThread(GetCurrentThread());
        if (result != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourUpdateThread failed");
            DetourTransactionAbort();
            *original = nullptr;
            return false;
        }

        result = DetourAttach(original, hook_proc);
        if (result != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourAttach failed");
            DetourTransactionAbort();
            *original = nullptr;
            return false;
        }

        result = DetourTransactionCommit();
        if (result != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourTransactionCommit failed");
            *original = nullptr;
            return false;
        }

        hook_log.push_back({ hook_proc, original });
        return true;
    }


    bool unhook_all()
    {
        if (hook_log.empty())
            return true;

        if (DetourTransactionBegin() != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourTransactionBegin failed while detaching hooks");
            return false;
        }

        if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourUpdateThread failed while detaching hooks");
            DetourTransactionAbort();
            return false;
        }

        for (const auto& log : hook_log)
        {
            if (!log.hook || !log.original || !*log.original)
                continue;

            if (DetourDetach(log.original, log.hook) != NO_ERROR)
            {
                ta::foundation::logger::warn("DetourDetach failed; preserving hook state");
                DetourTransactionAbort();
                return false;
            }
        }

        if (DetourTransactionCommit() != NO_ERROR)
        {
            ta::foundation::logger::warn("DetourTransactionCommit failed while detaching hooks");
            return false;
        }

        hook_log.clear();
        return true;
    }
}
