/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "acm_speex.h"
#include "acm_codec_database.h"
#include "acm_common_defs.h"
#include "acm_neteq.h"
#include "trace.h"
#include "webrtc_neteq.h"
#include "webrtc_neteq_help_macros.h"

#ifdef WEBRTC_CODEC_SPEEX
    // NOTE! Speex is not included in the open-source package. The following
    // interface file is needed:
    //
    // /modules/audio_coding/codecs/speex/main/interface/speex_interface.h
    //
    // The API in the header file should match the one below.
    //
    // int16_t WebRtcSpeex_CreateEnc(SPEEX_encinst_t **SPEEXenc_inst,
    //                               int32_t fs);
    // int16_t WebRtcSpeex_FreeEnc(SPEEX_encinst_t *SPEEXenc_inst);
    // int16_t WebRtcSpeex_CreateDec(SPEEX_decinst_t **SPEEXdec_inst,
    //                               int32_t fs,
    //                               int16_t enh_enabled);
    // int16_t WebRtcSpeex_FreeDec(SPEEX_decinst_t *SPEEXdec_inst);
    // int16_t WebRtcSpeex_Encode(SPEEX_encinst_t *SPEEXenc_inst,
    //                            int16_t *speechIn,
    //                            int32_t rate);
    // int16_t WebRtcSpeex_EncoderInit(SPEEX_encinst_t *SPEEXenc_inst,
    //                                 int16_t vbr, int16_t complexity,
    //                                 int16_t vad_enable);
    // int16_t WebRtcSpeex_GetBitstream(SPEEX_encinst_t *SPEEXenc_inst,
    //                                  int16_t *encoded);
    // int16_t WebRtcSpeex_DecodePlc(SPEEX_decinst_t *SPEEXdec_inst,
    //                               int16_t *decoded, int16_t noOfLostFrames);
    // int16_t WebRtcSpeex_Decode(SPEEX_decinst_t *SPEEXdec_inst,
    //                            int16_t *encoded, int16_t len,
    //                            int16_t *decoded, int16_t *speechType);
    // int16_t WebRtcSpeex_DecoderInit(SPEEX_decinst_t *SPEEXdec_inst);
    // void WebRtcSpeex_Version(char *versionStr, short len);
    #include "speex_interface.h"
#endif

namespace webrtc {

#ifndef WEBRTC_CODEC_SPEEX
ACMSPEEX::ACMSPEEX(WebRtc_Word16 /* codecID*/)
{
    return;
}

ACMSPEEX::~ACMSPEEX()
{
    return;
}

WebRtc_Word16
ACMSPEEX::InternalEncode(
    WebRtc_UWord8* /* bitStream        */,
    WebRtc_Word16* /* bitStreamLenByte */)
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::DecodeSafe(
    WebRtc_UWord8* /* bitStream        */,
    WebRtc_Word16  /* bitStreamLenByte */,
    WebRtc_Word16* /* audio            */,
    WebRtc_Word16* /* audioSamples     */,
    WebRtc_Word8*  /* speechType       */)
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::EnableDTX()
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::DisableDTX()
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::InternalInitEncoder(
    WebRtcACMCodecParams* /* codecParams */)
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::InternalInitDecoder(
    WebRtcACMCodecParams* /* codecParams */)
{
    return -1;
}

WebRtc_Word32
ACMSPEEX::CodecDef(
    WebRtcNetEQ_CodecDef& /* codecDef  */,
    const CodecInst&      /* codecInst */)
{
    return -1;
}

ACMGenericCodec*
ACMSPEEX::CreateInstance(void)
{
    return NULL;
}

WebRtc_Word16
ACMSPEEX::InternalCreateEncoder()
{
    return -1;
}

void
ACMSPEEX::DestructEncoderSafe()
{
    return;
}


WebRtc_Word16
ACMSPEEX::InternalCreateDecoder()
{
    return -1;
}

void
ACMSPEEX::DestructDecoderSafe()
{
    return;
}

WebRtc_Word16
ACMSPEEX::SetBitRateSafe(
    const WebRtc_Word32 /* rate */)
{
    return -1;
}

void
ACMSPEEX::InternalDestructEncoderInst(
    void* /* ptrInst */)
{
    return;
}

WebRtc_Word16
ACMSPEEX::UnregisterFromNetEqSafe(
    ACMNetEQ*     /* netEq       */,
    WebRtc_Word16 /* payloadType */)
{
    return -1;
}

#ifdef UNUSEDSPEEX
WebRtc_Word16
ACMSPEEX::EnableVBR()
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::DisableVBR()
{
    return -1;
}

WebRtc_Word16
ACMSPEEX::SetComplMode(
    WebRtc_Word16 mode)
{
    return -1;
}
#endif

#else     //===================== Actual Implementation =======================

ACMSPEEX::ACMSPEEX(WebRtc_Word16 codecID):
_encoderInstPtr(NULL),
_decoderInstPtr(NULL)
{
    _codecID = codecID;

    // Set sampling frequency, frame size and rate Speex
    if(_codecID == ACMCodecDB::kSPEEX8)
    {
        _samplingFrequency = 8000;
        _samplesIn20MsAudio = 160;
        _encodingRate = 11000;
    }
    else if(_codecID == ACMCodecDB::kSPEEX16)
    {
        _samplingFrequency = 16000;
        _samplesIn20MsAudio = 320;
        _encodingRate = 22000;
    }
    else
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Wrong codec id for Speex.");

        _samplingFrequency = -1;
        _samplesIn20MsAudio = -1;
        _encodingRate = -1;
    }

