/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * -----------------------------------------------------------------------------------
 * Most of the code in this file is based on:
 *     https://github.com/winterscar/functional-avr
 * -----------------------------------------------------------------------------------
 * MIT License
 *
 * Copyright (c) 2019 Winterscar
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * -----------------------------------------------------------------------------------
 */


#ifndef ECXX_CTL_FUNCTION_H
#define ECXX_CTL_FUNCTION_H


#include <eCxx/CTL/AlignedStorage.h>
#include <eCxx/CTL/MathUtility.h>
#include <eCxx/CTL/Utility.h>


//
// Uncomment this to enable Ext::function<T> debugging
// (the system-wide debugging macro must be enabled for it to have any effect)
//
// #define EXT_FUNCTION_DEBUG(FMT, ...) SYSTEM_DEBUG(FMT, ## __VA_ARGS__)

#ifndef EXT_FUNCTION_DEBUG
    #define NO_EXT_FUNCTION_DEBUG
    #define EXT_FUNCTION_DEBUG(FMT, ...) SYSTEM_DEBUG_NOP
#endif


namespace eCxx {


//
// Helper structs
//
namespace internal {

    template <typename...>
    struct voider : tag<void> {};

    template <typename Sig, typename = void>
    struct resOf {};

    template <typename ValueG, typename... Args>
    using invoke = decltype( eCxx::declval<ValueG>()( declval<Args>()... ) );

    template <typename ValueG, typename... Args>
    struct resOf<
        ValueG(Args...),
        typename voider< invoke<ValueG, Args...> >::Type
    >
    : tag< invoke<ValueG, Args...> >
    {};

} // namespace internal


//
// function<>
//
namespace internal {

    template <typename Sig, size_t Size, size_t Align>
    struct SmallFunction;

    template <typename ValueR, typename... Args, size_t Size, size_t Align>
    struct SmallFunction<ValueR(Args...), Size, Align> {

        struct VTable {

            void  (*copier   )(void* dst, const void* src);
            void  (*destroyer)(void*);
            ValueR(*invoke   )(const void* t, Args&&... args);

            template <typename ValueT>
            static inline const VTable* get() {
                static const VTable vtable = {
                    // copier()
                    [](void* dst, const void* src) {
                        new(dst) ValueT( *static_cast<const ValueT*>(src) );
                    },
                    // destroyer()
                    [](void* t) {
                        static_cast<ValueT*>(t)->~ValueT();
                    },
                    // invoke()
                    [] ICACHE_RAM_ATTR_LAMBDA_B (const void* t, Args&&... args) ICACHE_RAM_ATTR_LAMBDA_E -> ValueR {
                        return ( *static_cast<const ValueT*>(t) )( eCxx::forward<Args>(args)... );
                    }
                };
                return &vtable;
            }

        }; // struct VTable

        inline SmallFunction()
        : _vtable(nullptr)
        {}

        inline SmallFunction(nullptr_t)
        : _vtable(nullptr)
        {}

        template <
            typename FunctionT,
            typename DecayFuncT = typename eCxx::decay<FunctionT>::Type,
            typename eCxx::enableIf< !eCxx::isSame       <DecayFuncT,                                           SmallFunction>{}, void >::Type* = nullptr,
            typename eCxx::enableIf<  eCxx::isConvertible<typename internal::resOf<DecayFuncT&(Args...)>::Type, ValueR       >{}, void >::Type* = nullptr
        >
        inline SmallFunction(FunctionT&& f)
        : _vtable( VTable::template get<DecayFuncT>() )
        {
#if 0
            char (*__ecxx_get_size_DecayFuncT )[ sizeof (DecayFuncT) ];
            char (*__ecxx_get_align_DecayFuncT)[ alignof(DecayFuncT) ];
            char (*__ecxx_get_size_Size       )[ Size                ];
            char (*__ecxx_get_align_Align     )[ Align               ];
            ::printf("Size %d %d | Align %d %d", __ecxx_get_size_DecayFuncT, __ecxx_get_size_Size, __ecxx_get_align_DecayFuncT, __ecxx_get_align_Align);
#endif
            static_assert(sizeof (DecayFuncT) <= Size,  "the size of template argument substituting DecayFuncT is too large");
            static_assert(alignof(DecayFuncT) <= Align, "the alignment requirements for template argument substituting DecayFuncT is too large");

            new(&_data) DecayFuncT( eCxx::forward<FunctionT>(f) );
        }

