/****************************************************************************
**
** 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 {

// TODO consider if this is really needed
class AbstractContainer
{
public:
    class DirtyInfo {
    public:
        DirtyInfo() = default;
        virtual ~DirtyInfo() = default;
    };

    virtual ~AbstractContainer() {}
    virtual void markDirty(const Qt3DCore::QNodeId &id) = 0;
    virtual void markDirty(const Qt3DCore::QNodeId &id, const Immutable<DirtyInfo> &dirtyInfo) = 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_dirty.insert(id, DirtyInfo{});
    }

    void markDirty(const Key &id, const Immutable<DirtyInfo> &dirtyInfo) override
    {
        m_dirty.insert(id, dirtyInfo);
    }

    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_created.push_back(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();
    }

    QVector<Key> created() const
    {
        return m_created;
    }

    QVector<Key> removed() const
    {
        return m_removed;
    }

    QSet<Key> dirty() const
    {
        return m_dirty.keys().toSet();
    }

    // TODO reconsider the use of QList...
    QList<Key> dirtyOrNew() const
    {
        return m_dirty.keys() + m_created.toList();
    }

    bool anythingDirty() const
    {
        return !m_dirty.isEmpty()
                || !m_created.isEmpty()
                || !m_removed.isEmpty();
    }

    void insert(const Key & key, T value)
    {
        m_created.push_back(key);
        m_container.insert(key, value);
    }

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

    T take(const Key & key)
    {
        m_removed.push_back(key);
        return m_container.take(key);
    }

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

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

    void reset()
    {
        m_created.clear();
        m_removed.clear();
        m_dirty.clear();
    }

    void clear()
    {
        m_removed = m_removed + m_container.keys();
        m_container.clear();
    }

    // TODO rename to dirty
    const QHash<Key, Immutable<DirtyInfo>> &dirtyInfo()
    {
        return m_dirty;
    }

private:
    DataContainer m_container;
    QHash<Key, Immutable<DirtyInfo>> m_dirty;
    QVector<Key> m_created;
    QVector<Key> m_removed;
};

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
