/**
 * @file Action.h
 * @author apio (cloudapio.eu)
 * @brief Wrapper for callable objects, like function pointers or lambdas.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#pragma once
#include <luna/Heap.h>
#include <luna/SharedPtr.h>

namespace os
{
    /**
     * @brief Wrapper for callable objects with no arguments.
     *
     * Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as
     * arguments. The Action type acts as a callable wrapper for such functions, allowing them to be passed as arguments
     * to any function.
     */
    class Action
    {
      public:
        /**
         * @brief Construct a new empty Action object.
         *
         * Since it is not wrapping any callable object, this object is invalid and attempting to invoke its
         * (nonexistent) callable object will result in a crash at runtime.
         */
        Action()
        {
            m_action = nullptr;
        }

        /**
         * @brief Construct a new Action object, moving it from another one.
         *
         * @param other The old Action object to move from. This object will become invalid.
         */
        Action(Action&& other) : m_action(other.m_action)
        {
            other.m_action = {};
        }

        /**
         * @brief Construct a new Action object, copying it from another one.
         *
         * @param other The old Action object to copy from.
         */
        Action(const Action& other) : m_action(other.m_action)
        {
        }

        /**
         * @brief Construct a new Action object from a callable object.
         *
         * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
         * doesn't actually need a type name.
         * @param function The callable object to wrap.
         */
        template <typename T> Action(T&& function)
        {
            m_action =
                adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl<T>(move(function))).release_value();
        }

        /**
         * @brief Assign a new callable object to this Action.
         *
         * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
         * doesn't actually need a type name.
         * @param function The callable object to wrap.
         * @return Action& A reference to this object, as required by operator=().
         */
        template <typename T> Action& operator=(T&& function)
        {
            m_action =
                adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl<T>(move(function))).release_value();
            return *this;
        }

        /**
         * @brief Call the underlying object.
         */
        void operator()()
        {
            expect(m_action, "os::Action called with no underlying callable object");
            return m_action->call();
        }

      private:
        class ActionBase : public Shareable
        {
          public:
            virtual void call() = 0;
            virtual ~ActionBase() = default;
        };

        template <typename T> class ActionImpl final : public ActionBase
        {
          public:
            ActionImpl(T&& function) : m_function(move(function))
            {
            }

            void call()
            {
                return m_function();
            }

          private:
            T m_function;
        };

        SharedPtr<ActionBase> m_action;
    };

    /**
     * @brief Wrapper for callable objects.
     *
     * Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as
     * arguments. The Function type acts as a callable wrapper for such functions, allowing them to be passed as
     * arguments to any function.
     */
    template <class... Args> class Function
    {
      public:
        /**
         * @brief Construct a new empty Function object.
         *
         * Since it is not wrapping any callable object, this object is invalid and attempting to invoke its
         * (nonexistent) callable object will result in a crash at runtime.
         */
        Function()
        {
            m_action = nullptr;
        }

        /**
         * @brief Construct a new Function object, moving it from another one.
         *
         * @param other The old Function object to move from. This object will become invalid.
         */
        Function(Function&& other) : m_action(other.m_action)
        {
            other.m_action = {};
        }

        /**
         * @brief Construct a new Function object, copying it from another one.
         *
         * @param other The old Function object to copy from.
         */
        Function(const Function& other) : m_action(other.m_action)
        {
        }

        /**
         * @brief Construct a new Function object from a callable object.
         *
         * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
         * doesn't actually need a type name.
         * @param function The callable object to wrap.
         */
        template <typename T> Function(T&& function)
        {
            m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl<T>(move(function)))
                           .release_value();
        }

        /**
         * @brief Assign a new callable object to this Function.
         *
         * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
         * doesn't actually need a type name.
         * @param function The callable object to wrap.
         * @return Action& A reference to this object, as required by operator=().
         */
        template <typename T> Function& operator=(T&& function)
        {
            m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl<T>(move(function)))
                           .release_value();
            return *this;
        }

        /**
         * @brief Call the underlying object.
         */
        void operator()(Args... args)
        {
            expect(m_action, "os::Function called with no underlying callable object");
            return m_action->call(args...);
        }

      private:
        class FunctionBase : public Shareable
        {
          public:
            virtual void call(Args... args) = 0;
            virtual ~FunctionBase() = default;
        };

        template <typename T> class FunctionImpl final : public FunctionBase
        {
          public:
            FunctionImpl(T&& function) : m_function(move(function))
            {
            }

            void call(Args... args) override
            {
                return m_function(args...);
            }

          private:
            T m_function;
        };

        SharedPtr<FunctionBase> m_action;
    };
}