/****************************************************************************
**
** 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_JOBS_COMMON_H
#define QT3DRENDER_DRAGON_JOBS_COMMON_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/dragontextureimage_p.h>
#include <private/dragonvaluecontainer_p.h>

QT_BEGIN_NAMESPACE

namespace Qt3DRender {
namespace Dragon {

template<typename Target, typename Reference>
Target synchronizeKeys(Target target, const Reference &reference)
{
    for (const auto &key : reference.created()) {
        target[key];
    }
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

template<typename Target, typename Reference, typename Callback>
Target synchronizeKeys(Target target, const Reference &reference, Callback callback)
{
    for (const auto &key : reference.dirtyOrNew()) {
        if (target.contains(key))
            target.markDirty(key);
        const auto &item = reference[key];
        target.insert(key, callback(key, item));
    }
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

template<typename Target, typename Reference, typename Callback>
Target synchronizeNew(Target target, const Reference &reference, Callback callback)
{
    for (const auto &key : reference.created()) {
//        Q_ASSERT(!target.contains(key));
        const auto &item = reference[key];
        target.insert(key, callback(key, item));
    }
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

template<typename Target, typename Reference, typename Callback>
Target synchronizeDirty(Target target, const Reference &reference, Callback callback)
{
    for (const auto &key : reference.dirty()) {
        Q_ASSERT(target.contains(key));
        const auto &sourceItem = reference[key];
        target[key] = callback(key, std::move(target[key]), sourceItem);
    }
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

/*!
  \internal
 * Synchronize target container to hold elements of reference.
 * Use compare function to look for existing items in target to create new (caching)
 * and create function to create new elements.
 */
template<typename Target, typename Reference, typename Compare, typename CreateFunction>
Target synchronizeKeys(Target target,
                       const Reference &reference,
                       Compare compare,
                       CreateFunction create)
{
    for (const auto &id : reference.dirtyOrNew()) {
        if (target.contains(id))
            target.markDirty(id);

        const auto &item = reference[id];
        bool foundExisting = false;
        for (const auto &cached : target) {
            if (compare(*cached, item)) {
                target[id] = cached;
                foundExisting = true;
                break;
            }
        }
        if (!foundExisting)
            target[id] = create(id, *item);
    }
    // Remove unused
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

template<typename Target, typename Reference, typename Compare, typename CreateFunction>
Target synchronizeNew(Target target,
                      const Reference &reference,
                      Compare compare,
                      CreateFunction create)
{
    for (const auto &id : reference.created()) {
//        Q_ASSERT(!target.contains(id)); // this is a bit strict
        const auto &item = reference[id];
        bool foundExisting = false;
        for (const auto &cached : target) {
            if (compare(*cached, item)) {
                target[id] = cached;
                foundExisting = true;
                break;
            }
        }
        if (!foundExisting)
            target[id] = create(id, *item);
    }
    return target;
}

template<typename Target, typename Reference, typename Compare, typename CreateFunction>
Target synchronizeDirty(Target target,
                          const Reference &reference,
                          Compare compare,
                          CreateFunction create)
{
    for (const auto &id : reference.dirty()) {
        Q_ASSERT(target.contains(id)); // this is a bit strict
        target.markDirty(id);
        const auto &item = reference[id];
        bool foundExisting = false;
        for (const auto &cached : target) {
            if (compare(*cached, item)) {
                target[id] = cached;
                foundExisting = true;
                break;
            }
        }
        if (!foundExisting)
            target[id] = create(id, *item);
    }
    return target;
}

template<typename Target, typename Reference, typename Callback>
Target rebuildAll(Target target, const Reference &reference, Callback callback)
{
    for (const auto &key : reference.keys()) {
        //        if (target.contains(key))
        const auto &item = reference[key];
        target[key] = callback(key, item);
        target.markDirty(key);
    }
    for (const auto &key : reference.removed()) {
        target.remove(key);
    }
    return target;
}

} // namespace Dragon
} // namespace Qt3DRender

QT_END_NAMESPACE

#endif // QT3DRENDER_DRAGON_JOBS_COMMON_H
