/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#define HILOG_TAG "RingBuffer"

#include <linux/perf_event.h>

#include "ring_buffer.h"

namespace OHOS {
namespace Developtools {
namespace HiPerf {
RingBuffer::RingBuffer(size_t size) : size_(size)
{
    if (size > 0) {
        buf_ = std::make_unique<uint8_t[]>(size);
    }
}

RingBuffer::~RingBuffer() {}

// get size of the writable space
size_t RingBuffer::GetFreeSize() const
{
    return size_ - (head_.load(std::memory_order_relaxed) - tail_.load(std::memory_order_relaxed));
}

uint8_t *RingBuffer::AllocForWrite(size_t writeSize)
{
    size_t writeHead = head_.load(std::memory_order_relaxed);
    size_t readHead = tail_.load(std::memory_order_acquire);
    size_t writePos = writeHead % size_;
    size_t readPos = readHead % size_;
    writeSize_ = writeSize;
    if (writePos < readPos) {
        // |---writePos<---writeSize--->readPos---|
        if (writePos + writeSize > readPos) {
            return nullptr;
        }
    } else if (writePos == readPos and writeHead != readHead) {
        // writePos catch up with readPos, but buffer is full
        return nullptr;
    } else {
        // two cases: 1, writePos catch up with readPos, but buffer is empty
        //            2, |---readPos---writePos<---writeSize--->|
        if (writePos + writeSize > size_) {
            // no enough space at the end
            if (readPos < writeSize) {
                return nullptr;
            }
            // wrap to the start, set mark byte
            buf_.get()[writePos] = MARGIN_BYTE;
            writeSize_ += (size_ - writePos);
            writePos = 0;
        }
    }

    return buf_.get() + writePos;
}

void RingBuffer::EndWrite()
{
    size_t head = head_.load(std::memory_order_relaxed);
    head += writeSize_;
    head_.store(head, std::memory_order_release);
}

uint8_t *RingBuffer::GetReadData()
{
    size_t writeHead = head_.load(std::memory_order_acquire);
    size_t readHead = tail_.load(std::memory_order_relaxed);
    if (writeHead == readHead) {
        return nullptr;
    }

    readSize_ = 0;
    size_t writePos = writeHead % size_;
    size_t readPos = readHead % size_;
    if (writePos <= readPos) {
        // |<---data2--->writePos---readPos<---data1--->|
        if (buf_.get()[readPos] == MARGIN_BYTE) {
            if (writePos == 0) {
                return nullptr;
            }
            readSize_ = (size_ - readPos);
            readPos = 0;
        }
    }
    // else |---readPos<---data--->writePos---|
    perf_event_header *header = reinterpret_cast<perf_event_header *>(buf_.get() + readPos);
    readSize_ += header->size;
    return buf_.get() + readPos;
}

void RingBuffer::EndRead()
{
    size_t tail = tail_.load(std::memory_order_relaxed);
    tail += readSize_;
    tail_.store(tail, std::memory_order_release);
}
} // namespace HiPerf
} // namespace Developtools
} // namespace OHOS