/****************************************************************************
**
** 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 TRACKINGCHANGESCONTAINER_H
#define TRACKINGCHANGESCONTAINER_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 <Qt3DCore/QNodeId>
#include <private/dragonimmutable_p.h>

QT_BEGIN_NAMESPACE
namespace Qt3DRender {
namespace Dragon {

struct Change
{
    enum class Action
    {
        Created,
        Modified,
        Removed
    };

    enum class DirtyInfo
    {
        Other,
        Tree
    };

    Change() = default;

    Change(Action action_, Qt3DCore::QNodeId id_)
        : action(action_)
        , id(id_)
        , info(DirtyInfo::Other)
    {
    }

    Change(Action action_, Qt3DCore::QNodeId id_, DirtyInfo info_)
        : action(action_)
        , id(id_)
        , info(info_)
    {
        Q_ASSERT_X(action == Action::Modified, "Dragon::Change",
                   "ERROR: DirtyInfo provided for non-dirty change");
    }

    Action action = Action::Created;
    Qt3DCore::QNodeId id;
    DirtyInfo info = DirtyInfo::Other;
};

inline bool operator ==(const Change &a, const Change &b)
{
    return a.action == b.action && a.id == b.id;
};

// TODO consider if this is really needed
class AbstractContainer
{
public:
    virtual ~AbstractContainer() {}
    virtual void markDirty(const Qt3DCore::QNodeId &id) = 0;
    virtual void markDirty(const Qt3DCore::QNodeId &id, Change::DirtyInfo info) = 0;
};

// TODO make sure creating and removing is counted properly - consider adding a changelist instead
// TODO consider adding a key-value iterator
template<typename T>
class TrackingChangesContainer : public AbstractContainer
{
public:
    using Key = Qt3DCore::QNodeId;
    using DataContainer = QHash<Key, T>;
    using value_type = T;

    using iterator = typename DataContainer::iterator;
    using const_iterator = typename DataContainer::const_iterator;

    void markDirty(const Key &id) override
    {
        m_changes.push_back(Change{Change::Action::Modified, id, Change::DirtyInfo::Other});
    }

    void markDirty(const Key &id, Change::DirtyInfo info) override
    {
        m_changes.push_back(Change{Change::Action::Modified, id, info});
    }

    QList<Key> keys() const
    {
        return m_container.keys();
    }

    QList<T> values() const
    {
        return m_container.values();
    }

    bool contains(const Key &key) const
    {
        return m_container.contains(key);
    }

    // TODO consider adding a function that asserts that the container does not contain the key
    const T operator[](const Key &key) const
    {
        return m_container[key];
    }

    T& operator[](const Key &key)
    {
        if (!m_container.contains(key))
            m_changes.push_back(Change{Change::Action::Created, key});
        return m_container[key];
    }

    iterator begin()
    {
        return m_container.begin();
    }

    iterator end()
    {
        return m_container.end();
    }

    const_iterator begin() const
    {
        return m_container.begin();
    }

    const_iterator end() const
    {
        return m_container.end();
    }

    bool anythingDirty() const
    {
        return !m_changes.isEmpty();
    }

    bool hasCreated(const Key &key) const
    {
        return m_changes.contains(Change{Change::Action::Created, key});
    }

    bool hasDirty(const Key &key) const
    {
        return m_changes.contains(Change{Change::Action::Modified, key});
    }

    bool hasRemoved(const Key &key) const
    {
        return m_changes.contains(Change{Change::Action::Removed, key});
    }

    bool hasDirtyOrCreated(const Key &key) const
    {
        return hasDirty(key) || hasCreated(key);
    }

    void insert(const Key & key, T value)
    {
        m_changes.push_back(Change{Change::Action::Created, key});
        m_container.insert(key, value);
    }

    T get(const Key & key)
    {
        Q_ASSERT(m_container.contains(key));
        return m_container.value(key);
    }

    T take(const Key & key)
    {
        m_changes.push_back(Change{Change::Action::Removed, key});
        return m_container.take(key);
    }

    size_t size() const
    {
        return m_container.size();
    }

    void remove(const Key &key)
    {
        take(key);
    }

    void reset()
    {
        m_changes.clear();
    }

    QVector<Change> changes() const
    {
        return m_changes;
    }

    void clear()
    {
        for (const auto &key : m_container.keys()) {
            m_changes.push_back(Change{Change::Action::Removed, key});
        }
        m_container.clear();
    }

private:
    DataContainer m_container;
    QVector<Change> m_changes;
};

template<typename T, typename Key>
class CacheContainer : public TrackingChangesContainer<T>
{
public:
    using key_type = Key;
    using Cache = QHash<Key, T>;
    Cache cache;
};

}
}
QT_END_NAMESPACE

#endif // TRACKINGCHANGESCONTAINER_H
