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

/** \file BufferQueue_test.cpp */

#define LOG_NDEBUG 0
#define LOG_TAG "BufferQueue_test"

#ifdef ANDROID
#include <utils/Log.h>
#else
#define LOGV printf
#endif

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "SLES/OpenSLES.h"
#include "SLES/OpenSLESUT.h"
#include <gtest/gtest.h>

typedef struct {
    short left;
    short right;
} stereo;

// volume of sine wave in range 0.0 to 1.0
static float gVolume = 1.0f;

// 1 second of stereo audio at 44.1 kHz
static stereo stereoBuffer1[44100 * 1];
static const SLuint32 invalidNumBuffers[] = { 0, 0xFFFFFFFF, 0x80000000, 0x10002, 0x102,
        0x101, 0x100 };
static const SLuint32 validNumBuffers[] = { 1, 2, 3, 4, 5, 6, 7, 8, 255 };

//-----------------------------------------------------------------
/* Checks for error. If any errors exit the application! */
void CheckErr(SLresult res) {
    if (SL_RESULT_SUCCESS != res) {
        const char *str = slesutResultToString(res);
        if (NULL == str)
            str = "unknown";
        fprintf(stderr, "CheckErr failure: %s (0x%lx), exiting\n", str, res);
        //Fail the test case
        FAIL();
    }
}

