// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "rhibuffer_p.h"
#include <submissioncontext_p.h>
#include <rhi/qrhi.h>

QT_BEGIN_NAMESPACE

namespace Qt3DRender {

namespace Render {

namespace Rhi {

namespace {
QRhiBuffer::UsageFlags bufferTypeToRhi(RHIBuffer::Type t)
{
    QRhiBuffer::UsageFlags flag{};

    if (t & RHIBuffer::Type::ArrayBuffer ||
        t & RHIBuffer::Type::ShaderStorageBuffer) {
        // We have no easy way to know if a SSBO is used as a VertexBuffer
        // and vice versa, so we set both flags when either type is requested
        flag |= QRhiBuffer::StorageBuffer;
        flag |= QRhiBuffer::VertexBuffer;
    }

    if (t & RHIBuffer::Type::IndexBuffer)
        flag |= QRhiBuffer::IndexBuffer;

    if (t & RHIBuffer::Type::UniformBuffer)
        flag |= QRhiBuffer::UniformBuffer;

    return flag;
}
}

// A UBO is created for each ShaderData Shader Pair
// That means a UBO is unique to a shader/shaderdata

RHIBuffer::RHIBuffer()
    : m_bufferId(Qt3DCore::QNodeId())
    , m_dynamic(true)
{ }

bool RHIBuffer::bind(SubmissionContext *ctx, Type t)
{
    assert(ctx->m_currentUpdates);
    if (this->m_datasToUpload.empty())
        return bool(m_rhiBuffer);

    const auto uploadMethod = m_dynamic ? qOverload<QRhiBuffer *, quint32, QByteArray>(
                                                &QRhiResourceUpdateBatch::updateDynamicBuffer)
                                        : qOverload<QRhiBuffer *, quint32, QByteArray>(
                                                &QRhiResourceUpdateBatch::uploadStaticBuffer);
    if (!m_rhiBuffer) {
        if (m_allocSize <= 0)
            return false;

        const auto kind = m_dynamic ? QRhiBuffer::Dynamic : QRhiBuffer::Static;
        const auto usage = bufferTypeToRhi(t);

        m_rhiBuffer = ctx->rhi()->newBuffer(kind, usage, m_allocSize);
        assert(m_rhiBuffer);

        if (!m_rhiBuffer->create())
            return false;

#if defined(QT_DEBUG)
        {
            // for debug: we set the buffer to zero
            (ctx->m_currentUpdates->*uploadMethod)(m_rhiBuffer, 0, QByteArray(m_allocSize, 0));
        }
#endif
    }
    assert(m_rhiBuffer);

#if defined(QT_DEBUG)
    // RHI does not seem to support using the same buffer with different types
    assert(m_rhiBuffer->usage() == bufferTypeToRhi(t));
#endif

    for (auto &[data, offset] : m_datasToUpload) {
        (ctx->m_currentUpdates->*uploadMethod)(m_rhiBuffer, offset, std::move(data));
    }

    m_datasToUpload.clear();
    return true;
}

void RHIBuffer::destroy()
{
    if (m_rhiBuffer) {
        delete m_rhiBuffer;
        m_rhiBuffer = nullptr;
    }
    destroyOrphaned();
    m_allocSize = 0;
}

void RHIBuffer::destroyOrphaned()
{
    for (QRhiBuffer *oldBuffer : m_buffersToCleanup)
        delete oldBuffer;
    m_buffersToCleanup.clear();
}

// Note: when we orphan, we have to keep the previous QRhiBuffer alive until
// after frame has been submitted. This is because we might have already stored
// updates in the RHI Command Buffers for the buffer that is about to be
// destroyed. We therefore have to wait until command buffer has been submitted
// until we can destroy the buffer.
void RHIBuffer::orphan()
{
    m_datasToUpload.clear();
    if (m_rhiBuffer) {
        m_buffersToCleanup.push_back(m_rhiBuffer);
        m_rhiBuffer = nullptr;
    }
}

void RHIBuffer::allocate(const QByteArray &data, bool dynamic)
{
    // We orphan only if new size is larger than current size
    // Otherwise, we can just reuse current buffer
    if (data.size() > m_allocSize)
        orphan();

    m_datasToUpload.clear();
    m_datasToUpload.push_back({ data, 0 });
    m_allocSize = std::max(m_allocSize, data.size());
    m_dynamic = dynamic;
}

void RHIBuffer::update(const QByteArray &data, int offset)
{
    m_datasToUpload.push_back({ data, offset });
}

QByteArray RHIBuffer::download(SubmissionContext *ctx, uint size)
{
    Q_UNUSED(ctx);
    Q_UNUSED(size);
    RHI_UNIMPLEMENTED;
    //    char *gpu_ptr = ctx->mapBuffer(m_lastTarget, size);
    //    QByteArray data;
    //    if (gpu_ptr != nullptr) {
    //        data.resize(size);
    //        std::copy(gpu_ptr, gpu_ptr+size, data.data());
    //    }
    //    ctx->unmapBuffer(m_lastTarget);
    //    return data;
    return {};
}

void RHIBuffer::cleanup()
{
    m_bufferId = Qt3DCore::QNodeId();
    m_dynamic = true;
    destroy();
}

} // namespace Rhi

} // namespace Render

} // namespace Qt3DRender

QT_END_NAMESPACE
