/*
 * Copyright (C) 2017 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.
 */

// Test various AAudio features including AAudioStream_setBufferSizeInFrames().

#include <condition_variable>
#include <mutex>
#include <stdio.h>

#include <android-base/macros.h>
#include <aaudio/AAudio.h>

#include <gtest/gtest.h>
#include <unistd.h>
#include <thread>

// Callback function that does nothing.
aaudio_data_callback_result_t NoopDataCallbackProc(
        AAudioStream * stream,
        void * /* userData */,
        void *audioData,
        int32_t numFrames
) {
    aaudio_direction_t direction = AAudioStream_getDirection(stream);
    if (direction == AAUDIO_DIRECTION_INPUT) {
        return AAUDIO_CALLBACK_RESULT_CONTINUE;
    }
    // Check to make sure the buffer is initialized to all zeros.
    int channels = AAudioStream_getChannelCount(stream);
    int numSamples = channels * numFrames;
    bool allZeros = true;
    float * const floatData = reinterpret_cast<float *>(audioData);
    for (int i = 0; i < numSamples; i++) {
        allZeros &= (floatData[i] == 0.0f);
        floatData[i] = 0.0f;
    }
    EXPECT_TRUE(allZeros);
    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000;
constexpr int64_t MICROS_PER_MILLISECOND = 1000;

void checkReleaseThenClose(aaudio_performance_mode_t perfMode,
        aaudio_sharing_mode_t sharingMode,
        aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT) {
    AAudioStreamBuilder* aaudioBuilder = nullptr;
    AAudioStream* aaudioStream = nullptr;

    // Use an AAudioStreamBuilder to contain requested parameters.
    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));

    // Request stream properties.
    AAudioStreamBuilder_setDataCallback(aaudioBuilder,
                                        NoopDataCallbackProc,
                                        nullptr);
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
    AAudioStreamBuilder_setSharingMode(aaudioBuilder, sharingMode);
    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
    AAudioStreamBuilder_setFormat(aaudioBuilder, AAUDIO_FORMAT_PCM_FLOAT);

    // Create an AAudioStream using the Builder.
    ASSERT_EQ(AAUDIO_OK,
              AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
    AAudioStreamBuilder_delete(aaudioBuilder);

    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));

    sleep(1);

    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));

    EXPECT_EQ(AAUDIO_OK, AAudioStream_release(aaudioStream));
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(aaudioStream));

    // We should be able to call this again without crashing.
    EXPECT_EQ(AAUDIO_OK, AAudioStream_release(aaudioStream));
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(aaudioStream));

    // We expect these not to crash.
    AAudioStream_setBufferSizeInFrames(aaudioStream, 0);
    AAudioStream_setBufferSizeInFrames(aaudioStream, 99999999);

    // We should NOT be able to start or change a stream after it has been released.
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestStart(aaudioStream));
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(aaudioStream));
    // Pause is only implemented for OUTPUT.
    if (direction == AAUDIO_DIRECTION_OUTPUT) {
        EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE,
                  AAudioStream_requestPause(aaudioStream));
    }
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(aaudioStream));
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestStop(aaudioStream));
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(aaudioStream));

    // Does this crash?
    EXPECT_GT(AAudioStream_getFramesRead(aaudioStream), 0);
    EXPECT_GT(AAudioStream_getFramesWritten(aaudioStream), 0);
    EXPECT_GT(AAudioStream_getFramesPerBurst(aaudioStream), 0);
    EXPECT_GE(AAudioStream_getXRunCount(aaudioStream), 0);
    EXPECT_GT(AAudioStream_getBufferCapacityInFrames(aaudioStream), 0);
    EXPECT_GT(AAudioStream_getBufferSizeInFrames(aaudioStream), 0);

    int64_t timestampFrames = 0;
    int64_t timestampNanos = 0;
    aaudio_result_t result = AAudioStream_getTimestamp(aaudioStream, CLOCK_MONOTONIC,
            &timestampFrames, &timestampNanos);
    EXPECT_TRUE(result == AAUDIO_ERROR_INVALID_STATE || result == AAUDIO_ERROR_UNIMPLEMENTED);

    // Verify Closing State. Does this crash?
    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                         AAUDIO_STREAM_STATE_UNKNOWN, &state,
                                                         500 * NANOS_PER_MILLISECOND));
    EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, state);

    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}

