#include "audio_prompt_sbc.h"
#include "hal_trace.h"
#include "string.h"
#include "app_bt_stream.h"
#include "app_bt_media_manager.h"
#include "app_utils.h"
#include "cmsis.h"


#include "bt_drv_interface.h"
#include "audioflinger.h"
#include "app_media_player.h"
#if defined(IBRT)
#include "app_tws_ibrt.h"
#include "app_tws_ibrt_cmd_handler.h"
#include "app_tws_ctrl_thread.h"
#include "app_ibrt_if.h"
#endif
#include "app_audio.h"
#include "apps.h"
#ifdef MIX_AUDIO_PROMPT_WITH_A2DP_MEDIA_ENABLED

#define AUDIO_PROMPT_RESAMPLE_ITER_NUM               256

#ifdef TWS_PROMPT_SYNC
extern void tws_sync_mix_prompt_start_handling(void);
extern void tws_reset_mix_prompt_trigger_ticks(void);
extern void tws_enable_mix_prompt(bool isEnable);
#endif


extern void app_stop_a2dp_media_stream(uint8_t devId);
extern void app_stop_sco_media_stream(uint8_t devId);
extern void app_bt_stream_copy_track_one_to_two_16bits(int16_t *dst_buf, int16_t *src_buf, uint32_t src_len);

static uint32_t audio_prompt_sbc_decode(uint8_t* pcm_buffer, uint32_t expectedOutputSize, uint8_t isReset);

static btif_sbc_decoder_t audio_prompt_sbc_decoder;

static float audio_prompt_sbc_eq_band_gain[8] = {1, 1, 1, 1, 1, 1, 1, 1};

// keep the current volume
#define KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT          -1

// from TGT_VOLUME_LEVEL_T or KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT
#define DEFAULT_VOLUME_FOR_MIX_PROMPT               KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT

#define DEFAULT_COEFF_FOR_MIX_PROMPT_FOR_MUSIC      1.0
#define DEFAULT_COEFF_FOR_MIX_MUSIC_FOR_MUSIC       0.4
#define DEFAULT_COEFF_FOR_MIX_PROMPT_FOR_CALL       1.0
#define DEFAULT_COEFF_FOR_MIX_CALL_FOR_CALL         0.4

#define DEFAULT_OVERLAP_LENGTH 128

static int audio_prompt_sbc_init_decoder(void)
{
    btif_sbc_init_decoder(&audio_prompt_sbc_decoder);
    return 0;
}

typedef struct
{
    uint32_t    wholeEncodedDataLen;
    uint32_t    leftEncodedDataLen;
    uint8_t*    promptDataBuf;
    uint16_t    tmpSourcePcmDataLen;
    uint16_t    tmpSourcePcmDataOutIndex;
    uint8_t*    tmpSourcePcmDataBuf;
    uint8_t*    tmpTargetPcmDataBuf;
    CQueue      pcmDataQueue;
    uint8_t     isResetDecoder;
    uint8_t     isAudioPromptDecodingDone;
    uint8_t     targetBytesCntPerSample;
    uint32_t    targetSampleRate;
    struct APP_RESAMPLE_T * resampler;
    uint32_t    targetPcmChunkSize;
    float       resampleRatio;
    uint8_t*    bufForResampler;
    uint32_t    resampleBufLen;
    uint8_t     pendingStopOp;
    int8_t      savedStoppedStreamId;
    uint8_t     targetChannelCnt;
    uint8_t     mixType;
    uint16_t    promptId;
    int16_t     volume_level_override;
    uint16_t    promptPram;
    float       coeff_for_mix_prompt_for_music;
    float       coeff_for_mix_music_for_music;
    float       coeff_for_mix_prompt_for_call;
    float       coeff_for_mix_call_for_call;
    uint8_t     isMixPromptOn;
    int16_t     currentVol;
    uint16_t    mergeInOverlapLength;
    uint16_t    mergeOutOverlapLength;
    float       mergeInWeight;
    float       mergeOutWeight;
    float       mergeStep;
} AUDIO_PROMPT_ENV_T;

static AUDIO_PROMPT_ENV_T audio_prompt_env;

static void audio_prompt_set_pending_stop_op(uint8_t op)
{
    audio_prompt_env.pendingStopOp = op;
    TRACE(1,"pendingStopOp is set to %d", op);
}

static uint8_t audio_prompt_get_pending_stop_op(void)
{
    return audio_prompt_env.pendingStopOp;
}

static void audio_prompt_set_saved_stopped_stream_id(int8_t id)
{
    audio_prompt_env.savedStoppedStreamId = id;
    TRACE(1,"savedStoppedStreamId is set to %d", id);
}

static int8_t audio_prompt_get_saved_stopped_stream_id(void)
{
    return audio_prompt_env.savedStoppedStreamId;
}

void audio_prompt_init_handler(void)
{
    memset((uint8_t *)&audio_prompt_env, 0, sizeof(audio_prompt_env));
    audio_prompt_set_saved_stopped_stream_id(-1);
}

bool audio_prompt_is_allow_update_volume(void)
{
    bool isAllow = true;

    uint32_t lock = int_lock_global();

    isAllow= (!(audio_prompt_env.isMixPromptOn))||
        (KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT ==
        audio_prompt_env.volume_level_override);

    int_unlock_global(lock);

    return isAllow;
}

bool audio_prompt_is_playing_ongoing(void)
{
    bool isPlayingOnGoing = false;
    uint32_t lock = int_lock_global();
    if (audio_prompt_env.isMixPromptOn)
    {
        isPlayingOnGoing = true;
    }
    int_unlock_global(lock);
    return isPlayingOnGoing;
}