    _hasInternalDTX = true;
    _dtxEnabled = false;
    _vbrEnabled = false;
    _complMode =  3; // default complexity value

    return;
}

ACMSPEEX::~ACMSPEEX()
{
    if(_encoderInstPtr != NULL)
    {
        WebRtcSpeex_FreeEnc(_encoderInstPtr);
        _encoderInstPtr = NULL;
    }
    if(_decoderInstPtr != NULL)
    {
        WebRtcSpeex_FreeDec(_decoderInstPtr);
        _decoderInstPtr = NULL;
    }
    return;
}

WebRtc_Word16
ACMSPEEX::InternalEncode(
    WebRtc_UWord8* bitStream,
    WebRtc_Word16* bitStreamLenByte)
{
    WebRtc_Word16 status;
    WebRtc_Word16 numEncodedSamples = 0;
    WebRtc_Word16 n = 0;

    while( numEncodedSamples < _frameLenSmpl)
    {
        status = WebRtcSpeex_Encode(_encoderInstPtr, &_inAudio[_inAudioIxRead],
            _encodingRate);

        // increment the read index this tell the caller that how far
        // we have gone forward in reading the audio buffer
        _inAudioIxRead += _samplesIn20MsAudio;
        numEncodedSamples += _samplesIn20MsAudio;

        if(status < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
                "Error in Speex encoder");
            return status;
        }

        // Update VAD, if internal DTX is used
        if(_hasInternalDTX && _dtxEnabled)
        {
            _vadLabel[n++] = status;
            _vadLabel[n++] = status;
        }

        if(status == 0)
        {
            // This frame is detected as inactive. We need send whatever
            // encoded so far.
            *bitStreamLenByte = WebRtcSpeex_GetBitstream(_encoderInstPtr,
                (WebRtc_Word16*)bitStream);

            return *bitStreamLenByte;
        }
    }

    *bitStreamLenByte = WebRtcSpeex_GetBitstream(_encoderInstPtr,
        (WebRtc_Word16*)bitStream);
    return *bitStreamLenByte;
}

WebRtc_Word16
ACMSPEEX::DecodeSafe(
    WebRtc_UWord8* /* bitStream        */,
    WebRtc_Word16  /* bitStreamLenByte */,
    WebRtc_Word16* /* audio            */,
    WebRtc_Word16* /* audioSamples     */,
    WebRtc_Word8*  /* speechType       */)
{
    return 0;
}

