/* mbed Microcontroller Library
 * Copyright (c) 2006-2019 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef MBED_CALLBACK_H
#define MBED_CALLBACK_H

#include <string.h>
#include <stdint.h>
#include <new>
#include "platform/mbed_assert.h"
#include "platform/mbed_toolchain.h"

namespace mbed {
/** \addtogroup platform-public-api */
/** @{*/
/**
 * \defgroup platform_Callback Callback class
 * @{
 */

/** Callback class based on template specialization
 *
 * @note Synchronization level: Not protected
 */
template <typename F>
class Callback;

// Internal sfinae declarations
//
// These are used to eliminate overloads based on type attributes
// 1. Does a function object have a call operator
// 2. Does a function object fit in the available storage
//
// These eliminations are handled cleanly by the compiler and avoid
// massive and misleading error messages when confronted with an
// invalid type (or worse, runtime failures)
namespace detail {
struct nil {};

template <bool B, typename R = nil>
struct enable_if {
    typedef R type;
};

template <typename R>
struct enable_if<false, R> {};

template <typename M, M>
struct is_type {
    static const bool value = true;
};
}

#define MBED_ENABLE_IF_CALLBACK_COMPATIBLE(F, M)                            \
    typename detail::enable_if<                                             \
            detail::is_type<M, &F::operator()>::value &&                    \
            sizeof(F) <= sizeof(uintptr_t)                                  \
        >::type = detail::nil()

/** Callback class based on template specialization
 *
 * @note Synchronization level: Not protected
 */
template <typename R, typename... ArgTs>
class Callback<R(ArgTs...)> {
public:
    /** Create a Callback with a static function
     *  @param func     Static function to attach
     */
    Callback(R(*func)(ArgTs...) = 0)
    {
        if (!func) {
            memset(this, 0, sizeof(Callback));
        } else {
            generate(func);
        }
    }

    /** Attach a Callback
     *  @param func     The Callback to attach
     */
    Callback(const Callback<R(ArgTs...)> &func)
    {
        memset(this, 0, sizeof(Callback));
        if (func._ops) {
            func._ops->move(this, &func);
        }
        _ops = func._ops;
    }

    /** Create a Callback with a member function
     *  @param obj      Pointer to object to invoke member function on
     *  @param method   Member function to attach
     */
    template<typename T, typename U>
    Callback(U *obj, R(T::*method)(ArgTs...))
    {
        generate(method_context<T, R(T::*)(ArgTs...)>(obj, method));
    }

    /** Create a Callback with a member function
     *  @param obj      Pointer to object to invoke member function on
     *  @param method   Member function to attach
     */
    template<typename T, typename U>
    Callback(const U *obj, R(T::*method)(ArgTs...) const)
    {
        generate(method_context<const T, R(T::*)(ArgTs...) const>(obj, method));
    }

    /** Create a Callback with a member function
     *  @param obj      Pointer to object to invoke member function on
     *  @param method   Member function to attach
     */
    template<typename T, typename U>
    Callback(volatile U *obj, R(T::*method)(ArgTs...) volatile)
    {
        generate(method_context<volatile T, R(T::*)(ArgTs...) volatile>(obj, method));
    }

    /** Create a Callback with a member function
     *  @param obj      Pointer to object to invoke member function on
     *  @param method   Member function to attach
     */
    template<typename T, typename U>
    Callback(const volatile U *obj, R(T::*method)(ArgTs...) const volatile)
    {
        generate(method_context<const volatile T, R(T::*)(ArgTs...) const volatile>(obj, method));
    }

    /** Create a Callback with a static function and bound pointer
     *  @param func     Static function to attach
     *  @param arg      Pointer argument to function
     */
    template<typename T, typename U>
    Callback(R(*func)(T *, ArgTs...), U *arg)
    {
        generate(function_context<R(*)(T *, ArgTs...), T>(func, arg));
    }

    /** Create a Callback with a static function and bound pointer
     *  @param func     Static function to attach
     *  @param arg      Pointer argument to function
     */
    template<typename T, typename U>
    Callback(R(*func)(const T *, ArgTs...), const U *arg)
    {
        generate(function_context<R(*)(const T *, ArgTs...), const T>(func, arg));
    }