#ifdef TWS_PROMPT_SYNC

#define MEDIA_SHORT_TRIGGER_DELAY       8
#define MEDIA_LONG_TRIGGER_DELAY        12

#define PROMPT_TICKS_OFFSET_TO_TRIGGER_MIX  10  // 3.25 ms

static uint32_t mix_prompt_trigger_ticks = 0;
static uint32_t playback_interval_in_ticks = 0;
static uint32_t playback_last_irq_ticks = 0;
static uint8_t isStartMixPrompt = false;

static uint32_t get_prompt_trigger_delay(uint16_t promptId)
{
    switch (promptId)
    {
        case AUDIO_ID_BT_GSOUND_MIC_OPEN:
        case AUDIO_ID_BT_GSOUND_MIC_CLOSE:
        // TODO: add more case for specific prompts here if you wanna it played sooner
            return MEDIA_SHORT_TRIGGER_DELAY;
        default:
            return MEDIA_LONG_TRIGGER_DELAY;
    }
}

bool tws_calculate_mix_prompt_trigger_ticks(uint16_t promptId)
{
    if (0 == mix_prompt_trigger_ticks)
    {
        if ((playback_interval_in_ticks > 0)&&(playback_last_irq_ticks > 0))
        {
            mix_prompt_trigger_ticks = playback_last_irq_ticks+
                get_prompt_trigger_delay(promptId)*playback_interval_in_ticks;
            //TRACE(1,"playback_last_irq_ticks %d",playback_last_irq_ticks);
            //TRACE(2,"playback_interval_in_ticks %d mix_prompt_trigger_ticks %d",playback_interval_in_ticks, mix_prompt_trigger_ticks);
            return true;
        }
    }

    return false;
}

void tws_enable_mix_prompt(bool isEnable)
{
    TRACE(1,"isStartMixPrompt to %d.", isEnable);
    isStartMixPrompt = isEnable;
}

void tws_set_mix_prompt_trigger_ticks(uint32_t ticks)
{
    mix_prompt_trigger_ticks = ticks;
}

void tws_sync_mix_prompt_start_handling(void)
{
    if (!app_tws_ibrt_tws_link_connected())
    {
        tws_enable_mix_prompt(true);
    }
    else
    {
        tws_enable_mix_prompt(false);
    }
}

bool tws_is_mix_prompt_allowed_to_start(void)
{
    return isStartMixPrompt;
}

void app_ibrt_send_mix_prompt_req(void)
{
    if (app_tws_ibrt_tws_link_connected())
    {
        APP_TWS_CMD_MIX_PROMPT_SYNC_T req;
        req.promptId = audio_prompt_get_prompt_id();
        req.promptPram = audio_prompt_env.promptPram;
        req.trigger_time = mix_prompt_trigger_ticks;
        req.sampleRate = audio_prompt_get_sample_rate();
        tws_ctrl_send_cmd(APP_TWS_CMD_SYNC_MIX_PROMPT_REQ,(uint8_t*)&req,sizeof(APP_TWS_CMD_MIX_PROMPT_SYNC_T));
    }
}

bool tws_sync_mix_prompt_handling(void)
{
    if (!tws_is_mix_prompt_allowed_to_start())
    {
        if (tws_calculate_mix_prompt_trigger_ticks(audio_prompt_get_prompt_id()))
        {
            // get the trigger ticks, send request to slave
            app_ibrt_send_mix_prompt_req();
        }
        return false;
    }
    else
    {
        return true;
    }
}

uint32_t tws_get_mix_prompt_trigger_ticks(void)
{
    return mix_prompt_trigger_ticks;
}

void tws_reset_mix_prompt_trigger_ticks(void)
{
    mix_prompt_trigger_ticks = 0;
    playback_interval_in_ticks = 0;
    playback_last_irq_ticks = 0;
    isStartMixPrompt = false;
}

void app_tws_stop_peer_prompt(void)
{
    uint8_t stub_param = 0;

    tws_ctrl_send_cmd(APP_TWS_CMD_STOP_PEER_PROMPT_REQ, &stub_param, sizeof(stub_param));
}

void tws_playback_ticks_check_for_mix_prompt(void)
{
    if ((!audio_prompt_is_playing_ongoing()) || tws_is_mix_prompt_allowed_to_start())
    {
        //TRACE(0,"check for mix prompt<1>");
        return;
    }

    uint32_t curr_ticks = 0;
    uint16_t conhandle = INVALID_HANDLE;
    ibrt_ctrl_t *p_ibrt_ctrl = app_tws_ibrt_get_bt_ctrl_ctx();

    uint32_t btclk;
    uint16_t btcnt;
    int32_t offset;

    if (app_tws_ibrt_mobile_link_connected()){
        conhandle = p_ibrt_ctrl->mobile_conhandle;
    }else if (app_tws_ibrt_slave_ibrt_link_connected()){
        conhandle = p_ibrt_ctrl->ibrt_conhandle;
    }else{
        TRACE(0,"check for mix prompt<2>");
        tws_enable_mix_prompt(true);
        return;
    }

    bt_drv_reg_op_dma_tc_clkcnt_get(&btclk, &btcnt);
    if(conhandle>=0x80){
        offset = bt_syn_get_offset_ticks(conhandle);
    }else{
        offset = 0;
    }
    curr_ticks = (2*btclk + offset) & 0x0fffffff;

    if (mix_prompt_trigger_ticks > 0)
    {
        if (curr_ticks >= mix_prompt_trigger_ticks)
        {
            TRACE(2,"ticks<1> %d - trigger ticks %d", curr_ticks,mix_prompt_trigger_ticks);
            tws_enable_mix_prompt(true);
        }
        else if ((curr_ticks < mix_prompt_trigger_ticks) &&
                ((mix_prompt_trigger_ticks - curr_ticks) < PROMPT_TICKS_OFFSET_TO_TRIGGER_MIX))
        {
            TRACE(2,"ticks<2> %d - trigger ticks %d", curr_ticks,mix_prompt_trigger_ticks);
            tws_enable_mix_prompt(true);
        }
    }

    if (0 != playback_last_irq_ticks)
    {
       playback_interval_in_ticks = curr_ticks-playback_last_irq_ticks;
    }

    playback_last_irq_ticks = curr_ticks;
}

