/******************************************************************************
 *
 *  Copyright (C) 2014 The Android Open Source Project
 *  Copyright 2006 Open Interface North America, Inc. All rights reserved.
 *
 *  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.
 *
 ******************************************************************************/

/**********************************************************************************
  $Revision: #1 $
 ***********************************************************************************/

/** @file
@ingroup codec_internal
*/

/**@addtogroup codec_internal */
/**@{*/

#include "oi_codec_sbc_private.h"
#include "oi_bitstream.h"

#define SPECIALIZE_READ_SAMPLES_JOINT

/**
 * Scans through a buffer looking for a codec syncword. If the decoder has been
 * set for enhanced operation using OI_CODEC_SBC_DecoderReset(), it will search
 * for both a standard and an enhanced syncword.
 */
PRIVATE OI_STATUS FindSyncword(OI_CODEC_SBC_DECODER_CONTEXT *context,
                               const OI_BYTE **frameData,
                               OI_UINT32 *frameBytes)
{
#ifdef SBC_ENHANCED
    OI_BYTE search1 = OI_SBC_SYNCWORD;
    OI_BYTE search2 = OI_SBC_ENHANCED_SYNCWORD;
#endif // SBC_ENHANCED

    if (*frameBytes == 0) {
        return OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA;
    }

#ifdef SBC_ENHANCED
    if (context->limitFrameFormat && context->enhancedEnabled){
        /* If the context is restricted, only search for specified SYNCWORD */
        search1 = search2;
    } else if (context->enhancedEnabled == FALSE) {
        /* If enhanced is not enabled, only search for classic SBC SYNCWORD*/
        search2 = search1;
    }
    while (*frameBytes && (**frameData != search1) && (**frameData != search2)) {
        (*frameBytes)--;
        (*frameData)++;
    }
    if (*frameBytes) {
        /* Syncword found, *frameData points to it, and *frameBytes correctly
         * reflects the number of bytes available to read, including the
         * syncword. */
        context->common.frameInfo.enhanced = (**frameData == OI_SBC_ENHANCED_SYNCWORD);
        return OI_OK;
    } else {
        /* No syncword was found anywhere in the provided input data.
         * *frameData points past the end of the original input, and
         * *frameBytes is 0. */
        return OI_CODEC_SBC_NO_SYNCWORD;
    }
#else  // SBC_ENHANCED
    while (*frameBytes && (**frameData != OI_SBC_SYNCWORD)) {
        (*frameBytes)--;
        (*frameData)++;
    }
    if (*frameBytes) {
        /* Syncword found, *frameData points to it, and *frameBytes correctly
         * reflects the number of bytes available to read, including the
         * syncword. */
        context->common.frameInfo.enhanced = FALSE;
        return OI_OK;
    } else {
        /* No syncword was found anywhere in the provided input data.
         * *frameData points past the end of the original input, and
         * *frameBytes is 0. */
        return OI_CODEC_SBC_NO_SYNCWORD;
    }
#endif // SBC_ENHANCED
}

