/****************************************************************************
**
** 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_MUTABLE_P_H
#define QT3DRENDER_DRAGON_MUTABLE_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>

QT_BEGIN_NAMESPACE

/*!
 * The Mutable class is a reference-counting container that invalidates copies when modified.
 *
 * This class was designed to handle types that are not suitable to be stored in an Immutable
 * container, such as OpenGL buffers, textures, and shaders.
 * These types are expensive to copy, are referenced in multiple places, and need to be modified.
 * However, we do not want objects that can be modified in one place, only to have the changes
 * suddenly affect other parts of our code.
 * To make sure changes need to be properly propagated throughout the code,
 * copies of Mutable objects are invalidated once changed.
 *
 * TODO: Consider making the Mutable object non-copyable and introduce a MutableReference that is
 * read-only.
 */
template <typename T>
struct Mutable
{
    Mutable()
        : container(std::make_shared<T>())
    {
    }

    // Copy constructors
    Mutable(const Mutable<T> &other)
        : container(other.container)
    {}

    Mutable(const T& arg)
        : container(std::make_shared<T>(arg))
    {
    }

    // Move constructors
    Mutable(Mutable<T> &&other)
        : container(std::move(other.container))
    {
    }

    Mutable(T &&value)
        : container(std::make_shared<T>(std::forward<T>(value)))
    {
    }

    // Copy assignment
    Mutable &operator=(const Mutable<T> &other)
    {
        container = other.container;
        return *this;
    }

    Mutable &operator=(const T &value)
    {
        container = std::make_shared<T>(value);
        return *this;
    }

    // Move assignment
//    ImmutableResource &operator=(ImmutableResource<T> &&other)
//    {
//        container = std::make_shared<T>(std::move<std::shared_ptr<T>>(*other.container));
//        return *this;
//    }

    Mutable &operator=(T &&value)
    {
        container = std::make_shared<T>(std::forward<T>(value));
        return *this;
    }

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

    // TODO consider using a reference
    Mutable<T> take(std::function<T(T)> modifyFunction)
    {
        Q_ASSERT(*m_valid == true);
        Q_ASSERT(container != nullptr);

        Mutable<T> result;
        result.container = container;

        // Make all others know that they have been invalidated
        *m_valid = false;
        container.reset();

        *(result.container) = modifyFunction(std::move(*(result.container)));

        return result;
    }

    const T *operator->() const
    {
        // Note: These asserts are fine as debug-only as long as this is an internal class.
        // However, an exception is also in release builds if this class ever gets promoted
        // to a public Qt class. Otherwise, this class serves little purpose.
        Q_ASSERT(*m_valid == true);
        Q_ASSERT(container != nullptr);
        return container.get();
    }

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

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

private:
    std::shared_ptr<bool> m_valid = std::make_shared<bool>(true);
    std::shared_ptr<T> container;

    template<typename U>
    friend struct Mutable;
};

QT_END_NAMESPACE

#endif // QT3DRENDER_DRAGON_MUTABLE_P_H