void app_tws_cmd_sync_mix_prompt_req_handler(uint8_t* ptrParam, uint16_t paramLen)
{
    uint16_t promptPram = 0xFFFF;
    uint32_t curr_ticks = 0xFFFFFFFF;
    ibrt_ctrl_t *p_ibrt_ctrl = app_tws_ibrt_get_bt_ctrl_ctx();

    APP_TWS_CMD_MIX_PROMPT_SYNC_T* pReq = (APP_TWS_CMD_MIX_PROMPT_SYNC_T *)ptrParam;
    TRACE(2,"promptId, trigger_time:0x%x, %d",
        pReq->promptId, pReq->trigger_time);

    audio_prompt_stop_playing();

    if (app_tws_ibrt_slave_ibrt_link_connected())
    {
        curr_ticks = bt_syn_get_curr_ticks(p_ibrt_ctrl->ibrt_conhandle);
    }

    if ((!app_tws_ibrt_slave_ibrt_link_connected()) ||
        ((curr_ticks > pReq->trigger_time) &&
         ((curr_ticks - pReq->trigger_time) > PROMPT_TICKS_OFFSET_TO_TRIGGER_MIX)))
    {
        TRACE(0,"return directly.");
        return;
    }

    tws_set_mix_prompt_trigger_ticks(pReq->trigger_time);
    promptPram = pReq->promptId | pReq->promptPram;
    audio_prompt_start_playing(promptPram, pReq->sampleRate);
    if (curr_ticks >= pReq->trigger_time)
    {
        TRACE(1,"Instant passed %d",curr_ticks);
        tws_enable_mix_prompt(true);
    }
    else if ((curr_ticks < pReq->trigger_time) &&
        ((pReq->trigger_time - curr_ticks) < PROMPT_TICKS_OFFSET_TO_TRIGGER_MIX)){
        TRACE(1,"Instant near %d",curr_ticks);
        tws_enable_mix_prompt(true);
    }
}
#endif

static int audio_prompt_resample_iter(uint8_t *buf, uint32_t len)
{
    if (!buf)
    {
        TRACE(0, "NULL pointer received in %s", __func__);
        return -1;
    }

    uint32_t leftLen = audio_prompt_env.tmpSourcePcmDataLen - audio_prompt_env.tmpSourcePcmDataOutIndex;
    uint32_t lenToFetch;

    if (leftLen >= len)
    {
        lenToFetch = len;
    }
    else
    {
        lenToFetch = leftLen;
    }

    memcpy(buf, audio_prompt_env.tmpSourcePcmDataBuf + audio_prompt_env.tmpSourcePcmDataOutIndex,
        lenToFetch);
    audio_prompt_env.tmpSourcePcmDataOutIndex += lenToFetch;

    memset(buf + lenToFetch, 0, len - lenToFetch);

    return 0;
}

void audio_prompt_buffer_config(uint8_t mixType,
                                uint8_t channel_cnt,
                                uint8_t bitNumPerSample,
                                uint8_t *tmpSourcePcmDataBuf,
                                uint8_t *tmpTargetPcmDataBuf,
                                uint8_t *pcmDataBuf,
                                uint32_t pcmBufLen,
                                uint8_t *bufForResampler,
                                uint32_t resampleBufLen)
{
    af_lock_thread();
    audio_prompt_env.mixType = mixType;
    audio_prompt_env.targetChannelCnt = channel_cnt;
    if (24 == bitNumPerSample)
    {
        audio_prompt_env.targetBytesCntPerSample = 4;
    }
    else if (16 == bitNumPerSample)
    {
        audio_prompt_env.targetBytesCntPerSample = 2;
    }
    else
    {
        ASSERT(false, "bitNumPerSample %d is not supported by prompt mixer yet!", bitNumPerSample);
    }

    audio_prompt_env.tmpSourcePcmDataBuf = tmpSourcePcmDataBuf;
    audio_prompt_env.tmpTargetPcmDataBuf = tmpTargetPcmDataBuf;
    audio_prompt_env.bufForResampler = bufForResampler;
    audio_prompt_env.resampleBufLen = resampleBufLen;

    InitCQueue(&(audio_prompt_env.pcmDataQueue), pcmBufLen, (CQItemType *)(pcmDataBuf));
    af_unlock_thread();
}


uint16_t audio_prompt_get_prompt_id(void)
{
    return audio_prompt_env.promptId;
}

uint32_t audio_prompt_get_sample_rate(void)
{
    return audio_prompt_env.targetSampleRate;
}