static OI_STATUS DecodeBody(OI_CODEC_SBC_DECODER_CONTEXT *context,
                            const OI_BYTE *bodyData,
                            OI_INT16 *pcmData,
                            OI_UINT32 *pcmBytes,
                            OI_BOOL allowPartial)
{
    OI_BITSTREAM bs;
    OI_UINT frameSamples = context->common.frameInfo.nrof_blocks * context->common.frameInfo.nrof_subbands;
    OI_UINT decode_block_count;

    /*
     * Based on the header data, make sure that there is enough room to write the output samples.
     */
    if (*pcmBytes < (sizeof(OI_INT16) * frameSamples * context->common.pcmStride) && !allowPartial) {
        /* If we're not allowing partial decodes, we need room for the entire
         * codec frame */
        TRACE(("-OI_CODEC_SBC_Decode: OI_CODEC_SBC_NOT_ENOUGH_AUDIO_DATA"));
        return OI_CODEC_SBC_NOT_ENOUGH_AUDIO_DATA;
    } else if (*pcmBytes < sizeof (OI_INT16) * context->common.frameInfo.nrof_subbands * context->common.pcmStride) {
        /* Even if we're allowing partials, we can still only decode on a frame
         * boundary */
        return OI_CODEC_SBC_NOT_ENOUGH_AUDIO_DATA;
    }

    if (context->bufferedBlocks == 0) {
        TRACE(("Reading scalefactors"));
        OI_SBC_ReadScalefactors(&context->common, bodyData, &bs);

        TRACE(("Computing bit allocation"));
        OI_SBC_ComputeBitAllocation(&context->common);

        TRACE(("Reading samples"));
        if (context->common.frameInfo.mode == SBC_JOINT_STEREO) {
            OI_SBC_ReadSamplesJoint(context, &bs);
        } else {
            OI_SBC_ReadSamples(context, &bs);
        }

        context->bufferedBlocks = context->common.frameInfo.nrof_blocks;
    }

    if (allowPartial) {
        decode_block_count = *pcmBytes / sizeof(OI_INT16) / context->common.pcmStride / context->common.frameInfo.nrof_subbands;

        if (decode_block_count > context->bufferedBlocks) {
            decode_block_count = context->bufferedBlocks;
        }

    } else {
        decode_block_count = context->common.frameInfo.nrof_blocks;
    }

    TRACE(("Synthesizing frame"));
    {
        OI_UINT start_block = context->common.frameInfo.nrof_blocks - context->bufferedBlocks;
        OI_SBC_SynthFrame(context, pcmData, start_block, decode_block_count);
    }

    OI_ASSERT(context->bufferedBlocks >= decode_block_count);
    context->bufferedBlocks -= decode_block_count;

    frameSamples = decode_block_count * context->common.frameInfo.nrof_subbands;

    /*
     * When decoding mono into a stride-2 array, copy pcm data to second channel
     */
    if (context->common.frameInfo.nrof_channels == 1 && context->common.pcmStride == 2) {
        OI_UINT i;
        for (i = 0; i < frameSamples; ++i) {
            pcmData[2*i+1] = pcmData[2*i];
        }
    }

    /*
     * Return number of pcm bytes generated by the decode operation.
     */
    *pcmBytes = frameSamples * sizeof(OI_INT16) * context->common.pcmStride;
    if (context->bufferedBlocks > 0) {
        return OI_CODEC_SBC_PARTIAL_DECODE;
    } else {
        return OI_OK;
    }
}

PRIVATE OI_STATUS internal_DecodeRaw(OI_CODEC_SBC_DECODER_CONTEXT *context,
                                     OI_UINT8 bitpool,
                                     const OI_BYTE **frameData,
                                     OI_UINT32 *frameBytes,
                                     OI_INT16 *pcmData,
                                     OI_UINT32 *pcmBytes)
{
    OI_STATUS status;
    OI_UINT bodyLen;

    TRACE(("+OI_CODEC_SBC_DecodeRaw"));

    if (context->bufferedBlocks == 0) {
        /*
         * The bitallocator needs to know the bitpool value.
         */
        context->common.frameInfo.bitpool = bitpool;
        /*
         * Compute the frame length and check we have enough frame data to proceed
         */
        bodyLen = OI_CODEC_SBC_CalculateFramelen(&context->common.frameInfo) - SBC_HEADER_LEN;
        if (*frameBytes < bodyLen) {
            TRACE(("-OI_CODEC_SBC_Decode: OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA"));
            return OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA;
        }
    } else {
        bodyLen = 0;
    }
    /*
     * Decode the SBC data. Pass TRUE to DecodeBody to allow partial decoding of
     * tones.
     */
    status = DecodeBody(context, *frameData, pcmData, pcmBytes, TRUE);
    if (OI_SUCCESS(status) || status == OI_CODEC_SBC_PARTIAL_DECODE) {
        *frameData += bodyLen;
        *frameBytes -= bodyLen;
    }
    TRACE(("-OI_CODEC_SBC_DecodeRaw: %d", status));
    return status;
}

OI_STATUS OI_CODEC_SBC_DecoderReset(OI_CODEC_SBC_DECODER_CONTEXT *context,
                                    OI_UINT32 *decoderData,
                                    OI_UINT32 decoderDataBytes,
                                    OI_UINT8 maxChannels,
                                    OI_UINT8 pcmStride,
                                    OI_BOOL enhanced)
{
    return internal_DecoderReset(context, decoderData, decoderDataBytes, maxChannels, pcmStride, enhanced);
}