static const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
static const SLboolean flags[1] = { SL_BOOLEAN_TRUE };
static const SLInterfaceID ids_mutesolo[2] = { SL_IID_BUFFERQUEUE, SL_IID_MUTESOLO };
static const SLboolean flags_mutesolo[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
static const SLInterfaceID ids_seek[2] = { SL_IID_BUFFERQUEUE, SL_IID_SEEK };
static const SLboolean flags_seek[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };

// The fixture for testing class BufferQueue
class TestBufferQueue: public ::testing::Test {
public:
    SLresult res;
    SLObjectItf outputmixObject;
    SLObjectItf engineObject;

    SLDataSource audiosrc;
    SLDataSink audiosnk;
    SLDataFormat_PCM pcm;
    SLDataLocator_OutputMix locator_outputmix;
    SLDataLocator_BufferQueue locator_bufferqueue;
    SLBufferQueueItf playerBufferQueue;
    SLBufferQueueState bufferqueueState;
    SLPlayItf playerPlay;
    SLObjectItf playerObject;
    SLEngineItf engineEngine;
    SLuint32 playerState;

protected:
    TestBufferQueue() {
    }

    virtual ~TestBufferQueue() {

    }

    /*Test setup*/
    virtual void SetUp() {

        // create engine
        res = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
        CheckErr(res);
        res = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        CheckErr(res);
        res = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
        CheckErr(res);

        // create output mix
        res = (*engineEngine)->CreateOutputMix(engineEngine, &outputmixObject, 0, NULL, NULL);
        CheckErr(res);
        res = (*outputmixObject)->Realize(outputmixObject, SL_BOOLEAN_FALSE);
        CheckErr(res);

        locator_bufferqueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
        locator_bufferqueue.numBuffers = 0;
        locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
        locator_outputmix.outputMix = outputmixObject;

        pcm.formatType = SL_DATAFORMAT_PCM;
        pcm.numChannels = 2;
        pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
        pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
        pcm.containerSize = 16;
        pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
        pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;

        audiosrc.pLocator = &locator_bufferqueue;
        audiosrc.pFormat = &pcm;
        audiosnk.pLocator = &locator_outputmix;
        audiosnk.pFormat = NULL;

        // initialize the test tone to be a sine sweep from 441 Hz to 882 Hz
        unsigned nframes = sizeof(stereoBuffer1) / sizeof(stereoBuffer1[0]);
        float nframes_ = (float) nframes;
        SLuint32 i;
        for (i = 0; i < nframes; ++i) {
            float i_ = (float) i;
            float pcm_ = sin((i_ * (1.0f + 0.5f * (i_ / nframes_)) * 0.01 * M_PI * 2.0));
            int pcm = (int) (pcm_ * 32766.0 * gVolume);
            ASSERT_TRUE(-32768 <= pcm && pcm <= 32767) << "pcm out of bound " << pcm;
            stereoBuffer1[i].left = pcm;
            stereoBuffer1[nframes - 1 - i].right = pcm;
        }
    }

    virtual void TearDown() {
        // Clean up the mixer and the engine
        // (must be done in that order, and after player destroyed)
        if (outputmixObject){
            (*outputmixObject)->Destroy(outputmixObject);
            outputmixObject = NULL;
        }
        if (engineObject){
            (*engineObject)->Destroy(engineObject);
            engineObject = NULL;
        }
    }

    void DestroyPlayer() {
        if (playerObject){
            //printf("destroy player\n");
            (*playerObject)->Destroy(playerObject);
            playerObject = NULL;
        }
    }

    /* Test case for creating audio player with various invalid values for numBuffers*/
    void InvalidBuffer() {

        for (unsigned i = 0; i < sizeof(invalidNumBuffers) / sizeof(invalidNumBuffers[0]); ++i) {
            SLuint32 numBuffers = invalidNumBuffers[i];

            locator_bufferqueue.numBuffers = numBuffers;
            //printf("create audio player - invalid\n");
            SLresult result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject,
                                                            &audiosrc, &audiosnk, 1, ids, flags);
            ASSERT_EQ(SL_RESULT_PARAMETER_INVALID, result);
            ASSERT_EQ(NULL, playerObject);

        }
    }

    /*Prepare the buffer*/
    void PrepareValidBuffer(SLuint32 numBuffers) {

        locator_bufferqueue.numBuffers = numBuffers;
        //printf("create audio player - valid\n");
        res = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audiosrc, &audiosnk,
                                                1, ids, flags);
        CheckErr(res);
        res = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
        CheckErr(res);
        // get the play interface
        res = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
        CheckErr(res);
        // verify that player is initially stopped
        res = (*playerPlay)->GetPlayState(playerPlay, &playerState);
        CheckErr(res);
        ASSERT_EQ(SL_PLAYSTATE_STOPPED, playerState);

        // get the buffer queue interface
        res = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
        CheckErr(res);

        // verify that buffer queue is initially empty
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.count);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.playIndex);
    }

    void EnqueueMaxBuffer(SLuint32 numBuffers) {
        SLuint32 j;

        for (j = 0; j < numBuffers; ++j) {
            res = (*playerBufferQueue)->Enqueue(playerBufferQueue, "test", 4);
            CheckErr(res);
            // verify that each buffer is enqueued properly and increments the buffer count
            res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
            CheckErr(res);
            ASSERT_EQ(j + 1, bufferqueueState.count);
            ASSERT_EQ((SLuint32) 0, bufferqueueState.playIndex);
        }
    }

    void EnqueueExtraBuffer(SLuint32 numBuffers) {
        // enqueue one more buffer and make sure it fails
        res = (*playerBufferQueue)->Enqueue(playerBufferQueue, "test", 4);
        ASSERT_EQ(SL_RESULT_BUFFER_INSUFFICIENT, res);
        // verify that the failed enqueue did not affect the buffer count
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ(numBuffers, bufferqueueState.count);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.playIndex);
    }

    void SetPlayerState(SLuint32 state) {
        res = (*playerPlay)->SetPlayState(playerPlay, state);
        CheckErr(res);
        //verify the state can set correctly
        GetPlayerState(state);
    }

    void GetPlayerState(SLuint32 state) {
        res = (*playerPlay)->GetPlayState(playerPlay, &playerState);
        CheckErr(res);
        ASSERT_EQ(state, playerState);
    }

    void ClearQueue() {
        // now clear the buffer queue
        res = (*playerBufferQueue)->Clear(playerBufferQueue);
        CheckErr(res);
        // make sure the clear works
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.count);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.playIndex);
    }

    void CheckBufferCount(SLuint32 ExpectedCount, SLuint32 ExpectedPlayIndex) {
        // changing the play state should not affect the buffer count
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ(ExpectedCount, bufferqueueState.count);
        ASSERT_EQ(ExpectedPlayIndex, bufferqueueState.playIndex);
    }

    void PlayBufferQueue() {
        // enqueue a buffer
        res = (*playerBufferQueue)->Enqueue(playerBufferQueue, stereoBuffer1,
            sizeof(stereoBuffer1));
        CheckErr(res);
        // set play state to playing
        res = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
        CheckErr(res);
        // state should be playing immediately after enqueue
        res = (*playerPlay)->GetPlayState(playerPlay, &playerState);
        CheckErr(res);
        ASSERT_EQ(SL_PLAYSTATE_PLAYING, playerState);
        // buffer should still be on the queue
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ((SLuint32) 1, bufferqueueState.count);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.playIndex);
        //LOGV("Before 1.5 sec");
        // wait 1.5 seconds
        usleep(1500000);
        //LOGV("After 1.5 sec");
        // state should still be playing
        res = (*playerPlay)->GetPlayState(playerPlay, &playerState);
        //LOGV("GetPlayState");
        CheckErr(res);
        ASSERT_EQ(SL_PLAYSTATE_PLAYING, playerState);
        // buffer should be removed from the queue
        res = (*playerBufferQueue)->GetState(playerBufferQueue, &bufferqueueState);
        CheckErr(res);
        ASSERT_EQ((SLuint32) 0, bufferqueueState.count);
        ASSERT_EQ((SLuint32) 1, bufferqueueState.playIndex);
        //LOGV("TestEnd");
    }
};

TEST_F(TestBufferQueue, testInvalidBuffer){
    //LOGV("Test Fixture: InvalidBuffer");
    InvalidBuffer();
}

