/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.
 */

#include <iostream>

#include <gtest/gtest.h>
#include <stdlib.h>

#include "fifo/FifoBuffer.h"
#include "fifo/FifoController.h"

using android::fifo_frames_t;
using android::FifoController;
using android::FifoBuffer;
using android::WrappingBuffer;

//void foo() {
TEST(test_fifi_controller, fifo_indices) {
    // Values are arbitrary primes designed to trigger edge cases.
    constexpr int capacity = 83;
    constexpr int threshold = 47;
    FifoController   fifoController(capacity, threshold);
    ASSERT_EQ(capacity, fifoController.getCapacity());
    ASSERT_EQ(threshold, fifoController.getThreshold());

    ASSERT_EQ(0, fifoController.getReadCounter());
    ASSERT_EQ(0, fifoController.getWriteCounter());
    ASSERT_EQ(0, fifoController.getFullFramesAvailable());
    ASSERT_EQ(threshold, fifoController.getEmptyFramesAvailable());

    // Pretend to write some data.
    constexpr int advance1 = 23;
    fifoController.advanceWriteIndex(advance1);
    int advanced = advance1;
    ASSERT_EQ(0, fifoController.getReadCounter());
    ASSERT_EQ(0, fifoController.getReadIndex());
    ASSERT_EQ(advanced, fifoController.getWriteCounter());
    ASSERT_EQ(advanced, fifoController.getWriteIndex());
    ASSERT_EQ(advanced, fifoController.getFullFramesAvailable());
    ASSERT_EQ(threshold - advanced, fifoController.getEmptyFramesAvailable());

    // Pretend to read the data.
    fifoController.advanceReadIndex(advance1);
    ASSERT_EQ(advanced, fifoController.getReadCounter());
    ASSERT_EQ(advanced, fifoController.getReadIndex());
    ASSERT_EQ(advanced, fifoController.getWriteCounter());
    ASSERT_EQ(advanced, fifoController.getWriteIndex());
    ASSERT_EQ(0, fifoController.getFullFramesAvailable());
    ASSERT_EQ(threshold, fifoController.getEmptyFramesAvailable());

    // Write past end of buffer.
    constexpr int advance2 = 13 + capacity - advance1;
    fifoController.advanceWriteIndex(advance2);
    advanced += advance2;
    ASSERT_EQ(advance1, fifoController.getReadCounter());
    ASSERT_EQ(advance1, fifoController.getReadIndex());
    ASSERT_EQ(advanced, fifoController.getWriteCounter());
    ASSERT_EQ(advanced - capacity, fifoController.getWriteIndex());
    ASSERT_EQ(advance2, fifoController.getFullFramesAvailable());
    ASSERT_EQ(threshold - advance2, fifoController.getEmptyFramesAvailable());
}

// TODO consider using a template for other data types.
class TestFifoBuffer {
public:
    explicit TestFifoBuffer(fifo_frames_t capacity, fifo_frames_t threshold = 0)
        : mFifoBuffer(sizeof(int16_t), capacity) {
        // For reading and writing.
        mData = new int16_t[capacity];
        if (threshold <= 0) {
            threshold = capacity;
        }
        mFifoBuffer.setThreshold(threshold);
        mThreshold = threshold;
    }

    void checkMisc() {
        ASSERT_EQ((int32_t)(2 * sizeof(int16_t)), mFifoBuffer.convertFramesToBytes(2));
        ASSERT_EQ(mThreshold, mFifoBuffer.getThreshold());
    }

    // Verify that the available frames in each part add up correctly.
    void checkWrappingBuffer() {
        WrappingBuffer wrappingBuffer;
        fifo_frames_t framesAvailable =
                mFifoBuffer.getFifoControllerBase()->getEmptyFramesAvailable();
        fifo_frames_t wrapAvailable = mFifoBuffer.getEmptyRoomAvailable(&wrappingBuffer);
        EXPECT_EQ(framesAvailable, wrapAvailable);
        fifo_frames_t bothAvailable = wrappingBuffer.numFrames[0] + wrappingBuffer.numFrames[1];
        EXPECT_EQ(framesAvailable, bothAvailable);

        framesAvailable =
                mFifoBuffer.getFifoControllerBase()->getFullFramesAvailable();
        wrapAvailable = mFifoBuffer.getFullDataAvailable(&wrappingBuffer);
        EXPECT_EQ(framesAvailable, wrapAvailable);
        bothAvailable = wrappingBuffer.numFrames[0] + wrappingBuffer.numFrames[1];
        EXPECT_EQ(framesAvailable, bothAvailable);
    }

