/*
 * 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.
 */

/* AndroidBufferQueue implementation */

//#define USE_LOG SLAndroidLogLevel_Verbose

#include "sles_allinclusive.h"
// for AAC ADTS verification on enqueue:
#include "android/include/AacBqToPcmCbRenderer.h"

/**
 * Determine the state of the audio player or media player associated with a buffer queue.
 *  Note that PLAYSTATE and RECORDSTATE values are equivalent (where PLAYING == RECORDING).
 */

static SLuint32 getAssociatedState(IAndroidBufferQueue *thiz)
{
    SLuint32 state;
    switch (InterfaceToObjectID(thiz)) {
      case XA_OBJECTID_MEDIAPLAYER:
        state = ((CMediaPlayer *) thiz->mThis)->mPlay.mState;
        break;
      case SL_OBJECTID_AUDIOPLAYER:
        state = ((CAudioPlayer *) thiz->mThis)->mPlay.mState;
        break;
      default:
        // unreachable, but just in case we will assume it is stopped
        assert(SL_BOOLEAN_FALSE);
        state = SL_PLAYSTATE_STOPPED;
        break;
    }
    return state;
}


/**
 * parse and set the items associated with the given buffer, based on the buffer type,
 * which determines the set of authorized items and format
 */
static SLresult setItems(SLuint32 dataLength,
        const SLAndroidBufferItem *pItems, SLuint32 itemsLength,
        SLuint16 bufferType, AdvancedBufferHeader *pBuff, bool *pEOS)
{
    // reset item structure based on type
    switch (bufferType) {
      case kAndroidBufferTypeMpeg2Ts:
        pBuff->mItems.mTsCmdData.mTsCmdCode = ANDROID_MP2TSEVENT_NONE;
        pBuff->mItems.mTsCmdData.mPts = 0;
        break;
      case kAndroidBufferTypeAacadts:
        pBuff->mItems.mAdtsCmdData.mAdtsCmdCode = ANDROID_ADTSEVENT_NONE;
        break;
      case kAndroidBufferTypeInvalid:
      default:
        // shouldn't happen, but just in case clear out the item structure
        memset(&pBuff->mItems, 0, sizeof(AdvancedBufferItems));
        return SL_RESULT_INTERNAL_ERROR;
    }

    // process all items in the array; if no items then we break out of loop immediately
    while (itemsLength > 0) {

        // remaining length must be large enough for one full item without any associated data
        if (itemsLength < sizeof(SLAndroidBufferItem)) {
            SL_LOGE("Partial item at end of array");
            return SL_RESULT_PARAMETER_INVALID;
        }
        itemsLength -= sizeof(SLAndroidBufferItem);

        // remaining length must be large enough for data with current item and alignment padding
        SLuint32 itemDataSizeWithAlignmentPadding = (pItems->itemSize + 3) & ~3;
        if (itemsLength < itemDataSizeWithAlignmentPadding) {
            SL_LOGE("Partial item data at end of array");
            return SL_RESULT_PARAMETER_INVALID;
        }
        itemsLength -= itemDataSizeWithAlignmentPadding;

        // parse item data based on type
        switch (bufferType) {

          case kAndroidBufferTypeMpeg2Ts: {
            switch (pItems->itemKey) {

              case SL_ANDROID_ITEMKEY_EOS:
                pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_EOS;
                //SL_LOGD("Found EOS event=%d", pBuff->mItems.mTsCmdData.mTsCmdCode);
                if (pItems->itemSize != 0) {
                    SL_LOGE("Invalid item parameter size %u for EOS", pItems->itemSize);
                    return SL_RESULT_PARAMETER_INVALID;
                }
                break;

              case SL_ANDROID_ITEMKEY_DISCONTINUITY:
                if (pItems->itemSize == 0) {
                    pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_DISCONTINUITY;
                    //SL_LOGD("Found DISCONTINUITYevent=%d", pBuff->mItems.mTsCmdData.mTsCmdCode);
                } else if (pItems->itemSize == sizeof(SLAuint64)) {
                    pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_DISCON_NEWPTS;
                    pBuff->mItems.mTsCmdData.mPts = *((SLAuint64*)pItems->itemData);
                    //SL_LOGD("Found PTS=%lld", pBuff->mItems.mTsCmdData.mPts);
                } else {
                    SL_LOGE("Invalid item parameter size %u for MPEG-2 PTS", pItems->itemSize);
                    return SL_RESULT_PARAMETER_INVALID;
                }
                break;

              case SL_ANDROID_ITEMKEY_FORMAT_CHANGE:
                // distinguish between a "full" format change and one where it says what changed
                if (pItems->itemSize == 0) {
                    SL_LOGV("Received format change with no data == full format change");
                    pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
                } else if (pItems->itemSize == sizeof(SLuint32)) {
                    XAuint32 formatData = *((XAuint32*)pItems->itemData);
                    // intentionally only supporting video change when reading which specific
                    //    stream has changed, interpret other changes as full change
                    if (formatData == XA_ANDROID_FORMATCHANGE_ITEMDATA_VIDEO) {
                        pBuff->mItems.mTsCmdData.mTsCmdCode |=
                                ANDROID_MP2TSEVENT_FORMAT_CHANGE_VIDEO;
                        SL_LOGV("Received video format change");
                    } else {
                        // note that we don't support specifying
                        //    ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL by having all bits of
                        //    the data mask set, we default to it with unsupported masks
                        SL_LOGE("Received format change with unsupported data, ignoring data");
                        pBuff->mItems.mTsCmdData.mTsCmdCode |=
                                ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
                    }
                } else {
                    SL_LOGE("Received format change with invalid data size, ignoring data");
                    pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
                }
                break;

              default:
                // unknown item key
                SL_LOGE("Unknown item key %u with size %u", pItems->itemKey, pItems->itemSize);
                return SL_RESULT_PARAMETER_INVALID;

            }// switch (pItems->itemKey)
          } break;

          case kAndroidBufferTypeAacadts: {
            switch (pItems->itemKey) {

              case SL_ANDROID_ITEMKEY_EOS:
                pBuff->mItems.mAdtsCmdData.mAdtsCmdCode |= ANDROID_ADTSEVENT_EOS;
                if (pItems->itemSize != 0) {
                    SL_LOGE("Invalid item parameter size %u for EOS", pItems->itemSize);
                    return SL_RESULT_PARAMETER_INVALID;
                }
                break;

              default:
                // unknown item key
                SL_LOGE("Unknown item key %u with size %u", pItems->itemKey, pItems->itemSize);
                return SL_RESULT_PARAMETER_INVALID;

            }// switch (pItems->itemKey)
          } break;

          case kAndroidBufferTypeInvalid:
          default:
            // not reachable as we checked this earlier
            return SL_RESULT_INTERNAL_ERROR;

        }// switch (bufferType)

        // skip past this item, including data with alignment padding
        pItems = (SLAndroidBufferItem *) ((char *) pItems +
                sizeof(SLAndroidBufferItem) + itemDataSizeWithAlignmentPadding);
    }

    // now check for invalid combinations of items
    switch (bufferType) {

      case kAndroidBufferTypeMpeg2Ts: {
        // supported Mpeg2Ts commands are mutually exclusive
        switch (pBuff->mItems.mTsCmdData.mTsCmdCode) {
          // single items are allowed
          case ANDROID_MP2TSEVENT_EOS:
            if (dataLength > 0) {
                SL_LOGE("Can't enqueue non-zero data with EOS");
                return SL_RESULT_PRECONDITIONS_VIOLATED;
            }
            *pEOS = true;
            break;
          case ANDROID_MP2TSEVENT_NONE:
          case ANDROID_MP2TSEVENT_DISCONTINUITY:
          case ANDROID_MP2TSEVENT_DISCON_NEWPTS:
          case ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL:
          case ANDROID_MP2TSEVENT_FORMAT_CHANGE_VIDEO:
            break;
          // no combinations are allowed
          default:
            SL_LOGE("Invalid combination of items");
            return SL_RESULT_PARAMETER_INVALID;
        }
      } break;

      case kAndroidBufferTypeAacadts: {
        // only one item supported, and thus no combination check needed
        if (pBuff->mItems.mAdtsCmdData.mAdtsCmdCode == ANDROID_ADTSEVENT_EOS) {
            if (dataLength > 0) {
                SL_LOGE("Can't enqueue non-zero data with EOS");
                return SL_RESULT_PRECONDITIONS_VIOLATED;
            }
            *pEOS = true;
        }
      } break;

      case kAndroidBufferTypeInvalid:
      default:
        // not reachable as we checked this earlier
        return SL_RESULT_INTERNAL_ERROR;
    }

    return SL_RESULT_SUCCESS;
}