TEST(test_various, aaudio_release_close_none_output) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_SHARING_MODE_SHARED,
            AAUDIO_DIRECTION_OUTPUT);
    // No EXCLUSIVE streams with MODE_NONE.
}

TEST(test_various, aaudio_release_close_none_input) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_SHARING_MODE_SHARED,
            AAUDIO_DIRECTION_INPUT);
    // No EXCLUSIVE streams with MODE_NONE.
}

TEST(test_various, aaudio_release_close_low_shared_output) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_SHARING_MODE_SHARED,
            AAUDIO_DIRECTION_OUTPUT);
}

TEST(test_various, aaudio_release_close_low_shared_input) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_SHARING_MODE_SHARED,
            AAUDIO_DIRECTION_INPUT);
}

TEST(test_various, aaudio_release_close_low_exclusive_output) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_SHARING_MODE_EXCLUSIVE,
            AAUDIO_DIRECTION_OUTPUT);
}

TEST(test_various, aaudio_release_close_low_exclusive_input) {
    checkReleaseThenClose(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_SHARING_MODE_EXCLUSIVE,
            AAUDIO_DIRECTION_INPUT);
}

enum FunctionToCall {
    CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH, CALL_RELEASE
};

void checkStateTransition(aaudio_performance_mode_t perfMode,
                          aaudio_stream_state_t originalState,
                          FunctionToCall functionToCall,
                          aaudio_result_t expectedResult,
                          aaudio_stream_state_t expectedState) {
    AAudioStreamBuilder *aaudioBuilder = nullptr;
    AAudioStream *aaudioStream = nullptr;

    // Use an AAudioStreamBuilder to contain requested parameters.
    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));

    // Request stream properties.
    AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
    AAudioStreamBuilder_setFormat(aaudioBuilder, AAUDIO_FORMAT_PCM_FLOAT);

    // Create an AAudioStream using the Builder.
    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));

    // Verify Open State
    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                         AAUDIO_STREAM_STATE_UNKNOWN, &state,
                                                         1000 * NANOS_PER_MILLISECOND));
    EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);

    // Put stream into desired state.
    aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED;
    if (originalState != AAUDIO_STREAM_STATE_OPEN) {

        ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));

        if (originalState != AAUDIO_STREAM_STATE_STARTING) {

            ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                                 AAUDIO_STREAM_STATE_STARTING,
                                                                 &state,
                                                                 1000 * NANOS_PER_MILLISECOND));
            ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state);

            if (originalState == AAUDIO_STREAM_STATE_STOPPING) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
            } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
                inputState = AAUDIO_STREAM_STATE_STOPPING;
            } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
            } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
                inputState = AAUDIO_STREAM_STATE_PAUSING;
            } else if (originalState == AAUDIO_STREAM_STATE_FLUSHING) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
                // We can only flush() after pause is complete.
                ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                                 AAUDIO_STREAM_STATE_PAUSING,
                                                                 &state,
                                                                 1000 * NANOS_PER_MILLISECOND));
                ASSERT_EQ(AAUDIO_STREAM_STATE_PAUSED, state);
                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestFlush(aaudioStream));
                // That will put the stream into the FLUSHING state.
                // The FLUSHING state will persist until we process functionToCall.
                // That is because the transition to FLUSHED is caused by the callback,
                // or by calling write() or waitForStateChange(). But those will not
                // occur.
            } else if (originalState == AAUDIO_STREAM_STATE_CLOSING) {
                ASSERT_EQ(AAUDIO_OK, AAudioStream_release(aaudioStream));
            }
        }
    }

    // Wait until we get past the transitional state if requested.
    if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
        ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                             inputState,
                                                             &state,
                                                             1000 * NANOS_PER_MILLISECOND));
        ASSERT_EQ(originalState, state);
    }

    aaudio_stream_state_t transitionalState = originalState;
    switch(functionToCall) {
        case FunctionToCall::CALL_START:
            EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream));
            transitionalState = AAUDIO_STREAM_STATE_STARTING;
            break;
        case FunctionToCall::CALL_STOP:
            EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream));
            transitionalState = AAUDIO_STREAM_STATE_STOPPING;
            break;
        case FunctionToCall::CALL_PAUSE:
            EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream));
            transitionalState = AAUDIO_STREAM_STATE_PAUSING;
            break;
        case FunctionToCall::CALL_FLUSH:
            EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
            transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
            break;
        case FunctionToCall::CALL_RELEASE:
            EXPECT_EQ(expectedResult, AAudioStream_release(aaudioStream));
            // Set to UNINITIALIZED so the waitForStateChange() below will
            // will return immediately with the current state.
            transitionalState = AAUDIO_STREAM_STATE_UNINITIALIZED;
            break;
    }

    EXPECT_EQ(AAUDIO_OK,
            AAudioStream_waitForStateChange(aaudioStream,
                    transitionalState,
                    &state,
                    1000 * NANOS_PER_MILLISECOND));

    // We should not change state when a function fails.
    if (expectedResult != AAUDIO_OK) {
        ASSERT_EQ(originalState, expectedState);
    }
    EXPECT_EQ(expectedState, state);
    if (state != expectedState) {
        printf("ERROR - expected %s, actual = %s\n",
                AAudio_convertStreamStateToText(expectedState),
                AAudio_convertStreamStateToText(state));
        fflush(stdout);
    }

    AAudioStream_close(aaudioStream);
    AAudioStreamBuilder_delete(aaudioBuilder);
}

