// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

#include <platform/private/doublequeue.h>
#include <platform/mem.h>

#include <stddef.h>

#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
// IAR does not support mutex
#include <mutex>
#endif

namespace Qul {
namespace Platform {
namespace Private {

#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
// The queue implementation uses two buckets, one for reading and one for writing.
// This means reads and writes happen independent of each other with buckets swapped
// occasionally.
// A read- and write-lock is protecting each bucket separately from threads.
//
// std::mutex will only be available and or work, if threading is also available.
std::mutex doubleQueueReadLock;
std::mutex doubleQueueWriteLock;
#endif

DoubleQueue::DoubleQueue(uint32_t capacity, uint32_t messageSize)
    : MessageQueueInterface()
    , m_dataSetIdx(0)
    , m_data_0(capacity, messageSize)
    , m_data_1(capacity, messageSize)
{}

DoubleQueue::~DoubleQueue() {}

MessageQueueStatus DoubleQueue::enqueueOrDiscard(const void *message)
{
#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
    std::lock_guard<std::mutex> guard(doubleQueueWriteLock);
#endif
    DataSet &dataSet = data(!m_dataSetIdx);
    if (dataSet.m_buffer.isFull()) {
        dataSet.m_seenOverrun = true;
        return MessageQueueStatus::MessageDiscarded;
    }
    dataSet.m_buffer.pushBack(message);
    return MessageQueueStatus::Success;
}

MessageQueueStatus DoubleQueue::enqueueOrOverwrite(const void *message)
{
#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
    std::lock_guard<std::mutex> guard(doubleQueueWriteLock);
#endif
    DataSet &dataSet = data(!m_dataSetIdx);
    if (dataSet.m_buffer.isFull()) {
        dataSet.m_seenOverrun = true;
    }
    dataSet.m_buffer.pushBack(message);
    return MessageQueueStatus::Success;
}

MessageQueueStatus DoubleQueue::enqueueOrDiscardFromInterrupt(const void *message)
{
#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
    if (!doubleQueueWriteLock.try_lock()) {
        // Writing end of the queue is already locked.
        // From an interrupt it is not possible to wait for the lock to be released.
        return MessageQueueStatus::MessageDiscarded;
    }
    std::lock_guard<std::mutex> guard(doubleQueueWriteLock, std::adopt_lock);
#endif
    DataSet &dataSet = data(!m_dataSetIdx);
    if (dataSet.m_buffer.isFull()) {
        dataSet.m_seenOverrun = true;
        return MessageQueueStatus::MessageDiscarded;
    }
    dataSet.m_buffer.pushBack(message);
    return MessageQueueStatus::Success;
}

MessageQueueStatus DoubleQueue::enqueueOrOverwriteFromInterrupt(const void *message)
{
#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
    if (!doubleQueueWriteLock.try_lock()) {
        // Writing end of the queue is already locked.
        // From an interrupt it is not possible to wait for the lock to be released.
        return MessageQueueStatus::MessageDiscarded;
    }
    std::lock_guard<std::mutex> guard(doubleQueueWriteLock, std::adopt_lock);
#endif
    DataSet &dataSet = data(!m_dataSetIdx);
    if (dataSet.m_buffer.isFull()) {
        dataSet.m_seenOverrun = true;
    }
    dataSet.m_buffer.pushBack(message);
    return MessageQueueStatus::Success;
}

bool DoubleQueue::isOverrun() const
{
    return constData().m_seenOverrun;
}

void DoubleQueue::clearOverrun()
{
    data().m_seenOverrun = false;
}

MessageQueueStatus DoubleQueue::receive(void *message, int32_t timeout)
{
    // FIXME: What to do with timeout?
    QUL_UNUSED(timeout);

    if (isEmpty())
        return MessageQueueStatus::EmptyQueue;

#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
    std::lock_guard<std::mutex> readGuard(doubleQueueReadLock);
#endif

    if (data().m_buffer.size() == 0) {
#if __cplusplus >= 201103L && _GLIBCXX_HAS_GTHREADS && !defined(__ICCARM__)
        std::lock_guard<std::mutex> writeGuard(doubleQueueWriteLock);
#endif
        data().m_seenOverrun = false;
        // Swapping read and write buckets
        m_dataSetIdx = !m_dataSetIdx;
    }

    data().m_buffer.popFront(message);

    return MessageQueueStatus::Success;
}

bool DoubleQueue::isEmpty() const
{
    // This has to consider both buffers because messages in the writing
    // buffer will not be recognized until the next receieve call.
    return m_data_0.m_buffer.size() == 0 && m_data_1.m_buffer.size() == 0;
}

bool DoubleQueue::discardSupported() const
{
    return true;
}

bool DoubleQueue::overwriteSupported() const
{
    return true;
}

uint32_t DoubleQueue::capacity() const
{
    return m_data_0.m_buffer.capacity();
}

} // namespace Private

MessageQueueInterface *requestQueue(size_t queueCapacity, size_t messageSize)
{
    // Using new is not allowed, so we manually allocate the needed space from
    // heap and then use placement new to properly place the class instance
    // and call its constructor.
    void *queue = qul_malloc(sizeof(Private::DoubleQueue));

    if (!queue)
        return NULL;

    MessageQueueInterface *interface = new (queue) Private::DoubleQueue(queueCapacity, messageSize);
    return interface;
}

void deleteQueue(MessageQueueInterface *queue)
{
    Private::DoubleQueue *dq = static_cast<Private::DoubleQueue *>(queue);
    dq->~DoubleQueue();
    qul_free(dq);
}

size_t maximumQueueMessageSize()
{
    return LONG_MAX;
}

} // namespace Platform
} // namespace Qul