bool audio_prompt_start_playing(uint16_t promptPram, uint32_t targetSampleRate)
{
    uint16_t promptId = PROMPT_ID_FROM_ID_VALUE(promptPram);

    if (audio_prompt_is_playing_ongoing())
    {
        return false;
    }

    TRACE(0, "[%s]", __func__);

#ifdef TWS_PROMPT_SYNC
    bool isPlayingLocally = false;
    if (app_tws_ibrt_tws_link_connected())
    {
        isPlayingLocally = false;
    }
    else
    {
        isPlayingLocally = true;
    }
#endif

    promptId = PROMPT_ID_FROM_ID_VALUE(promptId);

    uint32_t lock = int_lock_global();
    audio_prompt_env.isMixPromptOn = true;

    uint8_t* promptDataPtr = NULL;
    uint32_t promptDataLen = 0;
    PROMPT_MIX_PROPERTY_T* pPromptProperty = NULL;

#ifdef MEDIA_PLAYER_SUPPORT
    media_runtime_audio_prompt_update(promptId, &promptDataPtr, &promptDataLen);
    pPromptProperty = get_prompt_mix_property(promptId);
#endif

    if (NULL == pPromptProperty)
    {
        TRACE(0,"use default mix property");

        // use default property
        audio_prompt_env.volume_level_override = DEFAULT_VOLUME_FOR_MIX_PROMPT;

        audio_prompt_env.coeff_for_mix_prompt_for_call = DEFAULT_COEFF_FOR_MIX_PROMPT_FOR_CALL;
        audio_prompt_env.coeff_for_mix_call_for_call = DEFAULT_COEFF_FOR_MIX_CALL_FOR_CALL;
        audio_prompt_env.coeff_for_mix_prompt_for_music = DEFAULT_COEFF_FOR_MIX_PROMPT_FOR_MUSIC;
        audio_prompt_env.coeff_for_mix_music_for_music = DEFAULT_COEFF_FOR_MIX_MUSIC_FOR_MUSIC;
    }
    else
    {
        audio_prompt_env.volume_level_override =
            pPromptProperty->volume_level_override;

        audio_prompt_env.coeff_for_mix_prompt_for_call =
            pPromptProperty->coeff_for_mix_prompt_for_call;
        audio_prompt_env.coeff_for_mix_call_for_call =
            pPromptProperty->coeff_for_mix_call_for_call;
        audio_prompt_env.coeff_for_mix_prompt_for_music =
            pPromptProperty->coeff_for_mix_prompt_for_music;
        audio_prompt_env.coeff_for_mix_music_for_music =
            pPromptProperty->coeff_for_mix_music_for_music;
    }

    if (KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT != audio_prompt_env.volume_level_override)
    {
        // if the prompt's volume is smaller than the current volume, don't change it
        if (audio_prompt_env.volume_level_override <= app_bt_stream_local_volume_get())
        {
            audio_prompt_env.volume_level_override = KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT;
        }
    }

    audio_prompt_env.promptId = promptId;
    audio_prompt_env.promptPram = PROMPT_PRAM_FROM_ID_VALUE(promptPram);
    audio_prompt_env.promptDataBuf = promptDataPtr;
    audio_prompt_env.isResetDecoder = true;
    audio_prompt_env.isAudioPromptDecodingDone = false;
    audio_prompt_env.wholeEncodedDataLen = promptDataLen;
    audio_prompt_env.leftEncodedDataLen = audio_prompt_env.wholeEncodedDataLen;
    audio_prompt_env.targetSampleRate = targetSampleRate;

    audio_prompt_env.resampleRatio = ((float)AUDIO_PROMPT_SBC_SAMPLE_RATE_VALUE) / audio_prompt_env.targetSampleRate;
    audio_prompt_env.targetPcmChunkSize = (uint32_t)(AUDIO_PROMPT_SBC_PCM_DATA_SIZE_PER_FRAME/audio_prompt_env.resampleRatio);

    audio_prompt_env.resampler = app_playback_resample_any_open_with_pre_allocated_buffer(( enum AUD_CHANNEL_NUM_T )AUDIO_PROMPT_SBC_CHANNEL_COUNT,
                                                                                          audio_prompt_resample_iter,
                                                                                          AUDIO_PROMPT_RESAMPLE_ITER_NUM,
                                                                                          audio_prompt_env.resampleRatio,
                                                                                          audio_prompt_env.bufForResampler,
                                                                                          audio_prompt_env.resampleBufLen);

    // audio resample out size should be even
    uint16_t targetOverlapLength = ((uint16_t)(DEFAULT_OVERLAP_LENGTH / audio_prompt_env.resampleRatio) / 2 * 2);
    audio_prompt_env.mergeInOverlapLength = targetOverlapLength * audio_prompt_env.targetChannelCnt;
    audio_prompt_env.mergeOutOverlapLength = targetOverlapLength * audio_prompt_env.targetChannelCnt;
    audio_prompt_env.mergeInWeight = 0.f;
    audio_prompt_env.mergeOutWeight = 1.f;
    audio_prompt_env.mergeStep = 1.f / (targetOverlapLength - 1);

    audio_prompt_set_saved_stopped_stream_id(-1);

    int_unlock_global(lock);

    TRACE(1,"start audio prompt. target sample rate %d", targetSampleRate);

    app_sysfreq_req(APP_SYSFREQ_USER_PROMPT_MIXER, APP_SYSFREQ_104M);

#ifdef TWS_PROMPT_SYNC
    if (!isPlayingLocally)
    {
        tws_sync_mix_prompt_start_handling();
    }
    else
    {
        tws_enable_mix_prompt(true);
    }
#endif


    return true;
}