// TODO Use parameterized tests instead of these individual specific tests.

// OPEN =================================================================
TEST(test_various, aaudio_state_lowlat_open_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_START,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_open_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_START,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_lowlat_open_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_none_open_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_lowlat_open_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_none_open_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_lowlat_open_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_FLUSHED);
}

TEST(test_various, aaudio_state_none_open_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_OPEN,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_FLUSHED);
}


// STARTED =================================================================
TEST(test_various, aaudio_state_lowlat_started_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_START,
            AAUDIO_ERROR_INVALID_STATE,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_started_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_START,
            AAUDIO_ERROR_INVALID_STATE,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_lowlat_started_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_none_started_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_lowlat_started_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_none_started_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_lowlat_started_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_ERROR_INVALID_STATE,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_started_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STARTED,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_ERROR_INVALID_STATE,
            AAUDIO_STREAM_STATE_STARTED);
}

// STOPPED =================================================================
TEST(test_various, aaudio_state_lowlat_stopped_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_START,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_stopped_start) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_START,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_lowlat_stopped_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_none_stopped_stop) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_STOP,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_lowlat_stopped_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_none_stopped_pause) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_PAUSE,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_lowlat_stopped_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_FLUSHED);
}

TEST(test_various, aaudio_state_none_stopped_flush) {
    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
            AAUDIO_STREAM_STATE_STOPPED,
            FunctionToCall::CALL_FLUSH,
            AAUDIO_OK,
            AAUDIO_STREAM_STATE_FLUSHED);
}

// PAUSED =================================================================
TEST(test_various, aaudio_state_lowlat_paused_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_START,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_paused_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_START,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_lowlat_paused_stop) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_STOP,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_none_paused_stop) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_STOP,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STOPPED);
}

TEST(test_various, aaudio_state_lowlat_paused_pause) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_PAUSE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_none_paused_pause) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_PAUSE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_PAUSED);
}

TEST(test_various, aaudio_state_lowlat_paused_flush) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_FLUSH,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_FLUSHED);
}

TEST(test_various, aaudio_state_none_paused_flush) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_PAUSED,
        FunctionToCall::CALL_FLUSH,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_FLUSHED);
}

// FLUSHING ================================================================
TEST(test_various, aaudio_state_lowlat_flushing_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_FLUSHING,
        FunctionToCall::CALL_START,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_none_flushing_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_FLUSHING,
        FunctionToCall::CALL_START,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_STARTED);
}

