//
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
// Copyright (C) 2019-2026 Calin A. Culianu <calin.culianu@gmail.com>
//
// 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 (see LICENSE.txt).  If not, see
// <https://www.gnu.org/licenses/>.
//
#pragma once

#include <functional> // for std::equal_to, etc
#include <memory>
#include <utility>

namespace bitcoin {
    /// An optional that stores its value on the heap, rather than in-line, in
    /// order to save memory. This is implemented using a std::unique_ptr. This
    /// optional is intended to be a drop-in replacement for a std::optional
    /// but with the in-line cost of a unique_ptr. Like an optional but unlike a
    /// unique_ptr, it can be treated as a value type. It is copy-constructible
    /// and copy-assignable (does a deep copy). It also can be
    /// copy/move-constructed or copy/move-assigned from a T.
    ///
    /// Intended to be used for "heavy" optional data members that are null in the
    /// common case, but take non-trivial amounts of memory when they are not
    /// null. In this way, the common-case ends up using less memory than a normal
    /// in-lined optional would.
    template <typename T>
    class HeapOptional {
        std::unique_ptr<T> p{};
    public:
        using element_type = T;

        constexpr HeapOptional() noexcept = default;
        explicit HeapOptional(const T & t) { *this = t; }
        explicit HeapOptional(T && t) noexcept { *this = std::move(t); }

        /// Construct the HeapOptional in-place using argument forwarding
        template <typename ...Args>
        explicit HeapOptional(Args && ...args) { emplace(std::forward<Args>(args)...); }

        HeapOptional(const HeapOptional & o) { *this = o; }
        HeapOptional(HeapOptional && o) = default;

        /// Create the new object in-place. Deletes previous object (if any) first.
        template <typename ...Args>
        void emplace(Args && ...args) { p = std::make_unique<T>(std::forward<Args>(args)...); }

        HeapOptional & operator=(const HeapOptional & o) {
            if (o.p) p = std::make_unique<T>(*o.p);
            else p.reset();
            return *this;
        }
        HeapOptional & operator=(HeapOptional && o) noexcept = default;

        HeapOptional & operator=(const T & t) { p = std::make_unique<T>(t); return *this; }
        HeapOptional & operator=(T && t) { p = std::make_unique<T>(std::move(t)); return *this; }

        operator bool() const { return static_cast<bool>(p); }
        T & operator*() { return *p; }
        const T & operator*() const { return *p; }
        T * get() { return p.get(); }
        const T * get() const { return p.get(); }
        T * operator->() { return p.operator->(); }
        const T * operator->() const { return p.operator->(); }

        void reset(T * t = nullptr) { p.reset(t); }
        T * release() { return p.release(); }


        //--- Comparison operators: ==, !=, <, does deep compare of pointed-to T values
        //    (only SFINAE-enabled if underlying type T supports these ops)

        auto operator==(const HeapOptional & o) const -> decltype(std::declval<std::equal_to<T>>()(std::declval<T>(),
                                                                                                   std::declval<T>())) {
            if (p && o.p) return std::equal_to{}(*p, *o.p); // compare by pointed-to value if both are not null
            return std::equal_to{}(p, o.p); // compare the unique_ptr's if either are null
        }
        // compare to a value directly
        auto operator==(const T & t) const -> decltype(std::declval<std::equal_to<T>>()(std::declval<T>(),
                                                                                        std::declval<T>())) {
            if (!p) return false; // we never compare equal to a real value if we are null
            return std::equal_to{}(*p, t); // compare by pointed-to value if we not null
        }

#if __cplusplus < 202000L /* C++20 auto-gens these so we don't need to bother to compile them in */
        auto operator!=(const HeapOptional & o) const -> decltype(std::declval<std::not_equal_to<T>>()(std::declval<T>(),
                                                                                                       std::declval<T>())) {
            if (p && o.p) return std::not_equal_to{}(*p, *o.p); // compare by pointed-to value if both are not null
            return std::not_equal_to{}(p, o.p); // compare the unique_ptr's if either are null
        }
        // compare to a value directly
        auto operator!=(const T & t) const -> decltype(std::declval<std::not_equal_to<T>>()(std::declval<T>(),
                                                                                            std::declval<T>())) {
            if (!p) return true; // we are not equal to t if we are nullptr
            return std::not_equal_to{}(*p, t); // compare by pointed-to value
        }
#endif

        auto operator<(const HeapOptional & o) const -> decltype(std::declval<std::less<T>>()(std::declval<T>(),
                                                                                              std::declval<T>())) {
            if (p && o.p) return std::less{}(*p, *o.p); // compare by pointed-to value if both are not null
            return std::less{}(p, o.p); // compare the unique_ptr's if either are null
        }
        // compare to a value directly
        auto operator<(const T & t) const -> decltype(std::declval<std::less<T>>()(std::declval<T>(), std::declval<T>())) {
            if (!p) return true; // if we are null we are always less
            return std::less{}(*p, t); // compare to the pointed-to value
        }
    };
} // namespace bitcoin
