// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

//
//  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 "qwmf_support_p.h"

#include <mferror.h>

QT_BEGIN_NAMESPACE

namespace QWMF {

HRESULT QByteArrayMFMediaBuffer::CreateInstance(QByteArray data, IMFMediaBuffer **ppBuffer,
                                                bool isReadOnly)
{
    if (!ppBuffer)
        return E_POINTER;

    DWORD size = data.size();

    QByteArrayMFMediaBuffer *pBuffer =
            new (std::nothrow) QByteArrayMFMediaBuffer(std::move(data), isReadOnly);
    if (!pBuffer)
        return E_OUTOFMEMORY;

    pBuffer->SetCurrentLength(size);

    HRESULT hr =
            pBuffer->QueryInterface(__uuidof(IMFMediaBuffer), reinterpret_cast<void **>(ppBuffer));

    pBuffer->Release();
    return hr;
}

HRESULT QByteArrayMFMediaBuffer::CreateInstance(qsizetype capacity, IMFMediaBuffer **ppBuffer)
{
    if (!ppBuffer)
        return E_POINTER;

    QByteArray buffer{ capacity, Qt::Initialization::Uninitialized };
    QByteArrayMFMediaBuffer *pBuffer =
            new (std::nothrow) QByteArrayMFMediaBuffer(std::move(buffer), /*isReadOnly=*/false);
    if (!pBuffer)
        return E_OUTOFMEMORY;

    HRESULT hr =
            pBuffer->QueryInterface(__uuidof(IMFMediaBuffer), reinterpret_cast<void **>(ppBuffer));

    pBuffer->Release();
    return hr;
}

HRESULT QByteArrayMFMediaBuffer::Lock(BYTE **ppbBuffer, DWORD *pcbMaxLength,
                                      DWORD *pcbCurrentLength)
{
    if (!ppbBuffer)
        return E_POINTER;

    if (m_isLocked.test_and_set(std::memory_order_acquire))
        return MF_E_INVALIDREQUEST; // Buffer is already locked.

    if (m_isReadOnly)
        // we assume that the IMFTransform is not working in-place, so we can avoid `detach` here
        *ppbBuffer = const_cast<BYTE *>(reinterpret_cast<const BYTE *>(m_byteArray.constData()));
    else
        *ppbBuffer = reinterpret_cast<BYTE *>(m_byteArray.data());

    if (pcbMaxLength)
        *pcbMaxLength = GetMaxLengthInternal();

    if (pcbCurrentLength)
        *pcbCurrentLength = m_currentLength;

    return S_OK;
}

HRESULT QByteArrayMFMediaBuffer::Unlock()
{
    m_isLocked.clear(std::memory_order_release);
    return S_OK;
}

HRESULT QByteArrayMFMediaBuffer::GetCurrentLength(DWORD *pcbCurrentLength)
{
    if (!pcbCurrentLength)
        return E_POINTER;

    *pcbCurrentLength = m_currentLength;
    return S_OK;
}

HRESULT QByteArrayMFMediaBuffer::SetCurrentLength(DWORD cbCurrentLength)
{
    if (cbCurrentLength > GetMaxLengthInternal())
        return E_INVALIDARG;

    m_currentLength = cbCurrentLength;
    return S_OK;
}

HRESULT QByteArrayMFMediaBuffer::GetMaxLength(DWORD *pcbMaxLength)
{
    if (!pcbMaxLength)
        return E_POINTER;

    *pcbMaxLength = GetMaxLengthInternal();
    return S_OK;
}

QByteArrayMFMediaBuffer::QByteArrayMFMediaBuffer(QByteArray &&data, bool isReadOnly)
    : m_byteArray(std::move(data)), m_isReadOnly(isReadOnly)
{
}

DWORD QByteArrayMFMediaBuffer::GetMaxLengthInternal() const
{
    return static_cast<DWORD>(m_byteArray.size());
}

QByteArray QByteArrayMFMediaBuffer::takeByteArray()
{
    return std::move(m_byteArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

HRESULT QPmrMediaBuffer::CreateInstance(QSpan<const std::byte> data,
                                        std::pmr::memory_resource *resource,
                                        IMFMediaBuffer **ppBuffer)
{
    if (!ppBuffer || !resource)
        return E_POINTER;

    *ppBuffer = nullptr;

    auto *buffer = resource->allocate(sizeof(QPmrMediaBuffer), alignof(QPmrMediaBuffer));
    if (!buffer)
        return E_OUTOFMEMORY;

    QPmrMediaBuffer *pBuffer = new (buffer) QPmrMediaBuffer(data, resource);

    HRESULT hr =
            pBuffer->QueryInterface(__uuidof(IMFMediaBuffer), reinterpret_cast<void **>(ppBuffer));

    pBuffer->Release();

    return hr;
}

HRESULT QPmrMediaBuffer::CreateInstance(qsizetype capacity, std::pmr::memory_resource *resource,
                                        IMFMediaBuffer **ppBuffer)
{
    if (!ppBuffer || !resource)
        return E_POINTER;

    *ppBuffer = nullptr;
    void *buffer = resource->allocate(sizeof(QPmrMediaBuffer), alignof(QPmrMediaBuffer));
    QPmrMediaBuffer *pBuffer = new (buffer) QPmrMediaBuffer(capacity, resource);
    if (!pBuffer)
        return E_OUTOFMEMORY;

    HRESULT hr =
            pBuffer->QueryInterface(__uuidof(IMFMediaBuffer), reinterpret_cast<void **>(ppBuffer));
    pBuffer->Release();
    return hr;
}

ULONG QPmrMediaBuffer::Release()
{
    const LONG referenceCount = m_referenceCount.fetch_sub(1, std::memory_order_release) - 1;
    if (referenceCount == 0) {
        // This acquire fence synchronizes with the release operation in other threads.
        // It ensures that all memory writes made to this object by other threads
        // are visible to this thread before we proceed to delete it.
        std::atomic_thread_fence(std::memory_order_acquire);

        std::pmr::memory_resource *resource = m_memoryResource;

        this->~QPmrMediaBuffer();
        resource->deallocate(this, sizeof(QPmrMediaBuffer), alignof(QPmrMediaBuffer));
    }

    return referenceCount;
}

STDMETHODIMP QPmrMediaBuffer::Lock(BYTE **ppbBuffer, DWORD *pcbMaxLength, DWORD *pcbCurrentLength)
{
    if (!ppbBuffer)
        return E_POINTER;

    if (m_isLocked.test_and_set(std::memory_order_acquire))
        return MF_E_INVALIDREQUEST;

    *ppbBuffer = reinterpret_cast<BYTE *>(m_buffer);

    if (pcbMaxLength)
        *pcbMaxLength = m_maxLength;

    if (pcbCurrentLength)
        *pcbCurrentLength = m_currentLength;

    return S_OK;
}

STDMETHODIMP QPmrMediaBuffer::Unlock()
{
    m_isLocked.clear(std::memory_order_release);
    return S_OK;
}

STDMETHODIMP QPmrMediaBuffer::GetCurrentLength(DWORD *pcbCurrentLength)
{
    if (!pcbCurrentLength)
        return E_POINTER;

    *pcbCurrentLength = m_currentLength;
    return S_OK;
}

STDMETHODIMP QPmrMediaBuffer::SetCurrentLength(DWORD cbCurrentLength)
{
    if (cbCurrentLength > m_maxLength)
        return E_INVALIDARG;

    m_currentLength = cbCurrentLength;
    return S_OK;
}

STDMETHODIMP QPmrMediaBuffer::GetMaxLength(DWORD *pcbMaxLength)
{
    if (!pcbMaxLength)
        return E_POINTER;

    *pcbMaxLength = m_maxLength;
    return S_OK;
}

static constexpr auto mfBufferAlignment = 16;

QPmrMediaBuffer::QPmrMediaBuffer(QSpan<const std::byte> data, std::pmr::memory_resource *resource)
    : QPmrMediaBuffer(data.size(), resource)
{
    m_currentLength = data.size(), std::copy(data.begin(), data.end(), m_buffer);
}

QPmrMediaBuffer::QPmrMediaBuffer(qsizetype capacity, std::pmr::memory_resource *resource)
    : m_memoryResource(resource),
      m_maxLength(DWORD(capacity)),
      m_buffer(static_cast<std::byte *>(resource->allocate(capacity, mfBufferAlignment)))
{
}

QPmrMediaBuffer::~QPmrMediaBuffer()
{
    m_memoryResource->deallocate(m_buffer, m_maxLength, mfBufferAlignment);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

} // namespace QWMF

QT_END_NAMESPACE