TEST(test_various, aaudio_state_lowlat_flushing_release) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_FLUSHING,
        FunctionToCall::CALL_RELEASE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_none_flushing_release) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_FLUSHING,
        FunctionToCall::CALL_RELEASE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_lowlat_starting_release) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_STARTING,
        FunctionToCall::CALL_RELEASE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_none_starting_release) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_STARTING,
        FunctionToCall::CALL_RELEASE,
        AAUDIO_OK,
        AAUDIO_STREAM_STATE_CLOSING);
}

// CLOSING ================================================================
TEST(test_various, aaudio_state_lowlat_closing_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_CLOSING,
        FunctionToCall::CALL_START,
        AAUDIO_ERROR_INVALID_STATE,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_none_closing_start) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_CLOSING,
        FunctionToCall::CALL_START,
        AAUDIO_ERROR_INVALID_STATE,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_lowlat_closing_stop) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
        AAUDIO_STREAM_STATE_CLOSING,
        FunctionToCall::CALL_STOP,
        AAUDIO_ERROR_INVALID_STATE,
        AAUDIO_STREAM_STATE_CLOSING);
}

TEST(test_various, aaudio_state_none_closing_stop) {
checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
        AAUDIO_STREAM_STATE_CLOSING,
        FunctionToCall::CALL_STOP,
        AAUDIO_ERROR_INVALID_STATE,
        AAUDIO_STREAM_STATE_CLOSING);
}

// ==========================================================================
TEST(test_various, aaudio_set_buffer_size) {

    int32_t bufferCapacity;
    int32_t framesPerBurst = 0;
    int32_t actualSize = 0;

    AAudioStreamBuilder *aaudioBuilder = nullptr;
    AAudioStream *aaudioStream = nullptr;

    // Use an AAudioStreamBuilder to contain requested parameters.
    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));

    // Request stream properties.
    AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

    // Create an AAudioStream using the Builder.
    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));

    // This is the number of frames that are read in one chunk by a DMA controller
    // or a DSP or a mixer.
    framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream);
    bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream);
    printf("          bufferCapacity = %d, remainder = %d\n",
           bufferCapacity, bufferCapacity % framesPerBurst);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 0);
    EXPECT_GE(actualSize, 0); // 0 is legal in R
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 2 * framesPerBurst);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity - 1);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity + 1);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 1234567);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MAX);
    EXPECT_GT(actualSize, framesPerBurst);
    EXPECT_LE(actualSize, bufferCapacity);

    actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MIN);
    EXPECT_GE(actualSize, 0); // 0 is legal in R
    EXPECT_LE(actualSize, bufferCapacity);

    AAudioStream_close(aaudioStream);
    AAudioStreamBuilder_delete(aaudioBuilder);
}

// ************************************************************
// Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works.

// Callback function that counts calls.
aaudio_data_callback_result_t CallbackOnceProc(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames
) {
    (void) stream;
    (void) audioData;
    (void) numFrames;

    std::atomic<int32_t> *callbackCountPtr = (std::atomic<int32_t> *)userData;
    (*callbackCountPtr)++;

    return AAUDIO_CALLBACK_RESULT_STOP;
}

void checkCallbackOnce(aaudio_performance_mode_t perfMode) {

    std::atomic<int32_t>   callbackCount{0};

    AAudioStreamBuilder *aaudioBuilder = nullptr;
    AAudioStream *aaudioStream = nullptr;

    // Use an AAudioStreamBuilder to contain requested parameters.
    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));

    // Request stream properties.
    AAudioStreamBuilder_setDataCallback(aaudioBuilder, CallbackOnceProc, &callbackCount);
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);

    // Create an AAudioStream using the Builder.
    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
    AAudioStreamBuilder_delete(aaudioBuilder);

    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));

    sleep(1); // Give callback a chance to run many times.

    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));

    EXPECT_EQ(1, callbackCount.load()); // should stop after first call

    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}

TEST(test_various, aaudio_callback_once_none) {
    checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_NONE);
}

TEST(test_various, aaudio_callback_once_lowlat) {
    checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
}