OI_STATUS OI_CODEC_SBC_DecodeFrame(OI_CODEC_SBC_DECODER_CONTEXT *context,
                                   const OI_BYTE **frameData,
                                   OI_UINT32 *frameBytes,
                                   OI_INT16 *pcmData,
                                   OI_UINT32 *pcmBytes)
{
    OI_STATUS status;
    OI_UINT framelen;
    OI_UINT8 crc;

    TRACE(("+OI_CODEC_SBC_DecodeFrame"));

    TRACE(("Finding syncword"));
    status = FindSyncword(context, frameData, frameBytes);
    if (!OI_SUCCESS(status)) {
        return status;
    }

    /* Make sure enough data remains to read the header. */
    if (*frameBytes < SBC_HEADER_LEN) {
        TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA"));
        return OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA;
    }

    TRACE(("Reading Header"));
    OI_SBC_ReadHeader(&context->common, *frameData);

    /*
     * Some implementations load the decoder into RAM and use overlays for 4 vs 8 subbands. We need
     * to ensure that the SBC parameters for this frame are compatible with the restrictions imposed
     * by the loaded overlays.
     */
    if (context->limitFrameFormat && (context->common.frameInfo.subbands != context->restrictSubbands)) {
        ERROR(("SBC parameters incompatible with loaded overlay"));
        return OI_STATUS_INVALID_PARAMETERS;
    }

    if (context->common.frameInfo.nrof_channels > context->common.maxChannels) {
        ERROR(("SBC parameters incompatible with number of channels specified during reset"));
        return OI_STATUS_INVALID_PARAMETERS;
    }

    if (context->common.pcmStride < 1 || context->common.pcmStride > 2) {
        ERROR(("PCM stride not set correctly during reset"));
        return OI_STATUS_INVALID_PARAMETERS;
    }

    /*
     * At this point a header has been read. However, it's possible that we found a false syncword,
     * so the header data might be invalid. Make sure we have enough bytes to read in the
     * CRC-protected header, but don't require we have the whole frame. That way, if it turns out
     * that we're acting on bogus header data, we don't stall the decoding process by waiting for
     * data that we don't actually need.
     */
    framelen = OI_CODEC_SBC_CalculateFramelen(&context->common.frameInfo);
    if (*frameBytes < framelen) {
        TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA"));
        return OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA;
    }

    TRACE(("Calculating checksum"));

    crc = OI_SBC_CalculateChecksum(&context->common.frameInfo, *frameData);
    if (crc != context->common.frameInfo.crc) {
        TRACE(("CRC Mismatch:  calc=%02x read=%02x\n", crc, context->common.frameInfo.crc));
        TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_CHECKSUM_MISMATCH"));
        return OI_CODEC_SBC_CHECKSUM_MISMATCH;
    }

#ifdef OI_DEBUG
    /*
     * Make sure the bitpool values are sane.
     */
    if ((context->common.frameInfo.bitpool < SBC_MIN_BITPOOL) && !context->common.frameInfo.enhanced) {
        ERROR(("Bitpool too small: %d (must be >= 2)", context->common.frameInfo.bitpool));
        return OI_STATUS_INVALID_PARAMETERS;
    }
    if (context->common.frameInfo.bitpool > OI_SBC_MaxBitpool(&context->common.frameInfo)) {
        ERROR(("Bitpool too large: %d (must be <= %ld)", context->common.frameInfo.bitpool, OI_SBC_MaxBitpool(&context->common.frameInfo)));
        return OI_STATUS_INVALID_PARAMETERS;
    }
#endif

    /*
     * Now decode the SBC data. Partial decode is not yet implemented for an SBC
     * stream, so pass FALSE to decode body to have it enforce the old rule that
     * you have to decode a whole packet at a time.
     */
    status = DecodeBody(context, *frameData + SBC_HEADER_LEN, pcmData, pcmBytes, FALSE);
    if (OI_SUCCESS(status)) {
        *frameData += framelen;
        *frameBytes -= framelen;
    }
    TRACE(("-OI_CODEC_SBC_DecodeFrame: %d", status));

    return status;
}