void audio_prompt_forcefully_stop(void)
{
    app_playback_resample_close(audio_prompt_env.resampler);
    audio_prompt_set_saved_stopped_stream_id(-1);
    audio_prompt_env.isAudioPromptDecodingDone  = true;
    audio_prompt_env.leftEncodedDataLen = 0;
    app_sysfreq_req(APP_SYSFREQ_USER_PROMPT_MIXER, APP_SYSFREQ_32K);
#if defined(IBRT) && defined(MEDIA_PLAYER_SUPPORT)
    app_tws_sync_prompt_check();
#endif
}

bool audio_prompt_check_on_stopping_stream(uint8_t pendingStopOp, uint8_t deviceId)
{
    uint32_t lock = int_lock_global();
    if (audio_prompt_env.isMixPromptOn)
    {
        if (bt_is_playback_triggered())
        {
            TRACE(1,"Prompt mixing ongoing, pending op:%d", pendingStopOp);
            audio_prompt_env.pendingStopOp        = pendingStopOp;
            audio_prompt_set_saved_stopped_stream_id(deviceId);
            int_unlock_global(lock);
            return false;
        }
        else
        {
            int_unlock_global(lock);
            audio_prompt_stop_playing();
            return true;
        }
    }

    int_unlock_global(lock);
    return true;
}

bool audio_prompt_clear_pending_stream(uint8_t op)
{
    bool isToClearActiveMedia = false;

    TRACE(4,
          "%s stop_id %d pendingStopOp %d op %d",
          __func__,
          audio_prompt_get_saved_stopped_stream_id(),
          audio_prompt_env.pendingStopOp,
          op);

    if (-1 != audio_prompt_get_saved_stopped_stream_id())
    {
        uint32_t lock = int_lock_global();
        if ((PENDING_TO_STOP_A2DP_STREAMING == op) &&
            (PENDING_TO_STOP_A2DP_STREAMING == audio_prompt_get_pending_stop_op()))
        {
            audio_prompt_set_saved_stopped_stream_id(-1);
            audio_prompt_set_pending_stop_op(PENDING_TO_STOP_STREAM_INVALID);
            isToClearActiveMedia = true;
        }
        else if ((PENDING_TO_STOP_SCO_STREAMING == op) &&
                 (PENDING_TO_STOP_SCO_STREAMING == audio_prompt_get_pending_stop_op()))
        {
            audio_prompt_set_saved_stopped_stream_id(-1);
            audio_prompt_set_pending_stop_op(PENDING_TO_STOP_STREAM_INVALID);
            isToClearActiveMedia = true;
        }
        int_unlock_global(lock);
    }

    return isToClearActiveMedia;
}

void audio_prompt_stop_playing(void)
{
    if (!audio_prompt_env.isMixPromptOn)
    {
        return;
    }

    TRACE(0,"Stop audio prompt.");

    app_playback_resample_close(audio_prompt_env.resampler);

    uint32_t lock = int_lock_global();

    audio_prompt_env.leftEncodedDataLen = 0;
    audio_prompt_env.isMixPromptOn = false;

#ifdef TWS_PROMPT_SYNC
    tws_reset_mix_prompt_trigger_ticks();
#endif

    int_unlock_global(lock);

    if (KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT != audio_prompt_env.volume_level_override)
    {
        // restore the volume
        app_bt_stream_volumeset_handler(app_bt_stream_local_volume_get());
    }

    lock = int_lock_global();

    uint8_t pendingStopOp;
    int8_t savedStoppedStreamId;

    pendingStopOp = audio_prompt_env.pendingStopOp;
    savedStoppedStreamId = audio_prompt_get_saved_stopped_stream_id();

    audio_prompt_set_saved_stopped_stream_id(-1);

    int_unlock_global(lock);

    if (savedStoppedStreamId >= 0)
    {
        if (PENDING_TO_STOP_A2DP_STREAMING == pendingStopOp)
        {
            TRACE(0,"Stop the pending stopped a2dp media stream.");
            app_stop_a2dp_media_stream(savedStoppedStreamId);
        }
        else if (PENDING_TO_STOP_SCO_STREAMING == pendingStopOp)
        {
            TRACE(0,"Stop the pending stopped sco media stream.");
            app_stop_sco_media_stream(savedStoppedStreamId);
        }
    }

    app_sysfreq_req(APP_SYSFREQ_USER_PROMPT_MIXER, APP_SYSFREQ_32K);

#if defined(IBRT) && defined(MEDIA_PLAYER_SUPPORT)
    app_tws_sync_prompt_check();
#endif
    //check if there is any pending prompt need to play
    APP_AUDIO_STATUS status_next;
    APP_AUDIO_STATUS aud_status;
    aud_status.id = APP_PLAY_BACK_AUDIO;
    if (app_audio_list_rmv_callback(&aud_status, &status_next,APP_BT_SETTING_Q_POS_HEAD, true))
    {
        TRACE(4,"%s next id: 0x%x%s, aud_id %d", __func__, status_next.id, player2str(status_next.id), status_next.aud_id);
#if defined(IBRT)
        app_ibrt_if_voice_report_handler(status_next.aud_id, true);
#endif
    }

}

template<typename DstType>
static inline DstType prompt_data_ssat(int32_t in)
{
    DstType out;

    if (sizeof(DstType) == 2) {
        out = __SSAT(in, 16);
    } else {
        out = __SSAT(in, 24);
    }

    return out;
}