static SLresult IAndroidBufferQueue_RegisterCallback(SLAndroidBufferQueueItf self,
        slAndroidBufferQueueCallback callback, void *pContext)
{
    SL_ENTER_INTERFACE

    IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;

    interface_lock_exclusive(thiz);

    // verify pre-condition that media object is in the SL_PLAYSTATE_STOPPED state
    if (SL_PLAYSTATE_STOPPED == getAssociatedState(thiz)) {
        thiz->mCallback = callback;
        thiz->mContext = pContext;
        result = SL_RESULT_SUCCESS;

    } else {
        result = SL_RESULT_PRECONDITIONS_VIOLATED;
    }

    interface_unlock_exclusive(thiz);

    SL_LEAVE_INTERFACE
}


static SLresult IAndroidBufferQueue_Clear(SLAndroidBufferQueueItf self)
{
    SL_ENTER_INTERFACE
    result = SL_RESULT_SUCCESS;

    IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;

    interface_lock_exclusive(thiz);

    // reset the queue pointers
    thiz->mFront = &thiz->mBufferArray[0];
    thiz->mRear = &thiz->mBufferArray[0];
    // reset the queue state
    thiz->mState.count = 0;
    thiz->mState.index = 0;

    // object-specific behavior for a clear
    switch (InterfaceToObjectID(thiz)) {
    case SL_OBJECTID_AUDIOPLAYER:
        android_audioPlayer_androidBufferQueue_clear_l((CAudioPlayer*) thiz->mThis);
        break;
    case XA_OBJECTID_MEDIAPLAYER:
        android_Player_androidBufferQueue_clear_l((CMediaPlayer*) thiz->mThis);
        break;
    default:
        result = SL_RESULT_PARAMETER_INVALID;
    }

    interface_unlock_exclusive(thiz);

    SL_LEAVE_INTERFACE
}