WebRtc_Word16
ACMSPEEX::EnableDTX()
{
    if(_dtxEnabled)
    {
        return 0;
    }
    else if(_encoderExist)  // check if encoder exist
    {
        // enable DTX
        if(WebRtcSpeex_EncoderInit(_encoderInstPtr, (_vbrEnabled ? 1:0), _complMode, 1) < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Cannot enable DTX for Speex");
            return -1;
        }
        _dtxEnabled = true;
        return 0;
    }
    else
    {
        return -1;
    }

    return 0;
}

WebRtc_Word16
ACMSPEEX::DisableDTX()
{
    if(!_dtxEnabled)
    {
        return 0;
    }
    else if(_encoderExist)  // check if encoder exist
    {
        // disable DTX
        if(WebRtcSpeex_EncoderInit(_encoderInstPtr, (_vbrEnabled ? 1:0), _complMode, 0) < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Cannot disable DTX for Speex");
            return -1;
        }
        _dtxEnabled = false;
        return 0;
    }
    else
    {
        // encoder doesn't exists, therefore disabling is harmless
        return 0;
    }

    return 0;
}

WebRtc_Word16
ACMSPEEX::InternalInitEncoder(
    WebRtcACMCodecParams* codecParams)
{
    // sanity check
    if (_encoderInstPtr == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Cannot initialize Speex encoder, instance does not exist");
        return -1;
    }

    WebRtc_Word16 status = SetBitRateSafe((codecParams->codecInstant).rate);
    status += (WebRtcSpeex_EncoderInit(_encoderInstPtr, _vbrEnabled, _complMode, ((codecParams->enableDTX)? 1:0)) < 0)? -1:0;

    if (status >= 0) {
        return 0;
    } else {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Error in initialization of Speex encoder");
        return -1;
    }
}

WebRtc_Word16
ACMSPEEX::InternalInitDecoder(
    WebRtcACMCodecParams* /* codecParams */)
{
    WebRtc_Word16 status;

    // sanity check
    if (_decoderInstPtr == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Cannot initialize Speex decoder, instance does not exist");
        return -1;
    }
    status = ((WebRtcSpeex_DecoderInit(_decoderInstPtr) < 0)? -1:0);

    if (status >= 0) {
        return 0;
    } else {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Error in initialization of Speex decoder");
        return -1;
    }
}

WebRtc_Word32
ACMSPEEX::CodecDef(
    WebRtcNetEQ_CodecDef& codecDef,
    const CodecInst&      codecInst)
{
    if (!_decoderInitialized)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Error, Speex decoder is not initialized");
        return -1;
    }

    // Fill up the structure by calling
    // "SET_CODEC_PAR" & "SET_SPEEX_FUNCTION."
    // Then call NetEQ to add the codec to it's
    // database.

    switch(_samplingFrequency)
    {
    case 8000:
        {
            SET_CODEC_PAR((codecDef), kDecoderSPEEX_8, codecInst.pltype,
                _decoderInstPtr, 8000);
            break;
        }
    case 16000:
        {
            SET_CODEC_PAR((codecDef), kDecoderSPEEX_16, codecInst.pltype,
                _decoderInstPtr, 16000);
            break;
        }
    default:
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Unsupported sampling frequency for Speex");

            return -1;
        }
    }

    SET_SPEEX_FUNCTIONS((codecDef));
    return 0;
}

ACMGenericCodec*
ACMSPEEX::CreateInstance(void)
{
    return NULL;
}

WebRtc_Word16
ACMSPEEX::InternalCreateEncoder()
{
    return WebRtcSpeex_CreateEnc(&_encoderInstPtr, _samplingFrequency);
}

void
ACMSPEEX::DestructEncoderSafe()
{
    if(_encoderInstPtr != NULL)
    {
        WebRtcSpeex_FreeEnc(_encoderInstPtr);
        _encoderInstPtr = NULL;
    }
    // there is no encoder set the following
    _encoderExist = false;
    _encoderInitialized = false;
    _encodingRate = 0;
}


WebRtc_Word16
ACMSPEEX::InternalCreateDecoder()
{
    return WebRtcSpeex_CreateDec(&_decoderInstPtr, _samplingFrequency, 1);
}