template<typename DstType>
static inline DstType prompt_data_extend(int16_t in)
{
    DstType out;

    if (sizeof(DstType) == 2) {
        out = in;
    } else {
        out = (in << 8);
    }

    return out;
}

template<typename DstType>
static void audio_prompt_crossfade(DstType *dst_buf, DstType *src_buf1, int16_t *src_buf0,
                                   float coeff_for_source, float coeff_for_prompt,
                                   uint32_t merge_in_end, uint32_t merge_out_start, uint32_t src_len)
{
    uint32_t i = 0;
    if (audio_prompt_env.targetChannelCnt == 2) {
        float weight = audio_prompt_env.mergeInWeight;
        for (; i < merge_in_end; i += 2) {
            /* TODO: increase coeff by step instead of calcualted every step */
            float coeff0 = 1 - weight + weight * coeff_for_source;
            float coeff1 = weight * coeff_for_prompt;

            float tmp0 = coeff0 * src_buf1[i] + coeff1 * prompt_data_extend<DstType>(src_buf0[i]);
            float tmp1 = coeff0 * src_buf1[i + 1] + coeff1 * prompt_data_extend<DstType>(src_buf0[i + 1]);
            dst_buf[i] = prompt_data_ssat<DstType>((int32_t)tmp0);
            dst_buf[i + 1] = prompt_data_ssat<DstType>((int32_t)tmp1);

            weight += audio_prompt_env.mergeStep;
        }
        audio_prompt_env.mergeInOverlapLength -= merge_in_end;
        audio_prompt_env.mergeInWeight = weight;
    } else if (audio_prompt_env.targetChannelCnt == 1) {
        float weight = audio_prompt_env.mergeInWeight;
        for (; i < merge_in_end; i++) {
            float coeff0 = 1 - weight + weight * coeff_for_source;
            float coeff1 = weight * coeff_for_prompt;

            float tmp0 = coeff0 * src_buf1[i] + coeff1 * prompt_data_extend<DstType>(src_buf0[i]);
            dst_buf[i] = prompt_data_ssat<DstType>((int32_t)tmp0);

            weight += audio_prompt_env.mergeStep;
        }
        audio_prompt_env.mergeInOverlapLength -= merge_in_end;
        audio_prompt_env.mergeInWeight = weight;
    } else {
        ASSERT(0, "[%s] channel number %d not supported", __FUNCTION__, audio_prompt_env.targetChannelCnt);
    }
    for (; i < merge_out_start; i++) {
        float mix_value = coeff_for_prompt * prompt_data_extend<DstType>(src_buf0[i]) +
                          coeff_for_source * src_buf1[i];
        dst_buf[i] = prompt_data_ssat<DstType>((int32_t)mix_value);
    }
    if (audio_prompt_env.targetChannelCnt == 2) {
        float weight = audio_prompt_env.mergeOutWeight;
        for (; i < src_len; i += 2) {
            float coeff0 = (1 - weight + weight * coeff_for_source);
            float coeff1 = weight * coeff_for_prompt;

            float tmp0 = coeff0 * src_buf1[i] + coeff1 * prompt_data_extend<DstType>(src_buf0[i]);
            float tmp1 = coeff0 * src_buf1[i + 1] + coeff1 * prompt_data_extend<DstType>(src_buf0[i + 1]);
            dst_buf[i] = prompt_data_ssat<DstType>((int32_t)tmp0);
            dst_buf[i + 1] = prompt_data_ssat<DstType>((int32_t)tmp1);

            weight -= audio_prompt_env.mergeStep;
        }
        audio_prompt_env.mergeOutOverlapLength -= src_len - merge_out_start;
        audio_prompt_env.mergeOutWeight = weight;
    } else if (audio_prompt_env.targetChannelCnt == 1) {
        float weight = audio_prompt_env.mergeOutWeight;
        for (; i < src_len; i++) {
            float coeff0 = (1 - weight + weight * coeff_for_source);
            float coeff1 = weight * coeff_for_prompt;

            float tmp0 = coeff0 * src_buf1[i] + coeff1 * prompt_data_extend<DstType>(src_buf0[i]);
            dst_buf[i] = prompt_data_ssat<DstType>((int32_t)tmp0);

            weight -= audio_prompt_env.mergeStep;
        }
        audio_prompt_env.mergeOutOverlapLength -= src_len - merge_out_start;
        audio_prompt_env.mergeOutWeight = weight;
    } else {
        ASSERT(0, "[%s] channel number %d not supported", __FUNCTION__, audio_prompt_env.targetChannelCnt);
    }
}

