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

/*
 * This file contains the function where the main decision logic for buffer level
 * adaptation happens.
 */

#include "buffer_stats.h"

#include <assert.h>

#include "signal_processing_library.h"

#include "automode.h"
#include "neteq_defines.h"
#include "neteq_error_codes.h"
#include "webrtc_neteq.h"

#define NETEQ_BUFSTAT_20MS_Q7 2560 /* = 20 ms in Q7  */

uint16_t WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, int16_t frameSize,
                                      int32_t cur_size, uint32_t targetTS,
                                      uint32_t availableTS, int noPacket,
                                      int cngPacket, int prevPlayMode,
                                      enum WebRtcNetEQPlayoutMode playoutMode,
                                      int timestampsPerCall, int NoOfExpandCalls,
                                      int16_t fs_mult,
                                      int16_t lastModeBGNonly, int playDtmf)
{

    int currentDelayMs;
    int32_t currSizeSamples = cur_size;
    int extraDelayPacketsQ8 = 0;

    /* Avoid overflow if the buffer size should be really large (cur_size is limited 256ms) */
    int32_t curr_sizeQ7 = WEBRTC_SPL_LSHIFT_W32(cur_size, 4);
    int level_limit_hi, level_limit_lo;

    inst->Automode_inst.prevTimeScale &= (prevPlayMode == MODE_SUCCESS_ACCELERATE
        || prevPlayMode == MODE_LOWEN_ACCELERATE || prevPlayMode == MODE_SUCCESS_PREEMPTIVE
        || prevPlayMode == MODE_LOWEN_PREEMPTIVE);

    if ((prevPlayMode != MODE_RFC3389CNG) && (prevPlayMode != MODE_CODEC_INTERNAL_CNG))
    {
        /*
         * Do not update buffer history if currently playing CNG
         * since it will bias the filtered buffer level.
         */
        WebRtcNetEQ_BufferLevelFilter(cur_size, &(inst->Automode_inst), timestampsPerCall,
            fs_mult);
    }
    else
    {
        /* only update time counters */
        inst->Automode_inst.packetIatCountSamp += timestampsPerCall; /* packet inter-arrival time */
        inst->Automode_inst.peakIatCountSamp += timestampsPerCall; /* peak inter-arrival time */
        inst->Automode_inst.timescaleHoldOff >>= 1; /* time-scaling limiter */
    }
    cur_size = WEBRTC_SPL_MIN(curr_sizeQ7, WEBRTC_SPL_WORD16_MAX);

    /* Calculate VQmon related variables */
    /* avgDelay = avgDelay*(511/512) + currentDelay*(1/512) (sample ms delay in Q8) */
    inst->avgDelayMsQ8 = (int16_t) (WEBRTC_SPL_MUL_16_16_RSFT(inst->avgDelayMsQ8,511,9)
        + (cur_size >> 9));

    /* Update maximum delay if needed */
    currentDelayMs = (curr_sizeQ7 >> 7);
    if (currentDelayMs > inst->maxDelayMs)
    {
        inst->maxDelayMs = currentDelayMs;
    }

    /* NetEQ is on with normal or steaming mode */
    if (playoutMode == kPlayoutOn || playoutMode == kPlayoutStreaming)
    {
        /* Guard for errors, so that it should not get stuck in error mode */
        if (prevPlayMode == MODE_ERROR)
        {
            if (noPacket)
            {
                return BUFSTATS_DO_EXPAND;
            }
            else
            {
                return BUFSTAT_REINIT;
            }
        }

        if (prevPlayMode != MODE_EXPAND && prevPlayMode != MODE_FADE_TO_BGN)
        {
            inst->w16_noExpand = 1;
        }
        else
        {
            inst->w16_noExpand = 0;
        }

        if (cngPacket)
        {
            /* signed difference between wanted and available TS */
            int32_t diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS;
            int32_t optimal_level_samp = (inst->Automode_inst.optBufLevel *
                inst->Automode_inst.packetSpeechLenSamp) >> 8;
            int32_t excess_waiting_time_samp = -diffTS - optimal_level_samp;

            if (excess_waiting_time_samp > optimal_level_samp / 2)
            {
                /* The waiting time for this packet will be longer than 1.5
                 * times the wanted buffer delay. Advance the clock to cut
                 * waiting time down to the optimal.
                 */
                inst->uw32_CNGplayedTS += excess_waiting_time_samp;
                diffTS += excess_waiting_time_samp;
            }

            if ((diffTS) < 0 && (prevPlayMode == MODE_RFC3389CNG))
            {
                /* Not time to play this packet yet. Wait another round before using this
                 * packet. Keep on playing CNG from previous CNG parameters. */
                return BUFSTATS_DO_RFC3389CNG_NOPACKET;
            }

            /* otherwise, go for the CNG packet now */
            return BUFSTATS_DO_RFC3389CNG_PACKET;
        }

        /*Check for expand/cng */
        if (noPacket)
        {
            if (inst->w16_cngOn == CNG_RFC3389_ON)
            {
                /* keep on playing CNG */
                return BUFSTATS_DO_RFC3389CNG_NOPACKET;
            }
            else if (inst->w16_cngOn == CNG_INTERNAL_ON)
            {
                /* keep on playing internal CNG */
                return BUFSTATS_DO_INTERNAL_CNG_NOPACKET;
            }
            else if (playDtmf == 1)
            {
                /* we have not audio data, but can play DTMF */
                return BUFSTATS_DO_DTMF_ONLY;
            }
            else
            {
                /* nothing to play => do Expand */
                return BUFSTATS_DO_EXPAND;
            }
        }

        /*
         * If the expand period was very long, reset NetEQ since it is likely that the
         * sender was restarted.
         */
        if (NoOfExpandCalls > REINIT_AFTER_EXPANDS) return BUFSTAT_REINIT_DECODER;

        /* Calculate extra delay in Q8 packets */
        if (inst->Automode_inst.extraDelayMs > 0 && inst->Automode_inst.packetSpeechLenSamp
            > 0)
        {

            /* (extra delay in samples in Q8) */
            extraDelayPacketsQ8 =
                ((inst->Automode_inst.extraDelayMs * 8 * fs_mult) << 8) /
                inst->Automode_inst.packetSpeechLenSamp;
        }

        /* Check if needed packet is available */
        if (targetTS == availableTS)
        {

            /* If last mode was not expand, and there is no DTMF to play */
            if (inst->w16_noExpand == 1 && playDtmf == 0)
            {
                /* If so check for accelerate */

                level_limit_lo = ((inst->Automode_inst.optBufLevel) >> 1) /* 50 % */
                    + ((inst->Automode_inst.optBufLevel) >> 2); /* ... + 25% = 75% */

                /* set upper limit to optBufLevel, but make sure that window is at least 20ms */
                level_limit_hi = WEBRTC_SPL_MAX(inst->Automode_inst.optBufLevel,
                    level_limit_lo +
                    WebRtcSpl_DivW32W16ResW16((WEBRTC_SPL_MUL(20*8, fs_mult) << 8),
                        inst->Automode_inst.packetSpeechLenSamp));

                /* if extra delay is non-zero, add it */
                if (extraDelayPacketsQ8 > 0)
                {
                    level_limit_hi += extraDelayPacketsQ8;
                    level_limit_lo += extraDelayPacketsQ8;
                }

                if (((inst->Automode_inst.buffLevelFilt >= level_limit_hi) &&
                    (inst->Automode_inst.timescaleHoldOff == 0)) ||
                    (inst->Automode_inst.buffLevelFilt >= level_limit_hi << 2))
                {
                    /*
                     * Buffer level higher than limit and time-scaling allowed,
                     * OR buffer level _really_ high.
                     */
                    return BUFSTATS_DO_ACCELERATE;
                }
                else if ((inst->Automode_inst.buffLevelFilt < level_limit_lo)
                    && (inst->Automode_inst.timescaleHoldOff == 0))
                {
                    return BUFSTATS_DO_PREEMPTIVE_EXPAND;
                }
            }
            return BUFSTATS_DO_NORMAL;
        }

        /* Check for Merge */
        else if (availableTS > targetTS)
        {

            /* Check that we do not play a packet "too early" */
            if ((prevPlayMode == MODE_EXPAND)
                && (availableTS - targetTS
                    < (uint32_t) WEBRTC_SPL_MUL_16_16((int16_t)timestampsPerCall,
                        (int16_t)REINIT_AFTER_EXPANDS))
                && (NoOfExpandCalls < MAX_WAIT_FOR_PACKET)
                && (availableTS
                    > targetTS
                        + WEBRTC_SPL_MUL_16_16((int16_t)timestampsPerCall,
                            (int16_t)NoOfExpandCalls))
                && (inst->Automode_inst.buffLevelFilt <= inst->Automode_inst.optBufLevel
                    + extraDelayPacketsQ8))
            {
                if (playDtmf == 1)
                {
                    /* we still have DTMF to play, so do not perform expand */
                    return BUFSTATS_DO_DTMF_ONLY;
                }
                else
                {
                    /* nothing to play */
                    return BUFSTATS_DO_EXPAND;
                }
            }

            /* If previous was CNG period or BGNonly then no merge is needed */
            if ((prevPlayMode == MODE_RFC3389CNG) || (prevPlayMode == MODE_CODEC_INTERNAL_CNG)
                || lastModeBGNonly)
            {
                /*
                 * Keep the same delay as before the CNG (or maximum 70 ms in buffer as safety
                 * precaution), but make sure that the number of samples in buffer is no
                 * higher than 4 times the optimal level.
                 */
                int32_t diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS;
                int val = ((inst->Automode_inst.optBufLevel +
                    extraDelayPacketsQ8) *
                    inst->Automode_inst.packetSpeechLenSamp) >> 6;
                if (diffTS >= 0 || val < currSizeSamples)
                {
                    /* it is time to play this new packet */
                    return BUFSTATS_DO_NORMAL;
                }
                else
                {
                    /* it is too early to play this new packet => keep on playing CNG */
                    if (prevPlayMode == MODE_RFC3389CNG)
                    {
                        return BUFSTATS_DO_RFC3389CNG_NOPACKET;
                    }
                    else if (prevPlayMode == MODE_CODEC_INTERNAL_CNG)
                    {
                        return BUFSTATS_DO_INTERNAL_CNG_NOPACKET;
                    }
                    else if (playDtmf == 1)
                    {
                        /* we have not audio data, but can play DTMF */
                        return BUFSTATS_DO_DTMF_ONLY;
                    }
                    else /* lastModeBGNonly */
                    {
                        /* signal expand, but this will result in BGN again */
                        return BUFSTATS_DO_EXPAND;
                    }
                }
            }

            /* Do not merge unless we have done a Expand before (for complexity reasons) */
            if ((inst->w16_noExpand == 0) || ((frameSize < timestampsPerCall) && (cur_size
                > NETEQ_BUFSTAT_20MS_Q7)))
            {
                return BUFSTATS_DO_MERGE;
            }
            else if (playDtmf == 1)
            {
                /* play DTMF instead of expand */
                return BUFSTATS_DO_DTMF_ONLY;
            }
            else
            {
                return BUFSTATS_DO_EXPAND;
            }
        }
    }
    else
    { /* kPlayoutOff or kPlayoutFax */
        if (cngPacket)
        {
            if (((int32_t) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0)
            {
                /* time to play this packet now */
                return BUFSTATS_DO_RFC3389CNG_PACKET;
            }
            else
            {
                /* wait before playing this packet */
                return BUFSTATS_DO_RFC3389CNG_NOPACKET;
            }
        }
        if (noPacket)
        {
            /*
             * No packet =>
             * 1. If in CNG mode play as usual
             * 2. Otherwise use other method to generate data and hold TS value
             */
            if (inst->w16_cngOn == CNG_RFC3389_ON)
            {
                /* keep on playing CNG */
                return BUFSTATS_DO_RFC3389CNG_NOPACKET;
            }
            else if (inst->w16_cngOn == CNG_INTERNAL_ON)
            {
                /* keep on playing internal CNG */
                return BUFSTATS_DO_INTERNAL_CNG_NOPACKET;
            }
            else
            {
                /* nothing to play => invent some data to play out */
                if (playoutMode == kPlayoutOff)
                {
                    return BUFSTATS_DO_ALTERNATIVE_PLC;
                }
                else if (playoutMode == kPlayoutFax)
                {
                    return BUFSTATS_DO_AUDIO_REPETITION;
                }
                else
                {
                    /* UNDEFINED, should not get here... */
                    assert(0);
                    return BUFSTAT_REINIT;
                }
            }
        }
        else if (targetTS == availableTS)
        {
            return BUFSTATS_DO_NORMAL;
        }
        else
        {
            if (((int32_t) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0)
            {
                return BUFSTATS_DO_NORMAL;
            }
            else if (playoutMode == kPlayoutOff)
            {
                /*
                 * If currently playing CNG, continue with that. Don't increase TS
                 * since uw32_CNGplayedTS will be increased.
                 */
                if (inst->w16_cngOn == CNG_RFC3389_ON)
                {
                    return BUFSTATS_DO_RFC3389CNG_NOPACKET;
                }
                else if (inst->w16_cngOn == CNG_INTERNAL_ON)
                {
                    return BUFSTATS_DO_INTERNAL_CNG_NOPACKET;
                }
                else
                {
                    /*
                     * Otherwise, do PLC and increase TS while waiting for the time to
                     * play this packet.
                     */
                    return BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS;
                }
            }
            else if (playoutMode == kPlayoutFax)
            {
                /*
                 * If currently playing CNG, continue with that don't increase TS since
                 * uw32_CNGplayedTS will be increased.
                 */
                if (inst->w16_cngOn == CNG_RFC3389_ON)
                {
                    return BUFSTATS_DO_RFC3389CNG_NOPACKET;
                }
                else if (inst->w16_cngOn == CNG_INTERNAL_ON)
                {
                    return BUFSTATS_DO_INTERNAL_CNG_NOPACKET;
                }
                else
                {
                    /*
                     * Otherwise, do audio repetition and increase TS while waiting for the
                     * time to play this packet.
                     */
                    return BUFSTATS_DO_AUDIO_REPETITION_INC_TS;
                }
            }
            else
            {
                /* UNDEFINED, should not get here... */
                assert(0);
                return BUFSTAT_REINIT;
            }
        }
    }
    /* We should not get here (but sometimes we do anyway...) */
    return BUFSTAT_REINIT;
}