    /** Create a Callback with a static function and bound pointer
     *  @param func     Static function to attach
     *  @param arg      Pointer argument to function
     */
    template<typename T, typename U>
    Callback(R(*func)(volatile T *, ArgTs...), volatile U *arg)
    {
        generate(function_context<R(*)(volatile T *, ArgTs...), volatile T>(func, arg));
    }

    /** Create a Callback with a static function and bound pointer
     *  @param func     Static function to attach
     *  @param arg      Pointer argument to function
     */
    template<typename T, typename U>
    Callback(R(*func)(const volatile T *, ArgTs...), const volatile U *arg)
    {
        generate(function_context<R(*)(const volatile T *, ArgTs...), const volatile T>(func, arg));
    }

    /** Create a Callback with a function object
     *  @param f Function object to attach
     *  @note The function object is limited to a single word of storage
     */
    template <typename F>
    Callback(F f, MBED_ENABLE_IF_CALLBACK_COMPATIBLE(F, R(F::*)(ArgTs...)))
    {
        generate(f);
    }

    /** Create a Callback with a function object
     *  @param f Function object to attach
     *  @note The function object is limited to a single word of storage
     */
    template <typename F>
    Callback(const F f, MBED_ENABLE_IF_CALLBACK_COMPATIBLE(F, R(F::*)(ArgTs...) const))
    {
        generate(f);
    }

    /** Create a Callback with a function object
     *  @param f Function object to attach
     *  @note The function object is limited to a single word of storage
     */
    template <typename F>
    Callback(volatile F f, MBED_ENABLE_IF_CALLBACK_COMPATIBLE(F, R(F::*)(ArgTs...) volatile))
    {
        generate(f);
    }

    /** Create a Callback with a function object
     *  @param f Function object to attach
     *  @note The function object is limited to a single word of storage
     */
    template <typename F>
    Callback(const volatile F f, MBED_ENABLE_IF_CALLBACK_COMPATIBLE(F, R(F::*)(ArgTs...) const volatile))
    {
        generate(f);
    }

    /** Destroy a callback
     */
    ~Callback()
    {
        if (_ops) {
            _ops->dtor(this);
        }
    }

    /** Assign a callback
     */
    Callback &operator=(const Callback &that)
    {
        if (this != &that) {
            this->~Callback();
            new (this) Callback(that);
        }

        return *this;
    }

    /** Call the attached function
     */
    R call(ArgTs... args) const
    {
        MBED_ASSERT(_ops);
        return _ops->call(this, args...);
    }

    /** Call the attached function
     */
    R operator()(ArgTs... args) const
    {
        return call(args...);
    }

    /** Test if function has been attached
     */
    operator bool() const
    {
        return _ops;
    }

    /** Test for equality
     */
    friend bool operator==(const Callback &l, const Callback &r)
    {
        return memcmp(&l, &r, sizeof(Callback)) == 0;
    }

    /** Test for inequality
     */
    friend bool operator!=(const Callback &l, const Callback &r)
    {
        return !(l == r);
    }

    /** Static thunk for passing as C-style function
     *  @param func Callback to call passed as void pointer
     *  @param args Arguments to be called with function func
     *  @return the value as determined by func which is of
     *      type and determined by the signature of func
     */
    static R thunk(void *func, ArgTs... args)
    {
        return static_cast<Callback *>(func)->call(args...);
    }

private:
    // Stored as pointer to function and pointer to optional object
    // Function pointer is stored as union of possible function types
    // to guarantee proper size and alignment
    struct _class;
    union {
        void (*_staticfunc)(ArgTs...);
        void (*_boundfunc)(_class *, ArgTs...);
        void (_class::*_methodfunc)(ArgTs...);
    } _func;
    void *_obj;

    // Dynamically dispatched operations
    const struct ops {
        R(*call)(const void *, ArgTs...);
        void (*move)(void *, const void *);
        void (*dtor)(void *);
    } *_ops;

