/**
 * Implementation of N4562 std::experimental::any (merged into C++17 as std::any)
 * for C++11 compilers.
 *
 * See also:
 *   + http://en.cppreference.com/w/cpp/any
 *   + http://en.cppreference.com/w/cpp/experimental/any
 *   + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any
 *   + https://cplusplus.github.io/LWG/lwg-active.html#2509
 *
 * Copyright (c) 2016 Denilson das Mercês Amorim
 * Copyright (c) 2018 Claudio Fantacci
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE.md or copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef ANY_H
#define ANY_H

#include <stdexcept>
#include <typeinfo>
#include <type_traits>


namespace libany
{

class bad_any_cast : public std::bad_cast
{
public:
    const char* what() const noexcept override
    {
        return "bad any_cast";
    }
};


class any final
{
public:
    /**
     * Constructs an object of type any with an empty state.
     */
    any() :
        vtable(nullptr)
    { }


    /**
     * Constructs an object of type any with an equivalent state as other.
     */
    any(const any& rhs) :
        vtable(rhs.vtable)
    {
        if(rhs.has_value())
        {
            rhs.vtable->copy(rhs.storage, this->storage);
        }
    }


    /**
     * Constructs an object of type any with a state equivalent to the original state of other.
     * rhs is left in a valid but otherwise unspecified state.
     */
    any(any&& rhs) noexcept :
        vtable(rhs.vtable)
    {
        if(rhs.has_value())
        {
            rhs.vtable->move(rhs.storage, this->storage);
            rhs.vtable = nullptr;
        }
    }


    /**
     * Same effect as this->clear().
     */
    ~any()
    {
        this->reset();
    }


    /**
     * Constructs an object of type any that contains an object of type T direct-initialized with std::forward<ValueType>(value).
     * T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
     * This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
     */
    template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
    any(ValueType&& value)
    {
        static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value,
                      "T shall satisfy the CopyConstructible requirements.");
        this->construct(std::forward<ValueType>(value));
    }


    /**
     * Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown.
     */
    any& operator=(const any& rhs)
    {
        any(rhs).swap(*this);
        return *this;
    }


    /**
     * Has the same effect as any(std::move(rhs)).swap(*this).
     * The state of *this is equivalent to the original state of rhs and rhs is left in a valid
     * but otherwise unspecified state.
     */
    any& operator=(any&& rhs) noexcept
    {
        any(std::move(rhs)).swap(*this);
        return *this;
    }


    /**
     * Has the same effect as any(std::forward<ValueType>(value)).swap(*this). No effect if a exception is thrown.
     * T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
     * This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
     */
    template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
    any& operator=(ValueType&& value)
    {
        static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value, "T shall satisfy the CopyConstructible requirements.");
        any(std::forward<ValueType>(value)).swap(*this);
        return *this;
    }


    /**
     * If not empty, destroys the contained object.
     */
    void reset() noexcept
    {
        if(has_value())
        {
            this->vtable->destroy(storage);
            this->vtable = nullptr;
        }
    }


    /**
     * Returns true if *this has no contained object, otherwise false.
     */
    bool has_value() const noexcept
    {
        return this->vtable != nullptr;
    }


    /**
     * If *this has a contained object of type T, typeid(T); otherwise typeid(void).
     */
    const std::type_info& type() const noexcept
    {
        return has_value()? this->vtable->type() : typeid(void);
    }


    /**
     * Exchange the states of *this and rhs.
     */
    void swap(any& other) noexcept
    {
        if(this->vtable != other.vtable)
        {
            any tmp(std::move(other));

            other.vtable = this->vtable;
            if(this->vtable != nullptr)
                this->vtable->move(this->storage, other.storage);

            this->vtable = tmp.vtable;
            if(tmp.vtable != nullptr)
            {
                tmp.vtable->move(tmp.storage, this->storage);
                tmp.vtable = nullptr;
            }
        }
        else
        {
            if(this->vtable != nullptr)
                this->vtable->swap(this->storage, other.storage);
        }
    }