    // Write data but do not overflow.
    void writeData(fifo_frames_t numFrames) {
        fifo_frames_t framesAvailable =
                mFifoBuffer.getFifoControllerBase()->getEmptyFramesAvailable();
        fifo_frames_t framesToWrite = std::min(framesAvailable, numFrames);
        for (int i = 0; i < framesToWrite; i++) {
            mData[i] = mNextWriteIndex++;
        }
        fifo_frames_t actual = mFifoBuffer.write(mData, framesToWrite);
        ASSERT_EQ(framesToWrite, actual);
    }

    // Read data but do not underflow.
    void verifyData(fifo_frames_t numFrames) {
        fifo_frames_t framesAvailable =
                mFifoBuffer.getFifoControllerBase()->getFullFramesAvailable();
        fifo_frames_t framesToRead = std::min(framesAvailable, numFrames);
        fifo_frames_t actual = mFifoBuffer.read(mData, framesToRead);
        ASSERT_EQ(framesToRead, actual);
        for (int i = 0; i < framesToRead; i++) {
            ASSERT_EQ(mNextVerifyIndex++, mData[i]);
        }
    }

    // Wrap around the end of the buffer.
    void checkWrappingWriteRead() {
        constexpr int frames1 = 43;
        constexpr int frames2 = 15;

        writeData(frames1);
        checkWrappingBuffer();
        verifyData(frames1);
        checkWrappingBuffer();

        writeData(frames2);
        checkWrappingBuffer();
        verifyData(frames2);
        checkWrappingBuffer();
    }

    // Write and Read a specific amount of data.
    void checkWriteRead() {
        const fifo_frames_t capacity = mFifoBuffer.getBufferCapacityInFrames();
        // Wrap around with the smaller region in the second half.
        const int frames1 = capacity - 4;
        const int frames2 = 7; // arbitrary, small
        writeData(frames1);
        verifyData(frames1);
        writeData(frames2);
        verifyData(frames2);
    }

    // Write and Read a specific amount of data.
    void checkWriteReadSmallLarge() {
        const fifo_frames_t capacity = mFifoBuffer.getBufferCapacityInFrames();
        // Wrap around with the larger region in the second half.
        const int frames1 = capacity - 4;
        const int frames2 = capacity - 9; // arbitrary, large
        writeData(frames1);
        verifyData(frames1);
        writeData(frames2);
        verifyData(frames2);
    }

    // Randomly read or write up to the maximum amount of data.
    void checkRandomWriteRead() {
        for (int i = 0; i < 20; i++) {
            fifo_frames_t framesEmpty =
                    mFifoBuffer.getFifoControllerBase()->getEmptyFramesAvailable();
            fifo_frames_t numFrames = (fifo_frames_t)(drand48() * framesEmpty);
            writeData(numFrames);

            fifo_frames_t framesFull =
                    mFifoBuffer.getFifoControllerBase()->getFullFramesAvailable();
            numFrames = (fifo_frames_t)(drand48() * framesFull);
            verifyData(numFrames);
        }
    }

    FifoBuffer     mFifoBuffer;
    int16_t       *mData;
    fifo_frames_t  mNextWriteIndex = 0;
    fifo_frames_t  mNextVerifyIndex = 0;
    fifo_frames_t  mThreshold;
};

TEST(test_fifo_buffer, fifo_read_write) {
    constexpr int capacity = 51; // arbitrary
    TestFifoBuffer tester(capacity);
    tester.checkMisc();
    tester.checkWriteRead();
}

TEST(test_fifo_buffer, fifo_wrapping_read_write) {
    constexpr int capacity = 59; // arbitrary, a little bigger this time
    TestFifoBuffer tester(capacity);
    tester.checkWrappingWriteRead();
}

TEST(test_fifo_buffer, fifo_read_write_small_large) {
    constexpr int capacity = 51; // arbitrary
    TestFifoBuffer tester(capacity);
    tester.checkWriteReadSmallLarge();
}

TEST(test_fifo_buffer, fifo_random_read_write) {
    constexpr int capacity = 51; // arbitrary
    TestFifoBuffer tester(capacity);
    tester.checkRandomWriteRead();
}

TEST(test_fifo_buffer, fifo_random_threshold) {
    constexpr int capacity = 67; // arbitrary
    constexpr int threshold = 37; // arbitrary
    TestFifoBuffer tester(capacity, threshold);
    tester.checkRandomWriteRead();
}