static void audio_prompt_processing_handler_func(uint32_t acquiredPcmDataLen, uint8_t* pcmDataToMerge)
{
#ifdef TWS_PROMPT_SYNC
    uint16_t prompt_chnlsel = PROMPT_CHNLSEl_FROM_ID_VALUE(audio_prompt_env.promptPram);
#endif

    uint32_t pcmDataToGetFromPrompt = acquiredPcmDataLen/(audio_prompt_env.targetChannelCnt*audio_prompt_env.targetBytesCntPerSample/2);

    while ((uint32_t)LengthOfCQueue(&(audio_prompt_env.pcmDataQueue)) < pcmDataToGetFromPrompt)
    {
        if (audio_prompt_env.isAudioPromptDecodingDone)
        {
            break;
        }

        // decode the audio prompt
        uint32_t returnedPcmDataLen = audio_prompt_sbc_decode(audio_prompt_env.tmpSourcePcmDataBuf,
                                                              AUDIO_PROMPT_SBC_PCM_DATA_SIZE_PER_FRAME,
                                                              audio_prompt_env.isResetDecoder);

        if (returnedPcmDataLen < AUDIO_PROMPT_SBC_PCM_DATA_SIZE_PER_FRAME)
        {
            audio_prompt_env.isAudioPromptDecodingDone = true;
        }

        audio_prompt_env.isResetDecoder = false;
        audio_prompt_env.tmpSourcePcmDataLen = returnedPcmDataLen;
        audio_prompt_env.tmpSourcePcmDataOutIndex = 0;

        // do resmpling
        if (audio_prompt_env.targetSampleRate != AUDIO_PROMPT_SBC_SAMPLE_RATE_VALUE)
        {
            uint32_t targetPcmSize = returnedPcmDataLen;
            if (AUDIO_PROMPT_SBC_PCM_DATA_SIZE_PER_FRAME == returnedPcmDataLen)
            {
                targetPcmSize = audio_prompt_env.targetPcmChunkSize;
            }
            else
            {
                targetPcmSize = (uint32_t)(returnedPcmDataLen/audio_prompt_env.resampleRatio);
            }

            targetPcmSize = (targetPcmSize/4)*4;

            app_playback_resample_run(audio_prompt_env.resampler,
                                      audio_prompt_env.tmpTargetPcmDataBuf,
                                      targetPcmSize);

            // fill into pcm data queue
            EnCQueue(&(audio_prompt_env.pcmDataQueue), audio_prompt_env.tmpTargetPcmDataBuf, targetPcmSize);
        }
        else
        {
            EnCQueue(&(audio_prompt_env.pcmDataQueue), audio_prompt_env.tmpSourcePcmDataBuf, returnedPcmDataLen);
        }
    }

    uint32_t pcmDataLenToMerge = pcmDataToGetFromPrompt;
    if ((uint32_t)LengthOfCQueue(&(audio_prompt_env.pcmDataQueue)) < pcmDataToGetFromPrompt)
    {
        pcmDataLenToMerge = LengthOfCQueue(&(audio_prompt_env.pcmDataQueue));
    }

    if (pcmDataLenToMerge == 0)
        goto exit;

    // copy to multiple channel if needed
    if (audio_prompt_env.targetChannelCnt > 1)
    {
        // get the data
        DeCQueue(&(audio_prompt_env.pcmDataQueue), audio_prompt_env.tmpSourcePcmDataBuf, pcmDataLenToMerge);
        app_bt_stream_copy_track_one_to_two_16bits(( int16_t * )audio_prompt_env.tmpTargetPcmDataBuf,
                                                   ( int16_t * )audio_prompt_env.tmpSourcePcmDataBuf,
                                                   pcmDataLenToMerge / sizeof(uint16_t));
    }
    else
    {
        DeCQueue(&(audio_prompt_env.pcmDataQueue), audio_prompt_env.tmpTargetPcmDataBuf, pcmDataLenToMerge);
    }

#ifdef TWS_PROMPT_SYNC
    if (IS_PROMPT_CHNLSEl_ALL(prompt_chnlsel) || app_ibrt_voice_report_is_me(prompt_chnlsel))
#endif
    {
        // merge the data
        int16_t *src_buf0 = (int16_t *)audio_prompt_env.tmpTargetPcmDataBuf;
        uint32_t src_len = pcmDataLenToMerge*audio_prompt_env.targetChannelCnt/sizeof(uint16_t);

        float coeff_for_prompt;
        float coeff_for_source;

        if (MIX_WITH_A2DP_STREAMING == audio_prompt_env.mixType) {
            coeff_for_prompt = audio_prompt_env.coeff_for_mix_prompt_for_music;
            coeff_for_source = audio_prompt_env.coeff_for_mix_music_for_music;
        } else {
            coeff_for_prompt = audio_prompt_env.coeff_for_mix_prompt_for_call;
            coeff_for_source = audio_prompt_env.coeff_for_mix_call_for_call;
        }

        /*
         * 0 --------------- merge_in_end --- merge_out_start --- src_len
         * |---- merge in ---------|---- merge ------|----- merge out ---|
         * Split data into three segments, merge in stands for merge begin crossfade, merge out for merge end crossfade.
         * In merge begin crossfade stage, old data is music/sco pcm data, new data is merged data.
         * In merge end crossfade stage, old data is merged data, new data is music/sco data.
         * Assume music/soc is a, ring is b, then merged data is x = m * a + p * b,
         * merge begin final data y = (1 - w) * a + w * x = (1 - w + w * m) * a + w * p * b for w = 0:step:1,
         * merge end final data y = (1 - w) * a + w * x = (1 - w + w * m) * a + w * p * b for w = 1:-step:0.
         */
        uint32_t merge_in_end = 0;
        if (audio_prompt_env.mergeInOverlapLength > 0) {
            TRACE(3, "[%s] bytes_per_sample %d, channel_num %d", __FUNCTION__,
                  audio_prompt_env.targetBytesCntPerSample, audio_prompt_env.targetChannelCnt);
            TRACE(2, "[%s] merge start, remain %d", __FUNCTION__, audio_prompt_env.mergeInOverlapLength);
            merge_in_end = MIN(audio_prompt_env.mergeInOverlapLength, src_len);
        }

        uint32_t merge_out_start = src_len;
        /* TODO: calc remain decoded pcm samples for DEFAULT_OVERLAP_LENGTH > 128 */
        if (audio_prompt_env.leftEncodedDataLen == 0) {
            if (LengthOfCQueue(&(audio_prompt_env.pcmDataQueue)) < audio_prompt_env.mergeOutOverlapLength / audio_prompt_env.targetChannelCnt * (int32_t)sizeof(uint16_t)) {
                TRACE(2, "[%s] merge end, remain %d", __FUNCTION__, audio_prompt_env.mergeOutOverlapLength);
                merge_out_start = src_len - (audio_prompt_env.mergeOutOverlapLength - LengthOfCQueue(&(audio_prompt_env.pcmDataQueue)) / sizeof(uint16_t) * audio_prompt_env.targetChannelCnt);
            }
        }

        //TRACE(3, "[%s] merge_in_end = %d, merge_out_start = %d", __FUNCTION__, merge_in_end, merge_out_start);
        //TRACE(3, "[%s] pcm queue size = %d, src_len = %d", __FUNCTION__, LengthOfCQueue(&(audio_prompt_env.pcmDataQueue)), src_len);

        if (2 == audio_prompt_env.targetBytesCntPerSample)
        {
            int16_t *src_buf1 = (int16_t *)pcmDataToMerge;
            int16_t *dst_buf = (int16_t *)pcmDataToMerge;

            audio_prompt_crossfade<int16_t>(dst_buf, src_buf1, src_buf0,
                                            coeff_for_source, coeff_for_prompt,
                                            merge_in_end, merge_out_start, src_len);
        }
        else if (4 == audio_prompt_env.targetBytesCntPerSample)
        {
            int32_t *src_buf1 = (int32_t *)pcmDataToMerge;
            int32_t *dst_buf = (int32_t *)pcmDataToMerge;

            audio_prompt_crossfade<int32_t>(dst_buf, src_buf1, src_buf0,
                                            coeff_for_source, coeff_for_prompt,
                                            merge_in_end, merge_out_start, src_len);
        }
    }

exit:
    if (audio_prompt_env.mergeOutOverlapLength == 0)
    {
        // prompt playing is completed
        audio_prompt_stop_playing();
        app_sysfreq_req(APP_SYSFREQ_USER_PROMPT_MIXER, APP_SYSFREQ_32K);
    }

}