private:
    union storage_union
    {
        using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of<void*>::value>::type;

        void* dynamic;

        stack_storage_t stack;
    };


    /**
     * Base VTable specification.
     *
     * Note: The caller is responsible for doing .vtable = nullptr after destructful operations
     * such as destroy() and/or move().
     */
    struct vtable_type
    {
        /**
         * The type of the object this vtable is for.
         */
        const std::type_info& (*type)() noexcept;


        /**
         * Destroys the object in the union.
         * The state of the union after this call is unspecified, caller must ensure not to use src anymore.
         */
        void(*destroy)(storage_union&) noexcept;


        /**
         * Copies the **inner** content of the src union into the yet unitialized dest union.
         * As such, both inner objects will have the same state, but on separate memory locations.
         */
        void(*copy)(const storage_union& src, storage_union& dest);


        /**
         * Moves the storage from src to the yet unitialized dest union.
         * The state of src after this call is unspecified, caller must ensure not to use src anymore.
         */
        void(*move)(storage_union& src, storage_union& dest) noexcept;


        /**
         * Exchanges the storage between lhs and rhs.
         */
        void(*swap)(storage_union& lhs, storage_union& rhs) noexcept;
    };


    /**
     * VTable for dynamically allocated storage.
     */
    template<typename T>
    struct vtable_dynamic
    {
        static const std::type_info& type() noexcept
        {
            return typeid(T);
        }


        static void destroy(storage_union& storage) noexcept
        {
            delete reinterpret_cast<T*>(storage.dynamic);
        }


        static void copy(const storage_union& src, storage_union& dest)
        {
            dest.dynamic = new T(*reinterpret_cast<const T*>(src.dynamic));
        }


        static void move(storage_union& src, storage_union& dest) noexcept
        {
            dest.dynamic = src.dynamic;
            src.dynamic = nullptr;
        }


        static void swap(storage_union& lhs, storage_union& rhs) noexcept
        {
            std::swap(lhs.dynamic, rhs.dynamic);
        }
    };


    /**
     * VTable for stack allocated storage.
     */
    template<typename T>
    struct vtable_stack
    {
        static const std::type_info& type() noexcept
        {
            return typeid(T);
        }


        static void destroy(storage_union& storage) noexcept
        {
            reinterpret_cast<T*>(&storage.stack)->~T();
        }


        static void copy(const storage_union& src, storage_union& dest)
        {
            new (&dest.stack) T(reinterpret_cast<const T&>(src.stack));
        }


        static void move(storage_union& src, storage_union& dest) noexcept
        {
            /**
             * One of the conditions for using vtable_stack is a nothrow move constructor,
             * so this move constructor will never throw a exception.
             */
            new (&dest.stack) T(std::move(reinterpret_cast<T&>(src.stack)));
            destroy(src);
        }


        static void swap(storage_union& lhs, storage_union& rhs) noexcept
        {
            storage_union tmp_storage;
            move(rhs, tmp_storage);
            move(lhs, rhs);
            move(tmp_storage, lhs);
        }
    };


    /**
     * Whether the type T must be dynamically allocated or can be stored on the stack.
     */
    template<typename T>
    struct requires_allocation :
        std::integral_constant<bool, !(std::is_nothrow_move_constructible<T>::value // N4562 6.3/3 [any.class]
                                       && sizeof(T) <= sizeof(storage_union::stack)
                                       && std::alignment_of<T>::value <= std::alignment_of<storage_union::stack_storage_t>::value)>
    { };


    /**
     * Returns the pointer to the vtable of the type T.
     */
    template<typename T>
    static vtable_type* vtable_for_type()
    {
        using VTableType = typename std::conditional<requires_allocation<T>::value, vtable_dynamic<T>, vtable_stack<T>>::type;
        static vtable_type table = { VTableType::type, VTableType::destroy, VTableType::copy, VTableType::move, VTableType::swap };
        return &table;
    }