        inline SmallFunction(const SmallFunction& o)
        : _vtable(o._vtable)
        {
            if(!_vtable) return;

            _vtable->copier(&_data, &o._data);
        }

        inline SmallFunction(SmallFunction&& o)
        : _vtable(o._vtable)
        {
            if(!_vtable) return;

            memcpy( &_data, &o._data, sizeof(_data) );
            o._vtable = nullptr;
        }

        inline ~SmallFunction()
        { if(_vtable) _vtable->destroyer(&_data); }

        inline SmallFunction& operator=(const SmallFunction& o)
        {
            this->~SmallFunction();
            new(this) SmallFunction(o);
            return *this;
        }

        inline SmallFunction& operator=(SmallFunction&& o)
        {
            this->~SmallFunction();
            new(this) SmallFunction( eCxx::move(o) );
            return *this;
        }

        inline ValueR ICACHE_RAM_ATTR operator()(Args... args) const
        { return _vtable->invoke(&_data, eCxx::forward<Args>(args)...); }

        inline explicit operator bool() const
        { return _vtable; }

        const VTable*                                    _vtable;
        typename eCxx::alignedStorage<Size, Align>::Type _data;

    }; // struct SmallFunction

    template <typename ValueR, typename... Args, size_t Size, size_t Align>
    inline bool operator==(const SmallFunction<ValueR(Args...), Size, Align>& f, nullptr_t)
    { return !static_cast<bool>(f); }

    template <typename ValueR, typename... Args, size_t Size, size_t Align>
    inline bool  operator==(nullptr_t, const SmallFunction<ValueR(Args...), Size, Align>& f)
    { return !static_cast<bool>(f); }

    template <typename ValueR, typename... Args, size_t Size, size_t Align>
    inline bool operator!=(const SmallFunction<ValueR(Args...), Size, Align>& f, nullptr_t)
    { return static_cast<bool>(f); }

    template <typename ValueR, typename... Args, size_t Size, size_t Align>
    inline bool operator!=(nullptr_t, const SmallFunction<ValueR(Args...), Size, Align>& f)
    { return static_cast<bool>(f); }

} // namespace internal

template <typename Sig>
using function = internal::SmallFunction<Sig, sizeof(void*) * 4, alignof(void*)>;


//
// Heap-backed version of function<>
//
// NOTE : # This version uses dynamic memory allocation. Therefore, it is more flexible than the
//          one implemented using SmallFunction<S, S, A> above.
//        # However, using many dynamic memory allocation on an MCU with very limited RAM can cause
//          severe heap fragmentation problems.
//        # To prevent conflicts, the class and its utility functions are wrapped in an additional
//          namespace.
//
namespace Ext {

namespace internal {

    using namespace eCxx::internal;

    template <typename Sig>
    struct HeapBackedFunction;

    template <typename ValueR, typename... Args>
    struct HeapBackedFunction<ValueR(Args...)> {

        struct VTable {

            void  (*copier   )(void* dst, const void* src);
            void  (*destroyer)(void*);
            ValueR(*invoke   )(const void* t, Args&&... args);

            template <typename ValueT>
            static inline const VTable* get() {
                static const VTable vtable = {
                    // copier()
                    [](void* dst, const void* src) {
                        new(dst) ValueT( *static_cast<const ValueT*>(src) );
                    },
                    // destroyer()
                    [](void* t) {
                        static_cast<ValueT*>(t)->~ValueT();
                    },
                    // invoke()
                    [] ICACHE_RAM_ATTR_LAMBDA_B (const void* t, Args&&... args) ICACHE_RAM_ATTR_LAMBDA_E -> ValueR {
                        return ( *static_cast<const ValueT*>(t) )( eCxx::forward<Args>(args)... );
                    }
                };
                return &vtable;
            }
            
        }; // struct VTable