void audio_prompt_processing_handler(uint32_t acquiredPcmDataLen, uint8_t* pcmDataToMerge)
{
#ifdef TWS_PROMPT_SYNC
    if (!tws_sync_mix_prompt_handling())
    {
        return;
    }
#endif

    af_lock_thread();

    if (audio_prompt_env.leftEncodedDataLen ==
        audio_prompt_env.wholeEncodedDataLen)
    {
        if (KEEP_CURRENT_VOLUME_FOR_MIX_PROMPT != audio_prompt_env.volume_level_override)
        {
            // first entering, coordinate the volume here
            app_bt_stream_volumeset_handler(audio_prompt_env.volume_level_override);
        }
    }

    uint32_t gotDataLen = 0;
    while (gotDataLen < acquiredPcmDataLen)
    {
        uint32_t lenToGet;
        if ((acquiredPcmDataLen - gotDataLen) > AUDIO_PROMPT_PCM_FILL_UNIT_SIZE)
        {
            lenToGet = AUDIO_PROMPT_PCM_FILL_UNIT_SIZE;
        }
        else
        {
            lenToGet = acquiredPcmDataLen - gotDataLen;
        }

        audio_prompt_processing_handler_func(lenToGet, pcmDataToMerge+gotDataLen);
        gotDataLen += lenToGet;
    }
    af_unlock_thread();
}

static uint32_t audio_prompt_sbc_decode(uint8_t* pcm_buffer, uint32_t expectedOutputSize, uint8_t isReset)
{
    if (isReset)
    {
        audio_prompt_sbc_init_decoder();
    }

    uint32_t sbcDataBytesToDecode;
    unsigned int pcm_offset = 0;
    uint16_t byte_decode;
    int8_t ret;
    btif_sbc_pcm_data_t audio_prompt_PcmDecData;

get_again:
    audio_prompt_PcmDecData.data = pcm_buffer+pcm_offset;
    audio_prompt_PcmDecData.dataLen = 0;

    if (audio_prompt_env.leftEncodedDataLen > AUDIO_PROMPT_SBC_ENCODED_DATA_SIZE_PER_FRAME)
    {
        sbcDataBytesToDecode = AUDIO_PROMPT_SBC_ENCODED_DATA_SIZE_PER_FRAME;
    }
    else
    {
        sbcDataBytesToDecode = audio_prompt_env.leftEncodedDataLen;
    }

    ret = btif_sbc_decode_frames(&audio_prompt_sbc_decoder,
        audio_prompt_env.promptDataBuf+audio_prompt_env.wholeEncodedDataLen-audio_prompt_env.leftEncodedDataLen,
        sbcDataBytesToDecode, &byte_decode,
        &audio_prompt_PcmDecData, expectedOutputSize-pcm_offset,
        audio_prompt_sbc_eq_band_gain);


    audio_prompt_env.leftEncodedDataLen -= byte_decode;

    pcm_offset += audio_prompt_PcmDecData.dataLen;

    if (0 == audio_prompt_env.leftEncodedDataLen)
    {
        goto exit;
    }

    if (expectedOutputSize == pcm_offset)
    {
        goto exit;
    }

    if ((ret == BT_STS_CONTINUE) || (ret == BT_STS_SUCCESS))  {
        goto get_again;
    }

exit:
    return pcm_offset;
}
#endif // MIX_AUDIO_PROMPT_WITH_A2DP_MEDIA_ENABLED
