// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qwltextureorphanage_p.h"

#include <QOpenGLContext>
#include <QOpenGLTexture>
#include <QDebug>
#include <QtTypeTraits>
#include <QMutexLocker>

QT_BEGIN_NAMESPACE

Q_LOGGING_CATEGORY(qLcWTO, "qt.waylandcompositor.orphanage")

Q_GLOBAL_STATIC(QtWayland::QWaylandTextureOrphanage, inst)

namespace QtWayland {

QWaylandTextureOrphanage::~QWaylandTextureOrphanage()
{
    QMutexLocker locker(&m_containerLock);
    if (!m_orphanedTextures.isEmpty()) {
        qCWarning(qLcWTO) << Q_FUNC_INFO << "m_orphanedTextures container isn't empty! content:"
                          << m_orphanedTextures;
    }
}

QWaylandTextureOrphanage *QWaylandTextureOrphanage::instance()
{
    return inst;
}

void QWaylandTextureOrphanage::admitTexture(QOpenGLTexture *tex, QOpenGLContext *ctx)
{
    qCDebug(qLcWTO) << Q_FUNC_INFO << "got a texture (" << (void *)tex
                    << ") ready to be deleted! It's ctx:" << ctx;

    {
        QMutexLocker locker(&m_containerLock);
        m_orphanedTextures.insert(ctx, tex);
    }

    connect(ctx, &QOpenGLContext::aboutToBeDestroyed, this,
            [this, ctx]() { this->onContextAboutToBeDestroyed(ctx); },
            Qt::ConnectionType(Qt::DirectConnection));
}

void QWaylandTextureOrphanage::deleteTextures()
{
    QOpenGLContext *cCtx = QOpenGLContext::currentContext();

    if (cCtx == nullptr) {
        qCWarning(qLcWTO) << Q_FUNC_INFO << "cannot delete textures without current OpenGL context";
        return;
    }

    {
        QMutexLocker locker(&m_containerLock);

        const auto contexts = m_orphanedTextures.keys();
        for (QOpenGLContext *aCtx : contexts) {
            if (QOpenGLContext::areSharing(cCtx, aCtx)) {

                qCDebug(qLcWTO) << Q_FUNC_INFO << "currentContext (" << cCtx
                                << ") and ctx of orphane(s) (" << aCtx
                                << ") are shared! => deleteTexturesByContext";

                deleteTexturesByContext(aCtx);
            }
        }
    }
}

void QWaylandTextureOrphanage::onContextAboutToBeDestroyed(QOpenGLContext *ctx)
{
    Q_ASSERT(ctx != nullptr);

    qCDebug(qLcWTO) << Q_FUNC_INFO << " ctx (" << ctx
                    << ") fired aboutToBeDestroyed => deleteTexturesByContext(ctx)";

    {
        QMutexLocker locker(&m_containerLock);
        deleteTexturesByContext(ctx);
    }
}

void QWaylandTextureOrphanage::deleteTexturesByContext(QOpenGLContext *ctx)
{
    // NOTE: We are (by class-internal design) locked (m_containerLock)
    // when we enter this function!
    // If not (e.g.: someone changes something in/around this class),
    // then in a debug-build we will fail below:
    Q_ASSERT(!m_containerLock.tryLock());

    const QList<QOpenGLTexture *> texturesToDelete = m_orphanedTextures.values(ctx);
    m_orphanedTextures.remove(ctx);

    for (QOpenGLTexture *tex : texturesToDelete) {
        delete tex;
        qCDebug(qLcWTO) << Q_FUNC_INFO << " texture (" << (void *)tex << ") got deleted";
    }
}

} // namespace QtWayland

QT_END_NAMESPACE

#include "moc_qwltextureorphanage_p.cpp"