    // Generate operations for function object
    template <typename F>
    void generate(const F &f)
    {
        static const ops ops = {
            &Callback::function_call<F>,
            &Callback::function_move<F>,
            &Callback::function_dtor<F>,
        };

        MBED_STATIC_ASSERT(sizeof(Callback) - sizeof(_ops) >= sizeof(F),
                           "Type F must not exceed the size of the Callback class");
        memset(this, 0, sizeof(Callback));
        new (this) F(f);
        _ops = &ops;
    }

    // Function attributes
    template <typename F>
    static R function_call(const void *p, ArgTs... args)
    {
        return (*(F *)p)(args...);
    }

    template <typename F>
    static void function_move(void *d, const void *p)
    {
        new (d) F(*(F *)p);
    }

    template <typename F>
    static void function_dtor(void *p)
    {
        ((F *)p)->~F();
    }

    // Wrappers for functions with context
    template <typename O, typename M>
    struct method_context {
        M method;
        O *obj;

        method_context(O *obj, M method)
            : method(method), obj(obj) {}

        R operator()(ArgTs... args) const
        {
            return (obj->*method)(args...);
        }
    };

    template <typename F, typename A>
    struct function_context {
        F func;
        A *arg;

        function_context(F func, A *arg)
            : func(func), arg(arg) {}

        R operator()(ArgTs... args) const
        {
            return func(arg, args...);
        }
    };
};

// Internally used event type
typedef Callback<void(int)> event_callback_t;


/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @return         Callback with inferred type
 */
template <typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(R(*func)(ArgTs...) = 0)
{
    return Callback<R(ArgTs...)>(func);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @return         Callback with inferred type
 */
template <typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(const Callback<R(ArgTs...)> &func)
{
    return Callback<R(ArgTs...)>(func);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param obj      Optional pointer to object to bind to function
 *  @param method   Member function to attach
 *  @return         Callback with inferred type
 */
template<typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(U *obj, R(T::*method)(ArgTs...))
{
    return Callback<R(ArgTs...)>(obj, method);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param obj      Optional pointer to object to bind to function
 *  @param method   Member function to attach
 *  @return         Callback with inferred type
 */
template<typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(const U *obj, R(T::*method)(ArgTs...) const)
{
    return Callback<R(ArgTs...)>(obj, method);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param obj      Optional pointer to object to bind to function
 *  @param method   Member function to attach
 *  @return         Callback with inferred type
 */
template<typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(volatile U *obj, R(T::*method)(ArgTs...) volatile)
{
    return Callback<R(ArgTs...)>(obj, method);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param obj      Optional pointer to object to bind to function
 *  @param method   Member function to attach
 *  @return         Callback with inferred type
 */
template<typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(const volatile U *obj, R(T::*method)(ArgTs...) const volatile)
{
    return Callback<R(ArgTs...)>(obj, method);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @param arg      Pointer argument to function
 *  @return         Callback with inferred type
 */
template <typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(R(*func)(T *, ArgTs...), U *arg)
{
    return Callback<R(ArgTs...)>(func, arg);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @param arg      Pointer argument to function
 *  @return         Callback with inferred type
 */
template <typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(R(*func)(const T *, ArgTs...), const U *arg)
{
    return Callback<R(ArgTs...)>(func, arg);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @param arg      Pointer argument to function
 *  @return         Callback with inferred type
 */
template <typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(R(*func)(volatile T *, ArgTs...), volatile U *arg)
{
    return Callback<R(ArgTs...)>(func, arg);
}

/** Create a callback class with type inferred from the arguments
 *
 *  @param func     Static function to attach
 *  @param arg      Pointer argument to function
 *  @return         Callback with inferred type
 */
template <typename T, typename U, typename R, typename... ArgTs>
Callback<R(ArgTs...)> callback(R(*func)(const volatile T *, ArgTs...), const volatile U *arg)
{
    return Callback<R(ArgTs...)>(func, arg);
}

/**@}*/

/**@}*/

} // namespace mbed

#endif