TEST_F(TestBufferQueue, testMuteSolo) {
    // create audio player with buffer queue data source in stereo PCM format and ask for mute solo
    locator_bufferqueue.numBuffers = 1;
    SLresult result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audiosrc,
            &audiosnk, 2, ids_mutesolo, flags_mutesolo);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    ASSERT_TRUE(NULL != playerObject);
    DestroyPlayer();
    // create audio player with buffer queue data source in mono PCM format and ask for mute solo
    pcm.numChannels = 1;
    pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audiosrc, &audiosnk,
            2, ids_mutesolo, flags_mutesolo);
    ASSERT_EQ(SL_RESULT_FEATURE_UNSUPPORTED, result);
    ASSERT_EQ(NULL, playerObject);
    DestroyPlayer();
}

TEST_F(TestBufferQueue, testSeek) {
    // can create audio player with buffer queue data source and ask for seek
    locator_bufferqueue.numBuffers = 1;
    SLresult result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject,
                                                    &audiosrc, &audiosnk, 2, ids_seek, flags_seek);
    ASSERT_EQ(SL_RESULT_FEATURE_UNSUPPORTED, result);
    ASSERT_EQ(NULL, playerObject);
    DestroyPlayer();
}

TEST_F(TestBufferQueue, testValidBuffer) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testEnqueueMaxBuffer) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        EnqueueMaxBuffer(numBuffers);
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testEnqueueExtraBuffer) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        EnqueueMaxBuffer(numBuffers);
        EnqueueExtraBuffer(numBuffers);
        GetPlayerState(SL_PLAYSTATE_STOPPED);
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testEnqueueAtStopped) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        SetPlayerState(SL_PLAYSTATE_STOPPED);
        EnqueueMaxBuffer(numBuffers);
        CheckBufferCount(numBuffers, (SLuint32) 0);
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testEnqueueAtPaused) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        SetPlayerState(SL_PLAYSTATE_PAUSED);
        EnqueueMaxBuffer(numBuffers);
        CheckBufferCount(numBuffers, (SLuint32) 0);
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testClearQueue) {
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        EnqueueMaxBuffer(numBuffers);
        ClearQueue();
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testStateTransitionEmptyQueue) {
    static const SLuint32 newStates[] = {
        SL_PLAYSTATE_PAUSED,    // paused -> paused
        SL_PLAYSTATE_STOPPED,   // paused -> stopped
        SL_PLAYSTATE_PAUSED,    // stopped -> paused
        SL_PLAYSTATE_PLAYING,   // paused -> playing
        SL_PLAYSTATE_PLAYING,   // playing -> playing
        SL_PLAYSTATE_STOPPED,   // playing -> stopped
        SL_PLAYSTATE_STOPPED,   // stopped -> stopped
        SL_PLAYSTATE_PLAYING,   // stopped -> playing
        SL_PLAYSTATE_PAUSED     // playing -> paused
    };

    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        SLuint32 j;

        PrepareValidBuffer(numBuffers);
        /* Set initial state to paused*/
        SetPlayerState(SL_PLAYSTATE_PAUSED);

        for (j = 0; j < sizeof(newStates) / sizeof(newStates[0]); ++j) {
            SetPlayerState(newStates[j]);
            CheckBufferCount((SLuint32) 0, (SLuint32) 0);
        }
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testStateTransitionNonEmptyQueue) {
    static const SLuint32 newStates[] = {
        SL_PLAYSTATE_PAUSED,    // paused -> paused
        SL_PLAYSTATE_STOPPED,   // paused -> stopped
        SL_PLAYSTATE_STOPPED,   // stopped -> stopped
        SL_PLAYSTATE_PAUSED     // stopped -> paused
    };

    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        SLuint32 j;

        /* Prepare the player */
        PrepareValidBuffer(numBuffers);
        EnqueueMaxBuffer(numBuffers);
        SetPlayerState(SL_PLAYSTATE_PAUSED);

        for (j = 0; j < sizeof(newStates) / sizeof(newStates[0]); ++j) {
            SetPlayerState(newStates[j]);
            CheckBufferCount(numBuffers, (SLuint32) 0);
        }
        DestroyPlayer();
    }
}

TEST_F(TestBufferQueue, testStatePlayBuffer){
    for (unsigned i = 0; i < sizeof(validNumBuffers) / sizeof(validNumBuffers[0]); ++i) {
        SLuint32 numBuffers = validNumBuffers[i];
        PrepareValidBuffer(numBuffers);
        PlayBufferQueue();
        DestroyPlayer();
    }
}

int main(int argc, char **argv) {
    testing::InitGoogleTest(&argc, argv);
#if 1   // temporary workaround if hardware volume control is not working
    const char *VOLUME = getenv("BufferQueue_test_VOLUME");
    if (NULL != VOLUME) {
        float volume = atof(VOLUME);
        if (volume >= 0.0f && volume <= 1.0f) {
            gVolume = volume;
        }
    }
#endif
    return RUN_ALL_TESTS();
}