#pragma once
#include <type_traits>
#include <utility>

namespace DotNetDupe {
    namespace System {
        namespace Internal {
            template <typename... Args>
            struct IActionHolder {
                virtual ~IActionHolder() = default;
                virtual void Invoke(Args... args) = 0;
                virtual IActionHolder* Clone() const = 0;
            };

            template <typename F, typename... Args>
            struct ActionHolder : IActionHolder<Args...> {
                F m_fn;
                ActionHolder(const F& fn) : m_fn(fn) {}
                ActionHolder(F&& fn) : m_fn(static_cast<F&&>(fn)) {}

                void Invoke(Args... args) override {
                    m_fn(args...);
                }

                IActionHolder<Args...>* Clone() const override {
                    return new ActionHolder<F, Args...>(m_fn);
                }
            };
        }

        // Primary Template
        template<typename... Args>
        class Action;

        // Specialization: Parameterless Action (0 Arguments)
        template<>
        class Action<> {
        public:
            Action() : m_pHolder(nullptr) {}
            Action(decltype(nullptr)) : m_pHolder(nullptr) {}
            
            template<typename F, typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, Action> && std::is_invocable_v<std::decay_t<F>>>>
            Action(F&& func) : m_pHolder(new Internal::ActionHolder<std::decay_t<F>>(static_cast<F&&>(func))) {}

            Action(const Action& other) : m_pHolder(other.m_pHolder ? other.m_pHolder->Clone() : nullptr) {}

            Action(Action&& other) noexcept : m_pHolder(other.m_pHolder) {
                other.m_pHolder = nullptr;
            }

            Action& operator=(const Action& other) {
                if (this != &other) {
                    delete m_pHolder;
                    m_pHolder = other.m_pHolder ? other.m_pHolder->Clone() : nullptr;
                }
                return *this;
            }

            Action& operator=(Action&& other) noexcept {
                if (this != &other) {
                    delete m_pHolder;
                    m_pHolder = other.m_pHolder;
                    other.m_pHolder = nullptr;
                }
                return *this;
            }

            ~Action() {
                delete m_pHolder;
                m_pHolder = nullptr;
            }
            
            void Invoke() const {
                if (m_pHolder) m_pHolder->Invoke();
            }
            
            void operator()() const {
                Invoke();
            }
            
            explicit operator bool() const { return m_pHolder != nullptr; }

        private:
            Internal::IActionHolder<>* m_pHolder;
        };

        // Partial Specialization: Parameterized Action (1 or more arguments)
        template<typename Arg1, typename... Args>
        class Action<Arg1, Args...> {
        public:
            Action() : m_pHolder(nullptr) {}
            Action(decltype(nullptr)) : m_pHolder(nullptr) {}
            
            template<typename F, typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, Action> && std::is_invocable_v<std::decay_t<F>, Arg1, Args...>>>
            Action(F&& func) : m_pHolder(new Internal::ActionHolder<std::decay_t<F>, Arg1, Args...>(static_cast<F&&>(func))) {}

            Action(const Action& other) : m_pHolder(other.m_pHolder ? other.m_pHolder->Clone() : nullptr) {}

            Action(Action&& other) noexcept : m_pHolder(other.m_pHolder) {
                other.m_pHolder = nullptr;
            }

            Action& operator=(const Action& other) {
                if (this != &other) {
                    delete m_pHolder;
                    m_pHolder = other.m_pHolder ? other.m_pHolder->Clone() : nullptr;
                }
                return *this;
            }

            Action& operator=(Action&& other) noexcept {
                if (this != &other) {
                    delete m_pHolder;
                    m_pHolder = other.m_pHolder;
                    other.m_pHolder = nullptr;
                }
                return *this;
            }

            ~Action() {
                delete m_pHolder;
                m_pHolder = nullptr;
            }
            
            void Invoke(Arg1 arg1, Args... args) const {
                if (m_pHolder) m_pHolder->Invoke(arg1, args...);
            }
            
            void operator()(Arg1 arg1, Args... args) const {
                Invoke(arg1, args...);
            }
            
            explicit operator bool() const { return m_pHolder != nullptr; }

        private:
            Internal::IActionHolder<Arg1, Args...>* m_pHolder;
        };
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)