void
ACMSPEEX::DestructDecoderSafe()
{
    if(_decoderInstPtr != NULL)
    {
        WebRtcSpeex_FreeDec(_decoderInstPtr);
        _decoderInstPtr = NULL;
    }
    // there is no encoder instance set the followings
    _decoderExist = false;
    _decoderInitialized = false;
}

WebRtc_Word16
ACMSPEEX::SetBitRateSafe(
    const WebRtc_Word32 rate)
{
    // Check if changed rate
    if (rate == _encodingRate) {
        return 0;
    } else if (rate > 2000) {
        _encodingRate = rate;
        _encoderParams.codecInstant.rate = rate;
    } else {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
        "Unsupported encoding rate for Speex");

        return -1;
    }

    return 0;
}


void
ACMSPEEX::InternalDestructEncoderInst(
    void* ptrInst)
{
    if(ptrInst != NULL)
    {
        WebRtcSpeex_FreeEnc((SPEEX_encinst_t_*)ptrInst);
    }
    return;
}


WebRtc_Word16
ACMSPEEX::UnregisterFromNetEqSafe(
    ACMNetEQ*     netEq,
    WebRtc_Word16 payloadType)
{
    if(payloadType != _decoderParams.codecInstant.pltype)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Cannot unregister codec %s given payload-type %d does not match \
the stored payload type",
            _decoderParams.codecInstant.plname,
            payloadType,
            _decoderParams.codecInstant.pltype);
        return -1;
    }


    switch(_samplingFrequency)
    {
    case 8000:
        {
            return netEq->RemoveCodec(kDecoderSPEEX_8);
        }
    case 16000:
        {
            return netEq->RemoveCodec(kDecoderSPEEX_16);
        }
    default:
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Could not unregister Speex from NetEQ. Sampling frequency doesn't match");
            return -1;
        }
    }
}


#ifdef UNUSEDSPEEX

// This API is currently not in use. If requested to be able to enable/disable VBR
// an ACM API need to be added.
WebRtc_Word16
ACMSPEEX::EnableVBR()
{
    if(_vbrEnabled)
    {
        return 0;
    }
    else if(_encoderExist)  // check if encoder exist
    {
        // enable Variable Bit Rate (VBR)
        if(WebRtcSpeex_EncoderInit(_encoderInstPtr, 1, _complMode, (_dtxEnabled? 1:0)) < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Cannot enable VBR mode for Speex");

            return -1;
        }
        _vbrEnabled = true;
        return 0;
    }
    else
    {
        return -1;
    }
}


// This API is currently not in use. If requested to be able to enable/disable VBR
// an ACM API need to be added.
WebRtc_Word16
ACMSPEEX::DisableVBR()
{
    if(!_vbrEnabled)
    {
        return 0;
    }
    else if(_encoderExist)  // check if encoder exist
    {
        // disable DTX
        if(WebRtcSpeex_EncoderInit(_encoderInstPtr, 0, _complMode, (_dtxEnabled? 1:0)) < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Cannot disable DTX for Speex");

            return -1;
        }
        _vbrEnabled = false;
        return 0;
    }
    else
    {
        // encoder doesn't exists, therefore disabling is harmless
        return 0;
    }
}

// This API is currently not in use. If requested to be able to set complexity
// an ACM API need to be added.
WebRtc_Word16
ACMSPEEX::SetComplMode(
    WebRtc_Word16 mode)
{
    // Check if new mode
    if(mode == _complMode)
    {
        return 0;
    }
    else if(_encoderExist)  // check if encoder exist
    {
        // Set new mode
        if(WebRtcSpeex_EncoderInit(_encoderInstPtr, 0, mode, (_dtxEnabled? 1:0)) < 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _uniqueID,
            "Error in complexity mode for Speex");
            return -1;
        }
        _complMode = mode;
        return 0;
    }
    else
    {
        // encoder doesn't exists, therefore disabling is harmless
        return 0;
    }
}

#endif

#endif

} // namespace webrtc