static SLresult IAndroidBufferQueue_Enqueue(SLAndroidBufferQueueItf self,
        void *pBufferContext,
        void *pData,
        SLuint32 dataLength,
        const SLAndroidBufferItem *pItems,
        SLuint32 itemsLength)
{
    SL_ENTER_INTERFACE
    SL_LOGD("IAndroidBufferQueue_Enqueue pData=%p dataLength=%d", pData, dataLength);

    if ((dataLength > 0) && (NULL == pData)) {
        SL_LOGE("Enqueue failure: non-zero data length %u but NULL data pointer", dataLength);
        result = SL_RESULT_PARAMETER_INVALID;
    } else if ((itemsLength > 0) && (NULL == pItems)) {
        SL_LOGE("Enqueue failure: non-zero items length %u but NULL items pointer", itemsLength);
        result = SL_RESULT_PARAMETER_INVALID;
    } else if ((0 == dataLength) && (0 == itemsLength)) {
        // no data and no msg
        SL_LOGE("Enqueue failure: trying to enqueue buffer with no data and no items.");
        result = SL_RESULT_PARAMETER_INVALID;
    // Note that a non-NULL data pointer with zero data length is allowed.
    // We track that data pointer as it moves through the queue
    // to assist the application in accounting for data buffers.
    // A non-NULL items pointer with zero items length is also allowed, but has no value.
    } else {
        IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;

        // buffer size check, can be done outside of lock because buffer type can't change
        switch (thiz->mBufferType) {
          case kAndroidBufferTypeMpeg2Ts:
            if (dataLength % MPEG2_TS_PACKET_SIZE == 0) {
                // The downstream Stagefright MPEG-2 TS parser is sensitive to format errors,
                // so do a quick sanity check beforehand on the first packet of the buffer.
                // We don't check all the packets to avoid thrashing the data cache.
                if ((dataLength > 0) && (*(SLuint8 *)pData != MPEG2_TS_PACKET_SYNC)) {
                    SL_LOGE("Error enqueueing MPEG-2 TS data: incorrect packet sync");
                    result = SL_RESULT_CONTENT_CORRUPTED;
                    SL_LEAVE_INTERFACE
                }
                break;
            }
            SL_LOGE("Error enqueueing MPEG-2 TS data: size must be a multiple of %d (packet size)",
                    MPEG2_TS_PACKET_SIZE);
            result = SL_RESULT_PARAMETER_INVALID;
            SL_LEAVE_INTERFACE
            break;
          case kAndroidBufferTypeAacadts:
            // zero dataLength is permitted in case of EOS command only
            if (dataLength > 0) {
                result = android::AacBqToPcmCbRenderer::validateBufferStartEndOnFrameBoundaries(
                    pData, dataLength);
                if (SL_RESULT_SUCCESS != result) {
                    SL_LOGE("Error enqueueing ADTS data: data must start and end on frame "
                            "boundaries");
                    SL_LEAVE_INTERFACE
                }
            }
            break;
          case kAndroidBufferTypeInvalid:
          default:
            result = SL_RESULT_PARAMETER_INVALID;
            SL_LEAVE_INTERFACE
        }

        interface_lock_exclusive(thiz);

        AdvancedBufferHeader *oldRear = thiz->mRear, *newRear;
        if ((newRear = oldRear + 1) == &thiz->mBufferArray[thiz->mNumBuffers + 1]) {
            newRear = thiz->mBufferArray;
        }
        if (thiz->mEOS) {
            SL_LOGE("Can't enqueue after EOS");
            result = SL_RESULT_PRECONDITIONS_VIOLATED;
        } else if (newRear == thiz->mFront) {
            result = SL_RESULT_BUFFER_INSUFFICIENT;
        } else {
            // set oldRear->mItems based on items
            result = setItems(dataLength, pItems, itemsLength, thiz->mBufferType, oldRear,
                    &thiz->mEOS);
            if (SL_RESULT_SUCCESS == result) {
                oldRear->mDataBuffer = pData;
                oldRear->mDataSize = dataLength;
                oldRear->mDataSizeConsumed = 0;
                oldRear->mBufferContext = pBufferContext;
                //oldRear->mBufferState = TBD;
                thiz->mRear = newRear;
                ++thiz->mState.count;
            }
        }
        // set enqueue attribute if state is PLAYING and the first buffer is enqueued
        interface_unlock_exclusive_attributes(thiz, ((SL_RESULT_SUCCESS == result) &&
                (1 == thiz->mState.count) && (SL_PLAYSTATE_PLAYING == getAssociatedState(thiz))) ?
                        ATTR_ABQ_ENQUEUE : ATTR_NONE);
    }

    SL_LEAVE_INTERFACE
}


