#pragma once

#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <sstream>
#include <string>
#include <vector>

#include "foundation/diagnostics/BuildOptions.h"
#include "foundation/diagnostics/Logger.h"

namespace ta::foundation::memory::scan
{
    namespace detail
    {
        struct pattern_byte
        {
            std::uint8_t value{};
            bool wildcard{};
        };

        inline constexpr std::uintptr_t npos = static_cast<std::uintptr_t>(-1);

        inline bool parse_hex_byte(const std::string& token, std::uint8_t& out)
        {
            if (token.size() != 2) return false;

            char* end = nullptr;
            const unsigned long value = std::strtoul(token.c_str(), &end, 16);
            if (end != token.c_str() + token.size() || value > 0xFF) return false;

            out = static_cast<std::uint8_t>(value);
            return true;
        }

        inline std::vector<pattern_byte> compile_pattern(const std::string& pattern)
        {
            std::vector<pattern_byte> result;
            std::istringstream stream(pattern);
            std::string token;

            while (stream >> token)
            {
                if (token == "?" || token == "??")
                {
                    result.push_back({0, true});
                    continue;
                }

                std::uint8_t value = 0;
                if (!parse_hex_byte(token, value))
                {
                    ta::foundation::logger::error("Invalid pattern byte: " + token + " in " + pattern);
                    result.clear();
                    return result;
                }

                result.push_back({value, false});
            }

            return result;
        }
    }

    inline std::uintptr_t find_pattern_internal(const std::string& pattern, const char* module_start, size_t module_size)
    {
        if (module_start == nullptr || module_size == 0) return detail::npos;

        const std::vector<detail::pattern_byte> compiled_pattern = detail::compile_pattern(pattern);
        const size_t pattern_size = compiled_pattern.size();

        if (pattern_size == 0 || pattern_size > module_size)
        {
            ta::foundation::logger::error("Pattern not found: " + pattern);
            return detail::npos;
        }

        const auto* module_bytes = reinterpret_cast<const std::uint8_t*>(module_start);

        for (size_t i = 0; i + pattern_size <= module_size; ++i)
        {
            bool match = true;

            for (size_t j = 0; j < pattern_size; ++j)
            {
                const detail::pattern_byte& current = compiled_pattern[j];
                if (current.wildcard) continue;

                if (current.value != module_bytes[i + j])
                {
                    match = false;
                    break;
                }
            }

            if (match) return i;
        }

        ta::foundation::logger::error("Pattern not found: " + pattern);
        return detail::npos;
    }

    inline std::uintptr_t find_pattern(const std::string& pattern, const char* module_start, size_t module_size)
    {
        const std::uintptr_t res = find_pattern_internal(pattern, module_start, module_size);
        if (res == detail::npos) return 0;

        if (ta::foundation::diagnostics::build_options::verbose) ta::foundation::logger::info("Found Pattern: " + pattern);
        return reinterpret_cast<std::uintptr_t>(module_start) + res;
    }

    inline std::uintptr_t find_pattern_xref(const std::string& pattern, const char* module_start, size_t module_size,
                                    size_t offset, size_t instruction_size)
    {
        const std::uintptr_t res = find_pattern_internal(pattern, module_start, module_size);
        if (res == detail::npos) return 0;

        if (module_start == nullptr || module_size < sizeof(std::int32_t) ||
            offset > module_size - sizeof(std::int32_t) ||
            res > module_size - sizeof(std::int32_t) - offset)
        {
            ta::foundation::logger::error("Xref out of bounds: " + pattern);
            return 0;
        }

        const std::uintptr_t base = reinterpret_cast<std::uintptr_t>(module_start);
        const std::uintptr_t location = base + res + offset;

        std::int32_t relative = 0;
        std::memcpy(&relative, reinterpret_cast<const void*>(location), sizeof(relative));

        std::uintptr_t target = base + res + instruction_size;
        if (relative < 0)
        {
            target -= static_cast<std::uintptr_t>(-static_cast<std::int64_t>(relative));
        }
        else
        {
            target += static_cast<std::uintptr_t>(relative);
        }

        if (ta::foundation::diagnostics::build_options::verbose) ta::foundation::logger::info("Found Xref: " + pattern);
        return target;
    }

}