        inline HeapBackedFunction()
        : _vtable  (nullptr)
        , _dataSize(0      )
        , _data    (nullptr)
        { EXT_FUNCTION_DEBUG( "HeapBackedFunction[CTOR-DEFAULT] : new = [%16p] %16p\n", this, _data ); }

        inline HeapBackedFunction(nullptr_t)
        : _vtable  (nullptr)
        , _dataSize(0      )
        , _data    (nullptr)
        { EXT_FUNCTION_DEBUG( "HeapBackedFunction[CTOR-NULLPTR] : new = [%16p] %16p\n", this, _data ); }

        template <
            typename FunctionT,
            typename DecayFuncT = typename eCxx::decay<FunctionT>::Type,
            typename eCxx::enableIf< !eCxx::isSame       <DecayFuncT,                                           HeapBackedFunction>{}, void >::Type* = nullptr,
            typename eCxx::enableIf<  eCxx::isConvertible<typename internal::resOf<DecayFuncT&(Args...)>::Type, ValueR            >{}, void >::Type* = nullptr
        >
        inline HeapBackedFunction(FunctionT&& f)
        : _vtable   ( VTable::template get<DecayFuncT>()                                   )
        , _dataSize ( eCxx::roundToTheNextMultipleOf<alignof(void*)>( sizeof(DecayFuncT) ) )
        , _data     ( nullptr                                                              )
        {
#ifdef __AVR_ARCH__
            static_assert(
                eCxx::roundToTheNextMultipleOf<alignof(void*)>( sizeof(DecayFuncT) ) != 0,
                "the aligned size of template argument substituting DecayFuncT is zero"
            );
#else
            static_assert(
                eCxx::roundToTheNextMultipleOf<alignof(void*)>( sizeof(DecayFuncT) ) >= sizeof(void*),
                "the aligned size of template argument substituting DecayFuncT is smaller than sizeof(void*)"
            );
#endif

            _data = aligned_malloc( alignof(void*), _dataSize );
            new(_data) DecayFuncT( eCxx::forward<FunctionT>(f) );

            EXT_FUNCTION_DEBUG( "HeapBackedFunction[CTOR-FOBJECT] : new = [%16p] %16p\n", this, _data );
        }

        inline HeapBackedFunction(const HeapBackedFunction& o)
        : _vtable   (o._vtable   )
        , _dataSize (o._dataSize )
        , _data     (nullptr     )
        {
            if(!_vtable || !_dataSize) return;

            _data = aligned_malloc( alignof(void*), _dataSize );
            _vtable->copier(_data, o._data);

            EXT_FUNCTION_DEBUG( "HeapBackedFunction[CTOR-COPY   ] : new = [%16p] %16p ; src = [%16p] %16p\n", this, _data, &o, o._data );
        }

        inline HeapBackedFunction(HeapBackedFunction&& o)
        : _vtable   (o._vtable   )
        , _dataSize (o._dataSize )
        , _data     (o._data     )
        {
            o._vtable   = nullptr;
            o._dataSize = 0;
            o._data     = nullptr;

            EXT_FUNCTION_DEBUG( "HeapBackedFunction[CTOR-MOVE   ] : dst = [%16p] %16p ; src = [%16p] %16p\n", this, _data, &o, o._data );
        }

        inline ~HeapBackedFunction()
        {
            EXT_FUNCTION_DEBUG( "HeapBackedFunction[DTOR        ] : cur = [%16p] %16p\n", this, _data );

            if(_data) {
                if(_vtable) _vtable->destroyer(_data);
                aligned_free(_data);
            }

            _vtable   = nullptr;
            _dataSize = 0;
            _data     = nullptr;
        }