static SLresult IAndroidBufferQueue_GetState(SLAndroidBufferQueueItf self,
        SLAndroidBufferQueueState *pState)
{
    SL_ENTER_INTERFACE

    // Note that GetState while a Clear is pending is equivalent to GetState before the Clear

    if (NULL == pState) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;

        interface_lock_shared(thiz);

        pState->count = thiz->mState.count;
        pState->index = thiz->mState.index;

        interface_unlock_shared(thiz);

        result = SL_RESULT_SUCCESS;
    }

    SL_LEAVE_INTERFACE
}


static SLresult IAndroidBufferQueue_SetCallbackEventsMask(SLAndroidBufferQueueItf self,
        SLuint32 eventFlags)
{
    SL_ENTER_INTERFACE

    IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
    interface_lock_exclusive(thiz);
    // FIXME only supporting SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED in this implementation
    if (!(~(SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED /* | others TBD */ ) & eventFlags)) {
        thiz->mCallbackEventsMask = eventFlags;
        result = SL_RESULT_SUCCESS;
    } else {
        result = SL_RESULT_FEATURE_UNSUPPORTED;
    }
    interface_unlock_exclusive(thiz);

    SL_LEAVE_INTERFACE
}


static SLresult IAndroidBufferQueue_GetCallbackEventsMask(SLAndroidBufferQueueItf self,
        SLuint32 *pEventFlags)
{
    SL_ENTER_INTERFACE

    if (NULL == pEventFlags) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
        interface_lock_shared(thiz);
        SLuint32 callbackEventsMask = thiz->mCallbackEventsMask;
        interface_unlock_shared(thiz);
        *pEventFlags = callbackEventsMask;
        result = SL_RESULT_SUCCESS;
    }

    SL_LEAVE_INTERFACE
}


static const struct SLAndroidBufferQueueItf_ IAndroidBufferQueue_Itf = {
    IAndroidBufferQueue_RegisterCallback,
    IAndroidBufferQueue_Clear,
    IAndroidBufferQueue_Enqueue,
    IAndroidBufferQueue_GetState,
    IAndroidBufferQueue_SetCallbackEventsMask,
    IAndroidBufferQueue_GetCallbackEventsMask
};


void IAndroidBufferQueue_init(void *self)
{
    IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
    thiz->mItf = &IAndroidBufferQueue_Itf;

    thiz->mState.count = 0;
    thiz->mState.index = 0;

    thiz->mCallback = NULL;
    thiz->mContext = NULL;
    thiz->mCallbackEventsMask = SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED;

    thiz->mBufferType = kAndroidBufferTypeInvalid;
    thiz->mBufferArray = NULL;
    thiz->mFront = NULL;
    thiz->mRear = NULL;
    thiz->mEOS = false;
}


