/****************************************************************************
**
** 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 DRAGONMANAGER_P_H
#define DRAGONMANAGER_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 <Qt3DCore/private/qresourcemanager_p.h>

#include <Qt3DCore/QNodeId>
#include <QtCore/qrefcount.h>

QT_BEGIN_NAMESPACE

namespace Qt3DCore {

template <typename T>
class Handlee
{
public:
    struct Data {
        union {
            quintptr counter;
            Data *nextFree;
        };
    };

    Handlee() = default;

    Handlee(Data *d_)
        : d(d_)
        , counter(d_->counter)
    {
        qDebug() << "Constructing handle";
        if (d)
            refCount = new QBasicAtomicInt(0);
    }

    Handlee(const Handlee &other)
        : d(other.d)
        , counter(other.counter)
        , refCount(other.refCount)
    {
        qDebug() << "Copy constructed";
        if (refCount != nullptr)
            refCount->ref();
    }

    ~Handlee()
    {
        qDebug() << "Destroying handle";
        if (refCount != nullptr) {
            qDebug() << "Ref count" << refCount->load();
            if (refCount->load() == 0)
                delete refCount;
            else
                refCount->deref();
        }
    }

    Handlee &operator=(const Handlee &other)
    {
        qDebug() << "Copy assigned";
        d = other.d;
        counter = other.counter;
        refCount = other.refCount;
        return *this;
    }

    inline const T *operator->() const;
    inline T *operator->();

    inline T *data() const;

    quintptr handle() const { return reinterpret_cast<quintptr>(d); }

    bool isNull() const
    {
        return d == nullptr;
    }

    Data *data_ptr() const { return d; }

    bool operator==(const Handlee &other) const { return d == other.d && counter == other.counter; }
    bool operator!=(const Handlee &other) const { return !operator==(other); }
private:
    Data *d = nullptr;
    quintptr counter = 0;
    QBasicAtomicInt *refCount = nullptr;
};

template<typename T>
class HandleDataa : public Handlee<T>::Data
{
public:
    T data;
};

template<typename T>
inline const T *Handlee<T>::operator->() const
{
    Q_ASSERT(d != nullptr);
    Q_ASSERT(counter == d->counter);
    return (d && counter == d->counter) ? &static_cast<HandleDataa<T> *>(d)->data : nullptr;
}

template<typename T>
inline T *Handlee<T>::operator->()
{
    // Only allow writing to the object behind the handle if we are the only
    // handle referring to the object.
    // This is similar to copy-on-write, but instead of copying, we crash.
    Q_ASSERT(refCount->load() == 1);
    if (refCount->load() > 1)
        qWarning() << "ERROR: QHandle access to operator-> on shared non-const object.";
    return (d && counter == d->counter) ? &static_cast<HandleDataa<T> *>(d)->data : nullptr;
}

// TODO remove or make private
template<typename T>
inline T *Handlee<T>::data() const
{
    return (d && counter == d->counter) ? &static_cast<HandleDataa<T> *>(d)->data : nullptr;
}

template <typename ValueType>
class ResourceManager
{
public:
    using Handle = Handlee<ValueType>;
    using HandleData = HandleDataa<ValueType>;

    using iterator = typename QVector<Handle>::iterator;
    using const_iterator = typename QVector<Handle>::const_iterator;

    ResourceManager()
    {
    }

    ~ResourceManager()
    {
        m_activeHandles.clear();
        deallocateBuckets();
    }

    Handle acquire()
    {
        return allocateResource();
    }

    void release(const Handle &handle)
    {
        remove(handle);
    }

    bool contains(const QNodeId &id) const
    {
        return m_keyToHandleMap.contains(id);
    }

    Handle getOrAcquireHandle(const QNodeId &id)
    {
        Handle handle = m_keyToHandleMap.value(id);
        if (handle.isNull()) {
            // Test that the handle hasn't been set (in the meantime between the read unlock and the write lock)
            Handle &handleToSet = m_keyToHandleMap[id];
            if (handleToSet.isNull()) {
                handleToSet = allocateResource();
            }
            return handleToSet;
        }
        return handle;
    }

    void insert(QNodeId id, ValueType data)
    {
        // TODO ugly, just call the constructor
        Handle handle = getOrAcquireHandle(id);
        ValueType *currentData = handle.operator->();
        *currentData = data;
    }

    Handle lookupHandle(const QNodeId &id)
    {
        return m_keyToHandleMap.value(id);
    }

    ValueType *lookupResource(const QNodeId &id)
    {
        ValueType* ret = nullptr;
        {
            Handle handle = m_keyToHandleMap.value(id);
            if (!handle.isNull())
                ret = handle.operator->();
        }
        return ret;
    }

    ValueType *getOrCreateResource(const QNodeId &id)
    {
        const Handle handle = getOrAcquireHandle(id);
        return handle.data();
    }

    void remove(const QNodeId &id)
    {
        Handle handle = m_keyToHandleMap.take(id);
        if (!handle.isNull())
            remove(handle);
    }

    Handle allocateResource()
    {
        if (!m_freeList)
            allocateBucket();
        typename Handle::Data *d = m_freeList;
//        new (d) HandleData; // TODO May not be aligned!
        m_freeList = m_freeList->nextFree;
        d->counter = m_allocCounter;
        m_allocCounter += 2; // ensure this will never clash with a pointer in nextFree by keeping the lowest bit set
        Handle handle(d);
        m_activeHandles.push_back(handle);
        return handle;
    }

    void remove(const Handle &handle)
    {
        m_activeHandles.removeOne(handle);
        typename Handle::Data *d = handle.data_ptr();
        HandleData *data = static_cast<HandleData*>(d);
//        data->~HandleData();
        d->nextFree = m_freeList;
        m_freeList = d;
        performCleanup(&static_cast<HandleDataa<ValueType> *>(d)->data, Int2Type<QResourceInfo<ValueType>::needsCleanup>());
    }

    void for_each(std::function<void(ValueType*)> f)
    {
        Bucket *b = m_firstBucket;
        while (b) {
            for (int idx = 0; idx < Bucket::NumEntries; ++idx) {
                ValueType *t = &b->data[idx].data;
                f(t);
            }
            b = b->header.next;
        }
    }

    int count() const {
        return m_activeHandles.size();
    }

    QVector<Handle> activeHandles() const {
        return m_activeHandles;
    }

    QList<QNodeId> keys() const
    {
        return m_keyToHandleMap.keys();
    }

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

    const Handle operator[](QNodeId id) const
    {
        const Handle handle = m_keyToHandleMap.value(id);
        return m_keyToHandleMap[id];
    }

//    ValueType* operator[](QNodeId id)
//    {
//        ValueType* ret = nullptr;
//        {
//            Handle handle = m_keyToHandleMap.value(id);
//            if (!handle.isNull())
//                ret = handle.operator->();
//        }
//        return ret;
//    }

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

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

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

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

private:
    struct Bucket
    {
        struct Header
        {
            Bucket *next;
        } header;
        enum {
            Size = 4096,
            DataSize = Size - sizeof(Header),
            NumEntries = DataSize / sizeof(HandleData)
        };
        HandleData data[NumEntries];
//        int8_t data[DataSize];
    };

    Bucket *m_firstBucket = nullptr;
    QVector<Handle> m_activeHandles;
    typename Handle::Data *m_freeList = nullptr;
    int m_allocCounter = 1;

    void allocateBucket()
    {
        // no free handle, allocate a new
        // allocate aligned memory
        Bucket *b = static_cast<Bucket *>(AlignedAllocator::allocate(sizeof(Bucket)));

        // placement new
        new (b) Bucket;

        b->header.next = m_firstBucket;
        m_firstBucket = b;

//        HandleData *data = reinterpret_cast<HandleData*>(b->data);
        for (int i = 0; i < Bucket::NumEntries - 1; ++i) {
//            data[i].nextFree = &data[i + 1];
            b->data[i].nextFree = &b->data[i + 1];
        }
        b->data[Bucket::NumEntries - 1].nextFree = nullptr;
        m_freeList = &b->data[0];
    }

    void deallocateBuckets()
    {
        Bucket *b = m_firstBucket;
        while (b) {
            Bucket *n = b->header.next;
            // Call dtor explicitly
            b->~Bucket();
            // Release aligned memory
            AlignedAllocator::release(b);
            b = n;
        }
    }

    void performCleanup(ValueType *r, Int2Type<true>)
    {
        r->cleanup();
    }

    void performCleanup(ValueType *, Int2Type<false>)
    {}

private:
    QHash<QNodeId, Handle> m_keyToHandleMap;
};

} // Qt3DCore
QT_END_NAMESPACE

#endif // DRAGONMANAGER_P_H
