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

//#define USE_LOG SLAndroidLogLevel_Verbose

#include "sles_allinclusive.h"
#include "android/BufferQueueSource.h"

#include <media/stagefright/MediaDebug.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

namespace android {


const SLuint32 BufferQueueSource::kItemProcessed[NB_BUFFEREVENT_ITEM_FIELDS] = {
        SL_ANDROID_ITEMKEY_BUFFERQUEUEEVENT, // item key
        sizeof(SLuint32),                    // item size
        SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED // item data
};


BufferQueueSource::BufferQueueSource(const void* user, void *context,  const void *caller) :
          mAndroidBufferQueueSource(NULL),
          mStreamToBqOffset(0),
          mEosReached(false)
{
    if (NULL != user) {
        mAndroidBufferQueueSource = &((CAudioPlayer*)user)->mAndroidBufferQueue;
    } else {
        SL_LOGE("Can't create BufferQueueSource with NULL user");
    }

}


BufferQueueSource::~BufferQueueSource() {
    SL_LOGD("BufferQueueSource::~BufferQueueSource");
}


//--------------------------------------------------------------------------
status_t BufferQueueSource::initCheck() const {
    return mAndroidBufferQueueSource != NULL ? OK : NO_INIT;
}

ssize_t BufferQueueSource::readAt(off64_t offset, void *data, size_t size) {
    SL_LOGD("BufferQueueSource::readAt(offset=%lld, data=%p, size=%d)", offset, data, size);

    if (mEosReached) {
        // once EOS has been received from the buffer queue, you can't read anymore
        return 0;
    }

    ssize_t readSize = 0;
    slAndroidBufferQueueCallback callback = NULL;
    void* pBufferContext, *pBufferData, *callbackPContext = NULL;
    AdvancedBufferHeader *oldFront = NULL;
    uint32_t dataSize, dataUsed = 0;

    interface_lock_exclusive(mAndroidBufferQueueSource);

    if (mAndroidBufferQueueSource->mState.count == 0) {
        readSize = 0;
    } else {
        assert(mAndroidBufferQueueSource->mFront != mAndroidBufferQueueSource->mRear);

        oldFront = mAndroidBufferQueueSource->mFront;
        AdvancedBufferHeader *newFront = &oldFront[1];

        // where to read from
        char *pSrc = NULL;
        // can this read operation cause us to call the buffer queue callback
        // (either because there was a command with no data, or all the data has been consumed)
        bool queueCallbackCandidate = false;

        // consume events when starting to read data from a buffer for the first time
        if (oldFront->mDataSizeConsumed == 0) {
            if (oldFront->mItems.mAdtsCmdData.mAdtsCmdCode & ANDROID_ADTSEVENT_EOS) {
                mEosReached = true;
                // EOS has no associated data
                queueCallbackCandidate = true;
            }
            oldFront->mItems.mAdtsCmdData.mAdtsCmdCode = ANDROID_ADTSEVENT_NONE;
        }

        //assert(mStreamToBqOffset <= offset);
        CHECK(mStreamToBqOffset <= offset);

        if (offset + size <= mStreamToBqOffset + oldFront->mDataSize) {
            pSrc = ((char*)oldFront->mDataBuffer) + (offset - mStreamToBqOffset);

            if (offset - mStreamToBqOffset + size == oldFront->mDataSize) {
                // consumed buffer entirely
                oldFront->mDataSizeConsumed = oldFront->mDataSize;
                mStreamToBqOffset += oldFront->mDataSize;
                queueCallbackCandidate = true;

                // move queue to next buffer
                if (newFront == &mAndroidBufferQueueSource->
                        mBufferArray[mAndroidBufferQueueSource->mNumBuffers + 1]) {
                    // reached the end, circle back
                    newFront = mAndroidBufferQueueSource->mBufferArray;
                }
                mAndroidBufferQueueSource->mFront = newFront;
                // update the queue state
                mAndroidBufferQueueSource->mState.count--;
                mAndroidBufferQueueSource->mState.index++;
                SL_LOGV("BufferQueueSource moving to next buffer");
            }
        }

        // consume data: copy to given destination
        if (NULL != pSrc) {
            memcpy(data, pSrc, size);
            readSize = size;
        } else {
            readSize = 0;
        }

        if (queueCallbackCandidate) {
            // data has been consumed, and the buffer queue state has been updated
            // we will notify the client if applicable
            if (mAndroidBufferQueueSource->mCallbackEventsMask &
                    SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED) {
                callback = mAndroidBufferQueueSource->mCallback;
                // save callback data while under lock
                callbackPContext = mAndroidBufferQueueSource->mContext;
                pBufferContext = (void *)oldFront->mBufferContext;
                pBufferData    = (void *)oldFront->mDataBuffer;
                dataSize       = oldFront->mDataSize;
                dataUsed       = oldFront->mDataSizeConsumed;
            }
        }
    }

    interface_unlock_exclusive(mAndroidBufferQueueSource);

    // notify client
    if (NULL != callback) {
        SLresult result = (*callback)(&mAndroidBufferQueueSource->mItf, callbackPContext,
                pBufferContext, pBufferData, dataSize, dataUsed,
                // no messages during playback other than marking the buffer as processed
                (const SLAndroidBufferItem*)(&kItemProcessed) /* pItems */,
                NB_BUFFEREVENT_ITEM_FIELDS * sizeof(SLuint32) /* itemsLength */ );
        if (SL_RESULT_SUCCESS != result) {
            // Reserved for future use
            SL_LOGW("Unsuccessful result %d returned from AndroidBufferQueueCallback", result);
        }
    }

    return readSize;
}


status_t BufferQueueSource::getSize(off64_t *size) {
    SL_LOGD("BufferQueueSource::getSize()");
    // we're streaming, we don't know how much there is
    *size = 0;
    return OK;
}

}  // namespace android