void waitForStateChangeToClosingorClosed(AAudioStream **stream, std::atomic<bool>* isReady)
{
    *isReady = true;
    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(*stream,
                                                         AAUDIO_STREAM_STATE_OPEN, &state,
                                                         10000 * NANOS_PER_MILLISECOND));
    if ((state != AAUDIO_STREAM_STATE_CLOSING) && (state != AAUDIO_STREAM_STATE_CLOSED)){
        FAIL() << "ERROR - State not closing or closed. Current state: " <<
                AAudio_convertStreamStateToText(state);
    }
}

void testWaitForStateChangeClose(aaudio_performance_mode_t perfMode) {
    AAudioStreamBuilder *aaudioBuilder = nullptr;
    AAudioStream *aaudioStream = nullptr;

    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));

    // Verify Open State
    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                         AAUDIO_STREAM_STATE_UNKNOWN, &state,
                                                         1000 * NANOS_PER_MILLISECOND));
    EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);

    std::atomic<bool> isWaitThreadReady{false};

    // Spawn a new thread to wait for the state change
    std::thread waitThread (waitForStateChangeToClosingorClosed, &aaudioStream,
                            &isWaitThreadReady);

    // Wait for worker thread to be ready
    while (!isWaitThreadReady) {
        usleep(MICROS_PER_MILLISECOND);
    }
    // Sleep an additional millisecond to make sure waitForAudioThread is called
    usleep(MICROS_PER_MILLISECOND);
    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
    waitThread.join();
}

TEST(test_various, wait_for_state_change_close_none) {
    testWaitForStateChangeClose(AAUDIO_PERFORMANCE_MODE_NONE);
}

TEST(test_various, wait_for_state_change_close_lowlat) {
    testWaitForStateChangeClose(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
}

// ************************************************************
struct WakeUpCallbackData {
    void wakeOther() {
        // signal waiting test to wake up
        {
            std::lock_guard <std::mutex> lock(mutex);
            finished = true;
        }
        conditionVariable.notify_one();
    }

    void waitForFinished() {
        std::unique_lock <std::mutex> aLock(mutex);
        conditionVariable.wait(aLock, [this] { return finished; });
    }

    // For signalling foreground test when callback finished
    std::mutex              mutex;
    std::condition_variable conditionVariable;
    bool                    finished = false;
};

// Test to make sure we cannot call recursively into the system from a callback.
struct DangerousData : public WakeUpCallbackData {
    aaudio_result_t resultStart = AAUDIO_OK;
    aaudio_result_t resultStop = AAUDIO_OK;
    aaudio_result_t resultPause = AAUDIO_OK;
    aaudio_result_t resultFlush = AAUDIO_OK;
    aaudio_result_t resultClose = AAUDIO_OK;
};

// Callback function that tries to call back into the stream.
aaudio_data_callback_result_t DangerousDataCallbackProc(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    (void) audioData;
    (void) numFrames;

    DangerousData *data = (DangerousData *)userData;
    data->resultStart = AAudioStream_requestStart(stream);
    data->resultStop = AAudioStream_requestStop(stream);
    data->resultPause = AAudioStream_requestPause(stream);
    data->resultFlush = AAudioStream_requestFlush(stream);
    data->resultClose = AAudioStream_close(stream);

    data->wakeOther();

    return AAUDIO_CALLBACK_RESULT_STOP;
}

//int main() { // To fix Android Studio formatting when editing.
void checkDangerousCallback(aaudio_performance_mode_t perfMode) {
    DangerousData        dangerousData;
    AAudioStreamBuilder *aaudioBuilder = nullptr;
    AAudioStream        *aaudioStream = nullptr;

    // Use an AAudioStreamBuilder to contain requested parameters.
    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));

    // Request stream properties.
    AAudioStreamBuilder_setDataCallback(aaudioBuilder, DangerousDataCallbackProc, &dangerousData);
    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);

    // Create an AAudioStream using the Builder.
    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
    AAudioStreamBuilder_delete(aaudioBuilder);

    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));

    dangerousData.waitForFinished();

    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));

    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStart);
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStop);
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultPause);
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultFlush);
    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultClose);

    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}

//int main() { // To fix Android Studio formatting when editing.

TEST(test_various, aaudio_callback_blockers_none) {
    checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_NONE);
}

TEST(test_various, aaudio_callback_blockers_lowlat) {
    checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
}