void IAndroidBufferQueue_deinit(void *self)
{
    IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
    if (NULL != thiz->mBufferArray) {
        free(thiz->mBufferArray);
        thiz->mBufferArray = NULL;
    }
}


#if 0
// Dump the contents of an IAndroidBufferQueue to the log.  This is for debugging only,
// and is not a documented API.  The associated object is locked throughout for atomicity,
// but the log entries may be interspersed with unrelated logs.

void IAndroidBufferQueue_log(IAndroidBufferQueue *thiz)
{
    interface_lock_shared(thiz);
    SL_LOGI("IAndroidBufferQueue %p:", thiz);
    SL_LOGI("  mState.count=%u mState.index=%u mCallback=%p mContext=%p",
            thiz->mState.count, thiz->mState.index, thiz->mCallback, thiz->mContext);
    const char *bufferTypeString;
    switch (thiz->mBufferType) {
    case kAndroidBufferTypeInvalid:
        bufferTypeString = "kAndroidBufferTypeInvalid";
        break;
    case kAndroidBufferTypeMpeg2Ts:
        bufferTypeString = "kAndroidBufferTypeMpeg2Ts";
        break;
    case kAndroidBufferTypeAacadts:
        bufferTypeString = "kAndroidBufferTypeAacadts";
        break;
    default:
        bufferTypeString = "unknown";
        break;
    }
    SL_LOGI("  mCallbackEventsMask=0x%x, mBufferType=0x%x (%s), mEOS=%s",
            thiz->mCallbackEventsMask,
            thiz->mBufferType, bufferTypeString,
            thiz->mEOS ? "true" : "false");
    SL_LOGI("  mBufferArray=%p, mFront=%p (%u), mRear=%p (%u)",
            thiz->mBufferArray,
            thiz->mFront, thiz->mFront - thiz->mBufferArray,
            thiz->mRear, thiz->mRear - thiz->mBufferArray);
    SL_LOGI("  index mDataBuffer mDataSize mDataSizeConsumed mBufferContext mItems");
    const AdvancedBufferHeader *hdr;
    for (hdr = thiz->mFront; hdr != thiz->mRear; ) {
        SLuint32 i = hdr - thiz->mBufferArray;
        char itemString[32];
        switch (thiz->mBufferType) {
        case kAndroidBufferTypeMpeg2Ts:
            switch (hdr->mItems.mTsCmdData.mTsCmdCode) {
            case ANDROID_MP2TSEVENT_NONE:
                strcpy(itemString, "NONE");
                break;
            case ANDROID_MP2TSEVENT_EOS:
                strcpy(itemString, "EOS");
                break;
            case ANDROID_MP2TSEVENT_DISCONTINUITY:
                strcpy(itemString, "DISCONTINUITY");
                break;
            case ANDROID_MP2TSEVENT_DISCON_NEWPTS:
                snprintf(itemString, sizeof(itemString), "NEWPTS %llu",
                        hdr->mItems.mTsCmdData.mPts);
                break;
            case ANDROID_MP2TSEVENT_FORMAT_CHANGE:
                strcpy(itemString, "FORMAT_CHANGE");
                break;
            default:
                snprintf(itemString, sizeof(itemString), "0x%x", hdr->mItems.mTsCmdData.mTsCmdCode);
                break;
            }
            break;
        case kAndroidBufferTypeAacadts:
            switch (hdr->mItems.mAdtsCmdData.mAdtsCmdCode) {
            case ANDROID_ADTSEVENT_NONE:
                strcpy(itemString, "NONE");
                break;
            case ANDROID_ADTSEVENT_EOS:
                strcpy(itemString, "EOS");
                break;
            default:
                snprintf(itemString, sizeof(itemString), "0x%x",
                        hdr->mItems.mAdtsCmdData.mAdtsCmdCode);
                break;
            }
            break;
        default:
            strcpy(itemString, "");
            break;
        }
        SL_LOGI("  %5u %11p %9u %17u %14p %s",
                i, hdr->mDataBuffer, hdr->mDataSize, hdr->mDataSizeConsumed,
                hdr->mBufferContext, itemString);
                // mBufferState
        if (++hdr == &thiz->mBufferArray[thiz->mNumBuffers + 1]) {
            hdr = thiz->mBufferArray;
        }
    }
    interface_unlock_shared(thiz);
}

#endif