protected:
    template<typename T>
    friend const T* any_cast(const any* operand) noexcept;


    template<typename T>
    friend T* any_cast(any* operand) noexcept;


    /**
     * Same effect as is_same(this->type(), t);
     */
    bool is_typed(const std::type_info& t) const
    {
        return is_same(this->type(), t);
    }


    /**
     * Checks if two type infos are the same.
     * If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the
     * type infos, otherwise does an actual comparision. Checking addresses is
     * only a valid approach when there's no interaction with outside sources
     * (other shared libraries and such).
     */
    static bool is_same(const std::type_info& a, const std::type_info& b)
    {
#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE
        return &a == &b;
#else
        return a == b;
#endif
    }


    /**
     * Casts (with no type_info checks) the storage pointer as const T*.
     */
    template<typename T>
    const T* cast() const noexcept
    {
        return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<const T*>(storage.dynamic) : reinterpret_cast<const T*>(&storage.stack);
    }


    /**
     * Casts (with no type_info checks) the storage pointer as T*.
     */
    template<typename T>
    T* cast() noexcept
    {
        return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<T*>(storage.dynamic) : reinterpret_cast<T*>(&storage.stack);
    }


private:
    storage_union storage; // On offset(0) so no padding for align

    vtable_type* vtable;


    template<typename ValueType, typename T>
    typename std::enable_if<requires_allocation<T>::value>::type do_construct(ValueType&& value)
    {
        storage.dynamic = new T(std::forward<ValueType>(value));
    }


    template<typename ValueType, typename T>
    typename std::enable_if<!requires_allocation<T>::value>::type do_construct(ValueType&& value)
    {
        new (&storage.stack) T(std::forward<ValueType>(value));
    }


    /**
     * Chooses between stack and dynamic allocation for the type decay_t<ValueType>,
     * assigns the correct vtable, and constructs the object on our storage.
     */
    template<typename ValueType>
    void construct(ValueType&& value)
    {
        using T = typename std::decay<ValueType>::type;

        this->vtable = vtable_for_type<T>();

        do_construct<ValueType,T>(std::forward<ValueType>(value));
    }
};


namespace detail
{
    template<typename ValueType>
    inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::true_type)
    {
        return std::move(*p);
    }


    template<typename ValueType>
    inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::false_type)
    {
        return *p;
    }
}


/**
 * Performs *any_cast<add_const_t<remove_reference_t<ValueType>>>(&operand), or throws bad_any_cast on failure.
 */
template<typename ValueType>
inline ValueType any_cast(const any& operand)
{
    auto p = any_cast<typename std::add_const<typename std::remove_reference<ValueType>::type>::type>(&operand);
    if(p == nullptr) throw bad_any_cast();
    return *p;
}


/**
 * Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure.
 */
template<typename ValueType>
inline ValueType any_cast(any& operand)
{
    auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
    if(p == nullptr) throw bad_any_cast();
    return *p;
}


/**
 * If ANY_IMPL_ANYCAST_MOVEABLE is not defined, does as N4562 specifies:
 *     Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure.
 *
 * If ANY_IMPL_ANYCAST_MOVEABLE is defined, does as LWG Defect 2509 specifies [1]:
 *     If ValueType is MoveConstructible and isn't a lvalue reference, performs
 *     std::move(*any_cast<remove_reference_t<ValueType>>(&operand)), otherwise
 *     *any_cast<remove_reference_t<ValueType>>(&operand).
 *     Throws bad_any_cast on failure.
 *
 *     [1] https://cplusplus.github.io/LWG/lwg-active.html#2509
 */
template<typename ValueType>
inline ValueType any_cast(any&& operand)
{
#ifdef ANY_IMPL_ANY_CAST_MOVEABLE
    using can_move = std::integral_constant<bool, std::is_move_constructible<ValueType>::value && !std::is_lvalue_reference<ValueType>::value>;
#else
    using can_move = std::false_type;
#endif

    auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
    if(p == nullptr) throw bad_any_cast();
    return detail::any_cast_move_if_true<ValueType>(p, can_move());
}


/**
 * If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
 * contained by operand, otherwise nullptr.
 */
template<typename T>
inline const T* any_cast(const any* operand) noexcept
{
    if(operand == nullptr || !operand->is_typed(typeid(T)))
        return nullptr;
    else
        return operand->cast<T>();
}


/**
 * If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
 * contained by operand, otherwise nullptr.
 */
template<typename T>
inline T* any_cast(any* operand) noexcept
{
    if(operand == nullptr || !operand->is_typed(typeid(T)))
        return nullptr;
    else
        return operand->cast<T>();
}


inline void swap(any& lhs, any& rhs) noexcept
{
    lhs.swap(rhs);
}

}

#endif /* ANY_H */