#include "Buffer.hpp"

IHLBuffer::~IHLBuffer() {
    if (locked() && m_resource)
        sendRelease();
}

void IHLBuffer::sendRelease() {
    m_resource->sendRelease();
    m_syncReleasers.clear();
}

void IHLBuffer::lock() {
    m_locks++;
}

void IHLBuffer::unlock() {
    m_locks--;

    ASSERT(m_locks >= 0);

    if (m_locks == 0)
        sendRelease();
}

bool IHLBuffer::locked() {
    return m_locks > 0;
}

void IHLBuffer::onBackendRelease(const std::function<void()>& fn) {
    if (m_hlEvents.backendRelease) {
        if (m_backendReleaseQueuedFn)
            m_backendReleaseQueuedFn();
        Log::logger->log(Log::DEBUG, "backendRelease emitted early");
    }

    m_backendReleaseQueuedFn = fn;

    m_hlEvents.backendRelease = events.backendRelease.listen([this] {
        if (m_backendReleaseQueuedFn)
            m_backendReleaseQueuedFn();
        m_backendReleaseQueuedFn = nullptr;
        m_hlEvents.backendRelease.reset();
    });
}

void IHLBuffer::addReleasePoint(CDRMSyncPointState& point) {
    ASSERT(locked());
    if (point)
        m_syncReleasers.emplace_back(point.createSyncRelease());
}

CHLBufferReference::CHLBufferReference() : m_buffer(nullptr) {
    ;
}

CHLBufferReference::CHLBufferReference(const CHLBufferReference& other) : m_buffer(other.m_buffer) {
    if (m_buffer)
        m_buffer->lock();
}

CHLBufferReference::CHLBufferReference(CHLBufferReference&& other) noexcept : m_buffer(std::move(other.m_buffer)) {
    ;
}

CHLBufferReference::CHLBufferReference(SP<IHLBuffer> buffer_) : m_buffer(buffer_) {
    if (m_buffer)
        m_buffer->lock();
}

CHLBufferReference::~CHLBufferReference() {
    if (m_buffer)
        m_buffer->unlock();
}

CHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& other) {
    if (m_buffer == other.m_buffer)
        return *this; // same buffer, do nothing

    if (other.m_buffer)
        other.m_buffer->lock();
    if (m_buffer)
        m_buffer->unlock();
    m_buffer = other.m_buffer;
    return *this;
}

CHLBufferReference& CHLBufferReference::operator=(CHLBufferReference&& other) {
    if (this != &other) {
        if (m_buffer)
            m_buffer->unlock();
        m_buffer       = other.m_buffer;
        other.m_buffer = nullptr;
    }
    return *this;
}

bool CHLBufferReference::operator==(const CHLBufferReference& other) const {
    return m_buffer == other.m_buffer;
}

bool CHLBufferReference::operator==(const SP<IHLBuffer>& other) const {
    return m_buffer == other;
}

bool CHLBufferReference::operator==(const SP<Aquamarine::IBuffer>& other) const {
    return m_buffer == other;
}

SP<IHLBuffer> CHLBufferReference::operator->() const {
    return m_buffer;
}

CHLBufferReference::operator bool() const {
    return m_buffer;
}

void CHLBufferReference::drop() {
    if (!m_buffer)
        return;

    m_buffer->m_locks--;
    ASSERT(m_buffer->m_locks >= 0);

    m_buffer = nullptr;
}
