/****************************************************************************
**
** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of Qt 3D Studio.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QT3DRENDER_DRAGON_IMMUTABLE_P_H
#define QT3DRENDER_DRAGON_IMMUTABLE_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists for the convenience
// of other Qt classes.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <functional>
#include <sstream>
#include <memory>
#include <type_traits>
#include <QDebug>

// Needed for MinGW
#if defined(Q_OS_WIN)
# if defined(Q_CC_GNU)
#  include <intrin.h>
# endif
#endif

#include <Qt3DRender/qt3drender_global.h>

QT_BEGIN_NAMESPACE
namespace Qt3DRender {
namespace Dragon {

// TODO Remove when std::in_place_t is introduced in C++17
struct InPlaceT {
    explicit InPlaceT() = default;
};
constexpr InPlaceT InPlace {};

namespace helper {
template<class T>
using decay_t = typename std::decay<T>::type;
template<bool B, class T=void>
using enable_if_t = typename std::enable_if<B,T>::type;
}

template <typename T>
struct Immutable
{
    template<typename U>
    struct is_derived
            : public std::integral_constant<bool,
            std::is_base_of<T, helper::decay_t<U>>::value>
    {};

    Immutable()
        : m_container(std::make_shared<T>())
        , m_copyFunction(&Immutable::make_shared_helper<T>)
    {
    }

    // Copy constructors
    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable(const Immutable<U> &other)
        : m_container(other.m_container)
        , m_copyFunction(&Immutable::make_shared_helper<UU>)
    {}

    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable(const U& arg)
        : m_container(std::make_shared<UU>(arg))
        , m_copyFunction(&Immutable::make_shared_helper<UU>)
    {
    }

    // Move constructors
    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable(Immutable<U> &&other)
        : m_container(std::move(other.m_container))
        , m_copyFunction(&Immutable::make_shared_helper<UU>)
    {
    }

    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable(U &&value)
        : m_container(std::make_shared<UU>(std::forward<U>(value)))
        , m_copyFunction(&Immutable::make_shared_helper<UU>)
    {
    }

    // Copy assignment
    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable &operator=(const Immutable<U> &other)
    {
        m_container = std::make_shared<UU>(*other.m_container);
        m_copyFunction = &Immutable::make_shared_helper<UU>;
        return *this;
    }

    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable &operator=(const U &value)
    {
        m_container = std::make_shared<UU>(value);
        m_copyFunction = &Immutable::make_shared_helper<UU>;
        return *this;
    }

    // Move assignment
    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable &operator=(Immutable<U> &&other)
    {
        // TODO it seems this is never used, check to verify
        m_container = std::make_shared<UU>(std::move<std::shared_ptr<UU>>(*other.m_container));
        m_copyFunction = &Immutable::make_shared_helper<UU>;
        return *this;
    }

    template <typename U,
              typename UU = helper::decay_t<U>,
              typename = helper::enable_if_t<is_derived<U>::value>>
    Immutable &operator=(U &&value)
    {
        m_container = std::make_shared<UU>(std::forward<U>(value));
        m_copyFunction = &Immutable::make_shared_helper<UU>;
        return *this;
    }

    template <typename... Args>
    Immutable(InPlaceT, Args&&... args)
        : m_container(std::make_shared<T>(std::forward<Args>(args)...))
        , m_copyFunction(&Immutable::make_shared_helper<T>)
    {
    }

    template <typename U>
    bool can_convert() const
    {
        auto typed_container = std::dynamic_pointer_cast<const U>(m_container);
        if (typed_container == nullptr) {
            return false;
        }
        return true;
    }

    const T& operator*() const
    {
        return *m_container;
    }

    const T &get() const
    {
        return *m_container;
    }

    template<typename U>
    const Immutable<U> as() const
    {
        auto typed_container = std::dynamic_pointer_cast<U>(m_container);
        Immutable<U> result;
        if (typed_container == nullptr) {
            qDebug() << "ERROR: Cannot cast from " << typeid(T).name() << " to " << typeid(U).name();
            return result;
        }

        // TODO consider adding a private construtor that can set this at construction time
        result.m_container = typed_container;
        return result;
    }

    template<typename U>
    Immutable<U> as()
    {
        if (m_container.use_count() != 1) {
            m_container = m_copyFunction(m_container);
        }

        auto typed_container = std::dynamic_pointer_cast<U>(m_container);
        Immutable<U> result;
        if (typed_container == nullptr) {
            qDebug() << "ERROR: Cannot cast from " << typeid(T).name() << " to " << typeid(U).name();
            return result;
        }

        // TODO consider adding a private construtor that can set this at construction time
        result.m_container = typed_container;
        return result;
    }

    const T* operator->() const
    {
        return m_container.get();
    }

    const T* operator->()
    {
        return m_container.get();
    }

    /*!
     * \brief modified lets you modify the contained object and returns a new Value with the
     * modified object.
     *
     * The container will always be copied.
     *
     * \param modifier
     * \return
     */
    Immutable<T> modified(const std::function<void(T*)> &modifier) const&
    {
        if (m_container == nullptr) {
            return *this;
        }

        // Make a real copy of ourselves.
        // Note: We cannot use result = *this, because this would just increase the ref count.
        Immutable<T> result;
        result.m_container = m_copyFunction(m_container);
        result.m_copyFunction = m_copyFunction;

        // Modify the copy before returning it. This is safe because we currently are the only
        // ones who know about this object.
        modifier(result.m_container.get());

        return result;
    }

    /*!
     * \brief modified lets you modify the contained object and returns a new Value with the
     * modified object.
     *
     * If there are no other references to the container, the modification happens directly on
     * the current object.
     *
     * \param modifier
     * \return
     */
    Immutable<T>&& modified(const std::function<void(T*)> &modifier) &&
    {
        if (m_container == nullptr) {
            return std::move(*this);
        }

        // Even though we're an rvalue, we might not be the only with a reference to the container
        if (m_container.use_count() != 1) {
            m_container = m_copyFunction(m_container);
        }

        // As the only user
        modifier(m_container.get());
        return std::move(*this);
    }

    template<typename U>
    static std::shared_ptr<T> make_shared_helper(std::shared_ptr<T> c)
    {
        auto typed_container = std::static_pointer_cast<U>(c);
        return std::make_shared<U>(*typed_container);
    }

    bool operator==(const Immutable<T> &other) const
    {
        return *m_container == *other.m_container;
    }

    bool operator!=(const Immutable<T> &other) const
    {
        return *m_container != *other.m_container;
    }

private:
    std::shared_ptr<T> m_container;

    // TODO copying this can be expensive, consider adding an inner struct { T, function<T> }
    // that is stored in a shared pointer for faster copying of the Value
    std::function<std::shared_ptr<T>(std::shared_ptr<T>)> m_copyFunction;

    template<typename U>
    friend struct Immutable;
};

}
}
QT_END_NAMESPACE

#endif // QT3DRENDER_DRAGON_IMMUTABLE_P_H