OI_STATUS OI_CODEC_SBC_SkipFrame(OI_CODEC_SBC_DECODER_CONTEXT *context,
                                 const OI_BYTE **frameData,
                                 OI_UINT32 *frameBytes)
{
    OI_STATUS status;
    OI_UINT framelen;
    OI_UINT headerlen;
    OI_UINT8 crc;

    status = FindSyncword(context, frameData, frameBytes);
    if (!OI_SUCCESS(status)) {
        return status;
    }
    if (*frameBytes < SBC_HEADER_LEN) {
        return OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA;
    }
    OI_SBC_ReadHeader(&context->common, *frameData);
    framelen = OI_SBC_CalculateFrameAndHeaderlen(&context->common.frameInfo, &headerlen);
    if (*frameBytes < headerlen) {
        return OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA;
    }
    crc = OI_SBC_CalculateChecksum(&context->common.frameInfo, *frameData);
    if (crc != context->common.frameInfo.crc) {
        return OI_CODEC_SBC_CHECKSUM_MISMATCH;
    }
    if (*frameBytes < framelen) {
        return OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA;
    }
    context->bufferedBlocks = 0;
    *frameData += framelen;
    *frameBytes -= framelen;
    return OI_OK;
}

OI_UINT8 OI_CODEC_SBC_FrameCount(OI_BYTE  *frameData,
                                 OI_UINT32 frameBytes)
{
    OI_UINT8 mode;
    OI_UINT8 blocks;
    OI_UINT8 subbands;
    OI_UINT8 frameCount = 0;
    OI_UINT  frameLen;

    while (frameBytes){
        while (frameBytes && ((frameData[0] & 0xFE) != 0x9C)){
            frameData++;
            frameBytes--;
        }

        if (frameBytes < SBC_HEADER_LEN) {
            return frameCount;
        }

        /* Extract and translate required fields from Header */
        subbands = mode = blocks = frameData[1];;
        mode = (mode & (BIT3 | BIT2)) >> 2;
        blocks = block_values[(blocks & (BIT5 | BIT4)) >> 4];
        subbands = band_values[(subbands & BIT0)];

        /* Inline logic to avoid corrupting context */
        frameLen = blocks * frameData[2];
        switch (mode){
            case SBC_JOINT_STEREO:
                frameLen += subbands + (8 * subbands);
                break;

            case SBC_DUAL_CHANNEL:
                frameLen *= 2;
                /* fall through */

            default:
                if (mode == SBC_MONO){
                    frameLen += 4*subbands;
                } else {
                    frameLen += 8*subbands;
                }
        }

        frameCount++;
        frameLen = SBC_HEADER_LEN + (frameLen + 7) / 8;
        if (frameBytes > frameLen){
            frameBytes -= frameLen;
            frameData += frameLen;
        } else {
            frameBytes = 0;
        }
    }
    return frameCount;
}

/** Read quantized subband samples from the input bitstream and expand them. */

#ifdef SPECIALIZE_READ_SAMPLES_JOINT

PRIVATE void OI_SBC_ReadSamplesJoint4(OI_CODEC_SBC_DECODER_CONTEXT *context, OI_BITSTREAM *global_bs)
{
#define NROF_SUBBANDS 4
#include "readsamplesjoint.inc"
#undef NROF_SUBBANDS
}

PRIVATE void OI_SBC_ReadSamplesJoint8(OI_CODEC_SBC_DECODER_CONTEXT *context, OI_BITSTREAM *global_bs)
{
#define NROF_SUBBANDS 8
#include "readsamplesjoint.inc"
#undef NROF_SUBBANDS
}

typedef void (*READ_SAMPLES)(OI_CODEC_SBC_DECODER_CONTEXT *context, OI_BITSTREAM *global_bs);

static const READ_SAMPLES SpecializedReadSamples[] = {
    OI_SBC_ReadSamplesJoint4,
    OI_SBC_ReadSamplesJoint8
};

#endif /* SPECIALIZE_READ_SAMPLES_JOINT */


PRIVATE void OI_SBC_ReadSamplesJoint(OI_CODEC_SBC_DECODER_CONTEXT *context, OI_BITSTREAM *global_bs)
{
    OI_CODEC_SBC_COMMON_CONTEXT *common = &context->common;
    OI_UINT nrof_subbands = common->frameInfo.nrof_subbands;
#ifdef SPECIALIZE_READ_SAMPLES_JOINT
    OI_ASSERT((nrof_subbands >> 3u) <= 1u);
    SpecializedReadSamples[nrof_subbands >> 3](context, global_bs);
#else

#define NROF_SUBBANDS nrof_subbands
#include "readsamplesjoint.inc"
#undef NROF_SUBBANDS
#endif /* SPECIALIZE_READ_SAMPLES_JOINT */
}

/**@}*/