        inline HeapBackedFunction& operator=(const HeapBackedFunction& o)
        {
            EXT_FUNCTION_DEBUG( "HeapBackedFunction[ASSIGN-COPY ] : cur = [%16p] %16p\n", this, _data );

            this->~HeapBackedFunction();
            new(this) HeapBackedFunction(o);
            return *this;
        }

        inline HeapBackedFunction& operator=(HeapBackedFunction&& o)
        {
            EXT_FUNCTION_DEBUG( "HeapBackedFunction[ASSIGN-MOVE ] : cur = [%16p] %16p\n", this, _data );

            this->~HeapBackedFunction();
            new(this) HeapBackedFunction( eCxx::move(o) );
            return *this;
        }

        inline ValueR ICACHE_RAM_ATTR operator()(Args... args) const
        { return _vtable->invoke(_data, eCxx::forward<Args>(args)...); }

        inline explicit operator bool() const
        { return _vtable; }

        const VTable* _vtable;
        size_t        _dataSize;
        void*         _data;

    }; // struct HeapBackedFunction

    template <typename ValueR, typename... Args>
    inline bool operator==(const HeapBackedFunction<ValueR(Args...)>& f, nullptr_t)
    { return !static_cast<bool>(f); }

    template <typename ValueR, typename... Args>
    inline bool  operator==(nullptr_t, const HeapBackedFunction<ValueR(Args...)>& f)
    { return !static_cast<bool>(f); }

    template <typename ValueR, typename... Args>
    inline bool operator!=(const HeapBackedFunction<ValueR(Args...)>& f, nullptr_t)
    { return static_cast<bool>(f); }

    template <typename ValueR, typename... Args>
    inline bool operator!=(nullptr_t, const HeapBackedFunction<ValueR(Args...)>& f)
    { return static_cast<bool>(f); }

} // namespace internal

template <typename Sig>
using function = internal::HeapBackedFunction<Sig>;

} // namespace Ext


//
// Common functional callback types using the non heap-backed version of function<>
//
using FunctionalBoolCallback                     = eCxx::function< bool(      void             ) >;

using FunctionalVoidCallback                     = eCxx::function< void(      void             ) >;
using FunctionalVoidCallback_Bool                = eCxx::function< void(      bool             ) >;
using FunctionalVoidCallback_Size                = eCxx::function< void(                size_t ) >;
using FunctionalVoidCallback_UInt8               = eCxx::function< void(                uint8_t) >;

using FunctionalVoidCallback_Size_Size           = eCxx::function< void(      size_t,   size_t ) >;
using FunctionalVoidCallback_UInt8_UInt8         = eCxx::function< void(      uint8_t,  uint8_t) >;

using FunctionalVoidCallback_VoidPtr             = eCxx::function< void(      void*            ) >;
using FunctionalVoidCallback_VoidPtr_Size        = eCxx::function< void(      void*,    size_t ) >;
using FunctionalVoidCallback_VoidPtr_UInt8       = eCxx::function< void(      void*,    uint8_t) >;
using FunctionalVoidCallback_ConstVoidPtr        = eCxx::function< void(const void*            ) >;
using FunctionalVoidCallback_ConstVoidPtr_Size   = eCxx::function< void(const void*,    size_t ) >;
using FunctionalVoidCallback_ConstVoidPtr_UInt8  = eCxx::function< void(const void*,    uint8_t) >;

using FunctionalVoidCallback_UInt8Ptr            = eCxx::function< void(      uint8_t*         ) >;
using FunctionalVoidCallback_UInt8Ptr_Size       = eCxx::function< void(      uint8_t*, size_t ) >;
using FunctionalVoidCallback_UInt8Ptr_UInt8      = eCxx::function< void(      uint8_t*, uint8_t) >;
using FunctionalVoidCallback_ConstUInt8Ptr       = eCxx::function< void(const uint8_t*         ) >;
using FunctionalVoidCallback_ConstUInt8Ptr_Size  = eCxx::function< void(const uint8_t*, size_t ) >;
using FunctionalVoidCallback_ConstUInt8Ptr_UInt8 = eCxx::function< void(const uint8_t*, uint8_t) >;


} // namespace eCxx


#endif
