/*
 * Copyright (C) 2025 Dominik Drexler
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *<
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef TYR_COMMON_EQUAL_TO_HPP_
#define TYR_COMMON_EQUAL_TO_HPP_

#include "tyr/common/declarations.hpp"
#include "tyr/common/dynamic_bitset.hpp"
#include "tyr/common/observer_ptr.hpp"

#include <array>
#include <cmath>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <span>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

namespace tyr
{

/// @brief `EqualTo` is our custom equality comparator, like std::equal_to.
///
/// Forwards to std::equal_to by default.
/// Specializations can be injected into the namespace.
template<typename T>
struct EqualTo
{
    bool operator()(const T& lhs, const T& rhs) const noexcept { return std::equal_to<T> {}(lhs, rhs); }
};

template<>
struct EqualTo<::cista::offset::string>
{
    using Type = ::cista::offset::string;

    bool operator()(const Type& lhs, const Type& rhs) const noexcept { return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<char> {}); }
};

template<typename T, template<typename> typename Ptr, bool IndexPointers, typename TemplateSizeType, class Allocator>
struct EqualTo<::cista::basic_vector<T, Ptr, IndexPointers, TemplateSizeType, Allocator>>
{
    using Type = ::cista::basic_vector<T, Ptr, IndexPointers, TemplateSizeType, Allocator>;

    bool operator()(const Type& lhs, const Type& rhs) const noexcept { return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<T> {}); }
};

template<typename... Ts>
struct EqualTo<::cista::offset::variant<Ts...>>
{
    using Type = ::cista::offset::variant<Ts...>;

    bool operator()(const Type& lhs, const Type& rhs) const noexcept
    {
        return lhs.apply(
            [&](auto&& l)
            {
                return rhs.apply(
                    [&](auto&& r)
                    {
                        // Recursively apply EqualTo for matching types
                        if constexpr (std::is_same_v<std::remove_cvref_t<decltype(l)>, std::remove_cvref_t<decltype(r)>>)
                            return EqualTo<std::remove_cvref_t<decltype(l)>> {}(l, r);
                        // Different types are always unequal
                        return false;
                    });
            });
    }
};

template<typename T>
struct EqualTo<::cista::optional<T>>
{
    using Type = ::cista::optional<T>;

    bool operator()(const Type& lhs, const Type& rhs) const noexcept
    {
        if (!lhs.has_value() && !rhs.has_value())
            return true;

        if (lhs.has_value() != rhs.has_value())
            return false;

        return EqualTo<T> {}(*lhs, *rhs);
    }
};

template<typename T, size_t N>
struct EqualTo<std::array<T, N>>
{
    bool operator()(const std::array<T, N>& lhs, const std::array<T, N>& rhs) const noexcept
    {
        if constexpr (N == 0)
            return true;

        return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<std::remove_cvref_t<T>> {});
    }
};

template<typename T>
struct EqualTo<std::reference_wrapper<T>>
{
    bool operator()(const std::reference_wrapper<T>& lhs, const std::reference_wrapper<T>& rhs) const noexcept
    {
        return EqualTo<std::remove_cvref_t<T>> {}(lhs.get(), rhs.get());
    }
};

template<typename Key, typename Compare, typename Allocator>
struct EqualTo<std::set<Key, Compare, Allocator>>
{
    bool operator()(const std::set<Key, Compare, Allocator>& lhs, const std::set<Key, Compare, Allocator>& rhs) const noexcept
    {
        // Check size first
        if (lhs.size() != rhs.size())
            return false;

        // Compare each element using EqualTo
        return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<std::remove_cvref_t<Key>> {});
    }
};

template<typename Key, typename T, typename Compare, typename Allocator>
struct EqualTo<std::map<Key, T, Compare, Allocator>>
{
    bool operator()(const std::map<Key, T, Compare, Allocator>& lhs, const std::map<Key, T, Compare, Allocator>& rhs) const noexcept
    {
        // Check if sizes are different
        if (lhs.size() != rhs.size())
            return false;

        return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<std::pair<std::remove_cvref_t<Key>, std::remove_cvref_t<T>>> {});
    }
};

template<typename T, typename Allocator>
struct EqualTo<std::vector<T, Allocator>>
{
    bool operator()(const std::vector<T, Allocator>& lhs, const std::vector<T, Allocator>& rhs) const noexcept
    {
        // Check size first
        if (lhs.size() != rhs.size())
            return false;

        // Compare each element using EqualTo
        return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<std::remove_cvref_t<T>> {});
    }
};

template<typename T1, typename T2>
struct EqualTo<std::pair<T1, T2>>
{
    bool operator()(const std::pair<T1, T2>& lhs, const std::pair<T1, T2>& rhs) const noexcept
    {
        return EqualTo<std::remove_cvref_t<T1>>()(lhs.first, rhs.first) && EqualTo<std::remove_cvref_t<T2>> {}(lhs.second, rhs.second);
    }
};

template<typename... Ts>
struct EqualTo<std::tuple<Ts...>>
{
    bool operator()(const std::tuple<Ts...>& lhs, const std::tuple<Ts...>& rhs) const noexcept
    {
        return std::apply(
            [&rhs](const Ts&... lhs_args)
            { return std::apply([&lhs_args...](const Ts&... rhs_args) { return (EqualTo<std::remove_cvref_t<Ts>> {}(lhs_args, rhs_args) && ...); }, rhs); },
            lhs);
    }
};

template<typename... Ts>
struct EqualTo<std::variant<Ts...>>
{
    bool operator()(const std::variant<Ts...>& lhs, const std::variant<Ts...>& rhs) const noexcept
    {
        return std::visit(
            [](const auto& l, const auto& r)
            {
                // Recursively apply EqualTo for matching types
                if constexpr (std::is_same_v<std::remove_cvref_t<decltype(l)>, std::remove_cvref_t<decltype(r)>>)
                    return EqualTo<std::remove_cvref_t<decltype(l)>> {}(l, r);
                // Different types are always unequal
                return false;
            },
            lhs,
            rhs);
    }
};

template<typename T>
struct EqualTo<std::optional<T>>
{
    bool operator()(const std::optional<T>& lhs, const std::optional<T>& rhs) const noexcept
    {
        // Check for presence of values
        if (lhs.has_value() != rhs.has_value())
            return false;

        // If both are empty, they're equal
        if (!lhs.has_value() && !rhs.has_value())
            return true;

        // Compare the contained values using EqualTo
        return EqualTo<std::remove_cvref_t<T>> {}(lhs.value(), rhs.value());
    }
};

template<typename T, std::size_t Extent>
struct EqualTo<std::span<T, Extent>>
{
    bool operator()(const std::span<T, Extent>& lhs, const std::span<T, Extent>& rhs) const noexcept
    {
        // Check size first
        if (lhs.size() != rhs.size())
            return false;

        // Compare each element using EqualTo
        return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), EqualTo<std::remove_cvref_t<T>> {});
    }
};

/// @brief EqualTo specialization for types T that satisfy `Identifiable`.
/// Dereferences the underlying pointer before forwarding the call to the std::equal_to
/// specialization of `IdentifiableMemberView` of T to pairwise compare all members.
/// @tparam T is the type.
template<typename T>
struct EqualTo<ObserverPtr<T>>
{
    bool operator()(ObserverPtr<T> lhs, ObserverPtr<T> rhs) const noexcept { return EqualTo<std::remove_cvref_t<T>> {}(*lhs, *rhs); }
};

template<std::unsigned_integral Block>
struct EqualTo<BitsetSpan<Block>>
{
    bool operator()(const BitsetSpan<Block>& lhs, const BitsetSpan<Block>& rhs) const noexcept { return lhs == rhs; }
};

/// @brief EqualTo specialization for an `IdentifiableMembersView`
/// that pairwise compares all members.
/// @tparam ...Ts are the types of all members.
template<Identifiable T>
struct EqualTo<T>
{
    using is_transparent = void;  // <-- enables hetero lookup

    using MembersTupleType = decltype(std::declval<T>().identifying_members());

    bool operator()(const T& lhs, const T& rhs) const noexcept
    {
        return EqualTo<std::remove_cvref_t<MembersTupleType>> {}(lhs.identifying_members(), rhs.identifying_members());
    }

    // Mixed overloads required by Abseil: (T, view) and (view, T)
    template<class U>
        requires std::same_as<std::remove_cvref_t<U>, MembersTupleType>
    bool operator()(const T& a, const U& v) const noexcept
    {
        return EqualTo<std::remove_cvref_t<MembersTupleType>> {}(a.identifying_members(), v);
    }

    template<class U>
        requires std::same_as<std::remove_cvref_t<U>, MembersTupleType>
    bool operator()(const U& v, const T& b) const noexcept
    {
        return EqualTo<std::remove_cvref_t<MembersTupleType>> {}(v, b.identifying_members());
    }

    // Optional: view-view compare (handy for testing)
    template<class U, class V>
        requires(std::same_as<std::remove_cvref_t<U>, MembersTupleType> && std::same_as<std::remove_cvref_t<V>, MembersTupleType>)
    bool operator()(const U& u, const V& v) const noexcept
    {
        return EqualTo<std::remove_cvref_t<MembersTupleType>> {}(u, v);
    }
};

}

#endif
