/*
 *  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 "video_coding_defines.h"
#include "fec_tables_xor.h"
#include "er_tables_xor.h"
#include "nack_fec_tables.h"
#include "qm_select_data.h"
#include "media_opt_util.h"

#include <math.h>
#include <float.h>
#include <limits.h>

namespace webrtc {

bool
VCMProtectionMethod::BetterThan(VCMProtectionMethod *pm)
{
  if (pm == NULL)
  {
      return true;
  }
  return pm->_score > _score;
}

bool
VCMNackFecMethod::ProtectionFactor(const VCMProtectionParameters* /*parameters*/)
{
    // use FEC model with modification with RTT for now
    return true;
}

bool
VCMNackFecMethod::EffectivePacketLoss(const
                                      VCMProtectionParameters* /*parameters*/)
{
    // use FEC model with modification with RTT for now
    return true;
}

bool
VCMNackFecMethod::UpdateParameters(const VCMProtectionParameters* parameters)
{
    // Hybrid Nack FEC has three operational modes:
    // 1. Low RTT - Nack only (Set FEC rates to zero)
    // 2. High RTT - FEC Only
    // 3. Medium RTT values - Hybrid ; in hybrid mode, we will only nack the
    //    residual following the decoding of the FEC (refer to JB logic)

    // Low RTT - NACK only mode
    if (parameters->rtt < kLowRttNackMs)
    {
        // Set the FEC parameters to 0
        _protectionFactorK = 0;
        _protectionFactorD = 0;

        // assume packets will be restored via NACK
        // TODO: relax this assumption?
        _effectivePacketLoss = 0;
        _score = _efficiency;
        return true;
    }
    // otherwise: we count on FEC; if the RTT is below a threshold, then we can
    // nack the residual, based on a decision made in the JB.
    // TODO(mikhal): adapt the FEC rate based on the RTT, i.e. the the level on
    // which we will rely on NACK, e.g. less as we approach upper threshold.
    VCMFecMethod fecMethod;

    const WebRtc_UWord8 plossMax = 129;

    // Compute the protection factor
    fecMethod.ProtectionFactor(parameters);

    // Compute the effective packet loss
    fecMethod.EffectivePacketLoss(parameters);

    WebRtc_UWord8 protFactorK = fecMethod._protectionFactorK;
    WebRtc_UWord8 protFactorD = fecMethod._protectionFactorD;
    WebRtc_UWord8 effPacketLoss = fecMethod._effectivePacketLoss;
    float resPacketLoss = fecMethod._residualPacketLoss;

    // Correct FEC rates based on the RTT ( NACK effectiveness)
    WebRtc_Word16 rttIndex= (WebRtc_UWord16) parameters->rtt;
    float softnessRtt = 1.0;
    if (parameters->rtt < kHighRttNackMs)
    {
        // TODO(mikhal): update table
         softnessRtt = (float)VCMNackFecTable[rttIndex] / (float)4096.0;

        // soften ER with NACK on
        // table depends on RTT relative to rttMax (NACK Threshold)
        _effectivePacketLoss = (WebRtc_UWord8)(effPacketLoss * softnessRtt);

        // soften FEC with NACK on
        // table depends on RTT relative to rttMax (NACK Threshold)
        _protectionFactorK = (WebRtc_UWord8) (protFactorK * softnessRtt);
        _protectionFactorD = (WebRtc_UWord8) (protFactorD * softnessRtt);
    }
    // else - NACK is disabled, rely on FEC only


    // make sure I frame protection is at least larger than P frame protection,
    // and at least as high as received loss
    WebRtc_UWord8 packetLoss = (WebRtc_UWord8) (255 * parameters->lossPr);
    _protectionFactorK = static_cast<WebRtc_UWord8> (VCM_MAX(packetLoss,
        VCM_MAX(_scaleProtKey * protFactorD, protFactorK)));

    // check limit on amount of protection for I frame: 50% is max
    if (_protectionFactorK >= plossMax)
        _protectionFactorK = plossMax - 1;

    // Bit cost for NackFec

    // NACK cost: based on residual packet loss (since we should only NACK
    // packets not recovered by FEC)
    _efficiency = 0.0f;
    if (parameters->rtt < kHighRttNackMs)
    {
        _efficiency = parameters->bitRate * resPacketLoss /
                                  (1.0f + resPacketLoss);
    }
    else
    {
        // efficiency based on FEC only
        // add FEC cost: ignore I frames for now
        float fecRate = static_cast<float> (_protectionFactorD) / 255.0f;
        if (fecRate >= 0.0f)
            _efficiency += parameters->bitRate * fecRate;
    }
    _score = _efficiency;

    // Protection/fec rates obtained above are defined relative to total number
    // of packets (total rate: source + fec) FEC in RTP module assumes
    // protection factor is defined relative to source number of packets so we
    // should convert the factor to reduce mismatch between mediaOpt's rate and
    // the actual one
    WebRtc_UWord8 codeRate = protFactorK;
    _protectionFactorK = fecMethod.ConvertFECRate(codeRate);
    codeRate = protFactorD;
    _protectionFactorD = fecMethod.ConvertFECRate(codeRate);

    return true;
}

bool
VCMNackMethod::EffectivePacketLoss(WebRtc_UWord8 effPacketLoss,
                                   WebRtc_UWord16 rttTime)
{
    WebRtc_UWord16 rttMax = MaxRttNack();

    // For large RTT, we should rely on some Error Resilience, so we set
    // packetLossEnc = 0 for RTT less than the NACK threshold
    if (rttTime < rttMax)
    {
        effPacketLoss = 0; //may want a softer transition here
    }
    _effectivePacketLoss = effPacketLoss;

    return true;
}

bool
VCMNackMethod::UpdateParameters(const VCMProtectionParameters* parameters)
{
    // Compute the effective packet loss for ER
    WebRtc_UWord8 effPacketLoss = (WebRtc_UWord8) (255 * parameters->lossPr);
    WebRtc_UWord16 rttTime = (WebRtc_UWord16) parameters->rtt;
    EffectivePacketLoss(effPacketLoss, rttTime);

    // Compute the NACK bit cost
    _efficiency = parameters->bitRate * parameters->lossPr /
                              (1.0f + parameters->lossPr);
    _score = _efficiency;
    if (parameters->rtt > _NACK_MAX_RTT)
    {
        _score = 0.0f;
        return false;
    }
    return true;
}

WebRtc_UWord8
VCMFecMethod::BoostCodeRateKey(WebRtc_UWord8 packetFrameDelta,
                               WebRtc_UWord8 packetFrameKey) const
{
    WebRtc_UWord8 boostRateKey = 2;
    // default: ratio scales the FEC protection up for I frames
    WebRtc_UWord8 ratio = 1;

    if (packetFrameDelta > 0)
    {
        ratio = (WebRtc_Word8) (packetFrameKey / packetFrameDelta);
    }
    ratio = VCM_MAX(boostRateKey, ratio);

    return ratio;
}

WebRtc_UWord8
VCMFecMethod::ConvertFECRate(WebRtc_UWord8 codeRateRTP) const
{
    return static_cast<WebRtc_UWord8> (VCM_MIN(255,(0.5 + 255.0 * codeRateRTP /
                                      (float)(255 - codeRateRTP))));
}

// AvgRecoveryFEC: average recovery from FEC, assuming random packet loss model
// Computed offline for a range of FEC code parameters and loss rates
float
VCMFecMethod::AvgRecoveryFEC(const VCMProtectionParameters* parameters) const
{
    // Total (avg) bits available per frame: total rate over actual/sent frame
    // rate units are kbits/frame
    const WebRtc_UWord16 bitRatePerFrame = static_cast<WebRtc_UWord16>
                        (parameters->bitRate / (parameters->frameRate));

    // Total (avg) number of packets per frame (source and fec):
    const WebRtc_UWord8 avgTotPackets = 1 +
                        (WebRtc_UWord8) ((float) bitRatePerFrame * 1000.0
                        / (float) (8.0 * _maxPayloadSize) + 0.5);

    // parameters for tables
    const WebRtc_UWord8 codeSize = 24;
    const WebRtc_UWord8 plossMax = 129;
    const WebRtc_UWord16 maxErTableSize = 38700;

    // Get index for table
    const float protectionFactor = (float) _protectionFactorD / (float) 255;
    WebRtc_UWord8 fecPacketsPerFrame = (WebRtc_UWord8) (0.5 + protectionFactor
                                                        * avgTotPackets);
    WebRtc_UWord8 sourcePacketsPerFrame = avgTotPackets - fecPacketsPerFrame;

    if (fecPacketsPerFrame == 0)
    {
        return 0.0; // no protection, so avg. recov from FEC == 0
    }

    // table defined up to codeSizexcodeSize code
    if (sourcePacketsPerFrame > codeSize)
    {
        sourcePacketsPerFrame = codeSize;
    }

    // check: protection factor is maxed at 50%, so this should never happen
    if (sourcePacketsPerFrame < 1)
    {
        assert("average number of source packets below 1\n");
    }

    // index for ER tables: up to codeSizexcodeSize mask
    WebRtc_UWord16 codeIndexTable[codeSize * codeSize];
    WebRtc_UWord16 k = -1;
    for (WebRtc_UWord8 i = 1; i <= codeSize; i++)
    {
        for (WebRtc_UWord8 j = 1; j <= i; j++)
        {
            k += 1;
            codeIndexTable[(j - 1) * codeSize + i - 1] = k;
        }
    }

    const WebRtc_UWord8 lossRate = (WebRtc_UWord8) (255.0 *
                                    parameters->lossPr + 0.5f);

    const WebRtc_UWord16 codeIndex = (fecPacketsPerFrame - 1) * codeSize
                                      + (sourcePacketsPerFrame - 1);
    const WebRtc_UWord16 indexTable = codeIndexTable[codeIndex] * plossMax
                                      + lossRate;

    const WebRtc_UWord16 codeIndex2 = (fecPacketsPerFrame) * codeSize
                                      + (sourcePacketsPerFrame);
    WebRtc_UWord16 indexTable2 = codeIndexTable[codeIndex2] * plossMax
                                 + lossRate;

    // checks on table index
    if (indexTable >= maxErTableSize)
    {
        assert("ER table index too large\n");
    }

    if (indexTable2 >= maxErTableSize)
    {
        indexTable2 = indexTable;
    }

    // Get the average effective packet loss recovery from FEC
    // this is from tables, computed using random loss model
    WebRtc_UWord8 avgFecRecov1 = 0;
    WebRtc_UWord8 avgFecRecov2 = 0;
    float avgFecRecov = 0;

    if (fecPacketsPerFrame > 0)
    {
        avgFecRecov1 = VCMAvgFECRecoveryXOR[indexTable];
        avgFecRecov2 = VCMAvgFECRecoveryXOR[indexTable2];
    }

    // interpolate over two FEC codes
    const float weightRpl = (float) (0.5 + protectionFactor * avgTotPackets)
        - (float) fecPacketsPerFrame;
    avgFecRecov = (float) weightRpl * (float) avgFecRecov2 + (float)
                  (1.0 - weightRpl) * (float) avgFecRecov1;

    return avgFecRecov;
}

bool
VCMFecMethod::ProtectionFactor(const VCMProtectionParameters* parameters)
{
    // FEC PROTECTION SETTINGS: varies with packet loss and bitrate

    WebRtc_UWord8 packetLoss = (WebRtc_UWord8) (255 * parameters->lossPr);

    // No protection if (filtered) packetLoss is 0
    if (packetLoss == 0)
    {
        _protectionFactorK = 0;
        _protectionFactorD = 0;
         return true;
    }

    // Size of tables
    const WebRtc_UWord16 maxFecTableSize = 6450;
    // Parameters for range of rate and packet loss for tables
    const WebRtc_UWord8 ratePar1 = 5;
    const WebRtc_UWord8 ratePar2 = 49;
    const WebRtc_UWord8 plossMax = 129;

    const float bitRate = parameters->bitRate;

    // Total (avg) bits available per frame: total rate over actual/frame_rate.
    // Units are kbits/frame
    const WebRtc_UWord16 bitRatePerFrame = static_cast<WebRtc_UWord16>
                                                     (bitRate /
                                                     (parameters->frameRate));

    // Total (avg) number of packets per frame (source and fec):
    const WebRtc_UWord8 avgTotPackets = 1 + (WebRtc_UWord8)
                                        ((float) bitRatePerFrame * 1000.0
                                       / (float) (8.0 * _maxPayloadSize) + 0.5);


    // First partition protection: ~ 20%
    WebRtc_UWord8 firstPartitionProt = (WebRtc_UWord8) (255 * 0.20);

    // Threshold on packetLoss and bitRrate/frameRate (=average #packets),
    // above which we allocate protection to cover at least roughly
    // first partition size.
    WebRtc_UWord8 lossThr = 0;
    WebRtc_UWord8 packetNumThr = 1;

    // Modulation of protection with available bits/frame (or avgTotpackets)
    float weight1 = 0.5;
    float weight2 = 0.5;
    if (avgTotPackets > 4)
    {
        weight1 = 0.75;
        weight2 = 0.25;
    }
    if (avgTotPackets > 6)
    {
        weight1 = 1.5;
        weight2 = 0.;
    }

    // FEC rate parameters: for P and I frame
    WebRtc_UWord8 codeRateDelta = 0;
    WebRtc_UWord8 codeRateKey = 0;

    // Get index for table: the FEC protection depends on the (average)
    // available bits/frame. The range on the rate index corresponds to rates
    // (bps) from 200k to 8000k, for 30fps
    WebRtc_UWord8 rateIndexTable =
        (WebRtc_UWord8) VCM_MAX(VCM_MIN((bitRatePerFrame - ratePar1) /
                                        ratePar1, ratePar2), 0);

    // Restrict packet loss range to 50:
    // current tables defined only up to 50%
    if (packetLoss >= plossMax)
    {
        packetLoss = plossMax - 1;
    }
    WebRtc_UWord16 indexTable = rateIndexTable * plossMax + packetLoss;

    // Check on table index
    assert(indexTable < maxFecTableSize);

    // Protection factor for P frame
    codeRateDelta = VCMCodeRateXORTable[indexTable];

    if (packetLoss > lossThr && avgTotPackets > packetNumThr)
    {
        // Average with minimum protection level given by (average) total
        // number of packets
        codeRateDelta = static_cast<WebRtc_UWord8>((weight1 *
            (float) codeRateDelta + weight2 * 255.0 / (float) avgTotPackets));

        // Set a minimum based on first partition size.
        if (codeRateDelta < firstPartitionProt)
        {
            codeRateDelta = firstPartitionProt;
        }
    }

    // Check limit on amount of protection for P frame; 50% is max.
    if (codeRateDelta >= plossMax)
    {
        codeRateDelta = plossMax - 1;
    }

    // For Key frame:
    // Effectively at a higher rate, so we scale/boost the rate
    // The boost factor may depend on several factors: ratio of packet
    // number of I to P frames, how much protection placed on P frames, etc.
    const WebRtc_UWord8 packetFrameDelta = (WebRtc_UWord8)
                                           (0.5 + parameters->packetsPerFrame);
    const WebRtc_UWord8 packetFrameKey = (WebRtc_UWord8)
                                         (0.5 + parameters->packetsPerFrameKey);
    const WebRtc_UWord8 boostKey = BoostCodeRateKey(packetFrameDelta,
                                                    packetFrameKey);

    rateIndexTable = (WebRtc_UWord8) VCM_MAX(VCM_MIN(
                      1 + (boostKey * bitRatePerFrame - ratePar1) /
                      ratePar1,ratePar2),0);
    WebRtc_UWord16 indexTableKey = rateIndexTable * plossMax + packetLoss;

    indexTableKey = VCM_MIN(indexTableKey, maxFecTableSize);

    // Check on table index
    assert(indexTableKey < maxFecTableSize);

    // Protection factor for I frame
    codeRateKey = VCMCodeRateXORTable[indexTableKey];

    // Boosting for Key frame.
    WebRtc_UWord32 boostKeyProt = _scaleProtKey * codeRateDelta;
    if ( boostKeyProt >= plossMax)
    {
        boostKeyProt = plossMax - 1;
    }

    // Make sure I frame protection is at least larger than P frame protection,
    // and at least as high as filtered packet loss.
    codeRateKey = static_cast<WebRtc_UWord8> (VCM_MAX(packetLoss,
            VCM_MAX(boostKeyProt, codeRateKey)));

    // Check limit on amount of protection for I frame: 50% is max.
    if (codeRateKey >= plossMax)
    {
        codeRateKey = plossMax - 1;
    }

    _protectionFactorK = codeRateKey;
    _protectionFactorD = codeRateDelta;

    // DONE WITH FEC PROTECTION SETTINGS
    return true;
}

bool
VCMFecMethod::EffectivePacketLoss(const VCMProtectionParameters* parameters)
{
    // ER SETTINGS:
    // Effective packet loss to encoder is based on RPL (residual packet loss)
    // this is a soft setting based on degree of FEC protection
    // RPL = received/input packet loss - average_FEC_recovery
    // note: received/input packet loss may be filtered based on FilteredLoss

    // The input packet loss:
    WebRtc_UWord8 effPacketLoss = (WebRtc_UWord8) (255 * parameters->lossPr);

    float scaleErRS = 0.5;
    float scaleErXOR = 0.5;
    float minErLevel = (float) 0.025;
    // float scaleErRS = 1.0;
    // float scaleErXOR = 1.0;
    // float minErLevel = (float) 0.0;

    float avgFecRecov = 0.;
    // Effective packet loss for ER:
    float scaleEr = scaleErXOR;
    avgFecRecov = AvgRecoveryFEC(parameters);

    // Residual Packet Loss:
    _residualPacketLoss = (float) (effPacketLoss - avgFecRecov) / (float) 255.0;

    //Effective Packet Loss for encoder:
    _effectivePacketLoss = 0;
    if (effPacketLoss > 0) {
      _effectivePacketLoss = VCM_MAX((effPacketLoss -
              (WebRtc_UWord8)(scaleEr * avgFecRecov)),
          static_cast<WebRtc_UWord8>(minErLevel * 255));
    }

    // DONE WITH ER SETTING
    return true;
}

bool
VCMFecMethod::UpdateParameters(const VCMProtectionParameters* parameters)
{
    // Compute the protection factor
    ProtectionFactor(parameters);

    // Compute the effective packet loss
    EffectivePacketLoss(parameters);

    // Compute the bit cost
    // Ignore key frames for now.
    float fecRate = static_cast<float> (_protectionFactorD) / 255.0f;
    if (fecRate >= 0.0f)
    {
        // use this formula if the fecRate (protection factor) is defined
        // relative to number of source packets
        // this is the case for the previous tables:
        // _efficiency = parameters->bitRate * ( 1.0 - 1.0 / (1.0 + fecRate));

        // in the new tables, the fecRate is defined relative to total number of
        // packets (total rate), so overhead cost is:
        _efficiency = parameters->bitRate * fecRate;
    }
    else
    {
        _efficiency = 0.0f;
    }
    _score = _efficiency;

    // Protection/fec rates obtained above is defined relative to total number
    // of packets (total rate: source+fec) FEC in RTP module assumes protection
    // factor is defined relative to source number of packets so we should
    // convert the factor to reduce mismatch between mediaOpt suggested rate and
    // the actual rate
    _protectionFactorK = ConvertFECRate(_protectionFactorK);
    _protectionFactorD = ConvertFECRate(_protectionFactorD);

    return true;
}

bool
VCMIntraReqMethod::UpdateParameters(const VCMProtectionParameters* parameters)
{
    float packetRate = parameters->packetsPerFrame * parameters->frameRate;
    // Assume that all lost packets cohere to different frames
    float lossRate = parameters->lossPr * packetRate;
    if (parameters->keyFrameSize <= 1e-3)
    {
        _score = FLT_MAX;
        return false;
    }
    _efficiency = lossRate * parameters->keyFrameSize;
    _score = _efficiency;
    if (parameters->lossPr >= 1.0f / parameters->keyFrameSize ||
        parameters->rtt > _IREQ_MAX_RTT)
    {
        return false;
    }
    return true;
}

bool
VCMPeriodicIntraMethod::UpdateParameters(const
                                        VCMProtectionParameters* /*parameters*/)
{
    // Periodic I-frames. The last thing we want to use.
    _efficiency = 0.0f;
    _score = FLT_MAX;
    return true;
}

bool
VCMMbIntraRefreshMethod::UpdateParameters(const
                                          VCMProtectionParameters* parameters)
{
    // Assume optimal for now.
    _efficiency = parameters->bitRate * parameters->lossPr /
                  (1.0f + parameters->lossPr);
    _score = _efficiency;
    if (parameters->bitRate < _MBREF_MIN_BITRATE)
    {
        return false;
    }
    return true;
}

WebRtc_UWord16
VCMNackMethod::MaxRttNack() const
{
    return _NACK_MAX_RTT;
}

VCMLossProtectionLogic::~VCMLossProtectionLogic()
{
    ClearLossProtections();
}

void
VCMLossProtectionLogic::ClearLossProtections()
{
    ListItem *item;
    while ((item = _availableMethods.First()) != 0) {
      VCMProtectionMethod *method = static_cast<VCMProtectionMethod*>
                                    (item->GetItem());
      if (method != NULL)
      {
          delete method;
      }
      _availableMethods.PopFront();
    }
    _selectedMethod = NULL;
}

bool
VCMLossProtectionLogic::AddMethod(VCMProtectionMethod *newMethod)
{
    VCMProtectionMethod *method;
    ListItem *item;
    if (newMethod == NULL)
    {
        return false;
    }
    for (item = _availableMethods.First(); item != NULL;
        item = _availableMethods.Next(item))
    {
        method = static_cast<VCMProtectionMethod *> (item->GetItem());
        if (method != NULL && method->Type() == newMethod->Type())
        {
            return false;
        }
    }
    _availableMethods.PushBack(newMethod);
    return true;
}
bool
VCMLossProtectionLogic::RemoveMethod(VCMProtectionMethodEnum methodType)
{
    VCMProtectionMethod *method;
    ListItem *item;
    bool foundAndRemoved = false;
    for (item = _availableMethods.First(); item != NULL;
         item = _availableMethods.Next(item))
    {
        method = static_cast<VCMProtectionMethod *> (item->GetItem());
        if (method != NULL && method->Type() == methodType)
        {
            if (_selectedMethod != NULL &&
                _selectedMethod->Type() == method->Type())
            {
                _selectedMethod = NULL;
            }
            _availableMethods.Erase(item);
            item = NULL;
            delete method;
            foundAndRemoved = true;
        }
    }
    return foundAndRemoved;
}

VCMProtectionMethod*
VCMLossProtectionLogic::FindMethod(VCMProtectionMethodEnum methodType) const
{
    VCMProtectionMethod *method;
    ListItem *item;
    for (item = _availableMethods.First(); item != NULL;
         item = _availableMethods.Next(item))
    {
        method = static_cast<VCMProtectionMethod *> (item->GetItem());
        if (method != NULL && method->Type() == methodType)
        {
            return method;
        }
    }
    return NULL;
}

float
VCMLossProtectionLogic::HighestOverhead() const
{
    VCMProtectionMethod *method;
    ListItem *item;
    float highestOverhead = 0.0f;
    for (item = _availableMethods.First(); item != NULL;
         item = _availableMethods.Next(item))
    {
        method = static_cast<VCMProtectionMethod *> (item->GetItem());
        if (method != NULL && method->RequiredBitRate() > highestOverhead)
        {
            highestOverhead = method->RequiredBitRate();
        }
    }
    return highestOverhead;
}

void
VCMLossProtectionLogic::UpdateRtt(WebRtc_UWord32 rtt)
{
    _rtt = rtt;
}

void
VCMLossProtectionLogic::UpdateResidualPacketLoss(float residualPacketLoss)
{
    _residualPacketLoss = residualPacketLoss;
}

void
VCMLossProtectionLogic::UpdateFecType(VCMFecTypes fecType)
{
    _fecType = fecType;
}

void
VCMLossProtectionLogic::UpdateLossPr(WebRtc_UWord8 lossPr255)
{
    const WebRtc_Word64 now = VCMTickTime::MillisecondTimestamp();
    UpdateMaxLossHistory(lossPr255, now);
    _lossPr255.Apply(static_cast<float> (now - _lastPrUpdateT),
                     static_cast<float> (lossPr255));
    _lastPrUpdateT = now;
    _lossPr = _lossPr255.Value() / 255.0f;
}

void
VCMLossProtectionLogic::UpdateMaxLossHistory(WebRtc_UWord8 lossPr255,
                                             WebRtc_Word64 now)
{
    if (_lossPrHistory[0].timeMs >= 0 &&
        now - _lossPrHistory[0].timeMs < kLossPrShortFilterWinMs)
    {
        if (lossPr255 > _shortMaxLossPr255)
        {
            _shortMaxLossPr255 = lossPr255;
        }
    }
    else
    {
        // Only add a new value to the history once a second
        if (_lossPrHistory[0].timeMs == -1)
        {
            // First, no shift
            _shortMaxLossPr255 = lossPr255;
        }
        else
        {
            // Shift
            for (WebRtc_Word32 i = (kLossPrHistorySize - 2); i >= 0; i--)
            {
                _lossPrHistory[i + 1].lossPr255 = _lossPrHistory[i].lossPr255;
                _lossPrHistory[i + 1].timeMs = _lossPrHistory[i].timeMs;
            }
        }
        if (_shortMaxLossPr255 == 0)
        {
            _shortMaxLossPr255 = lossPr255;
        }

        _lossPrHistory[0].lossPr255 = _shortMaxLossPr255;
        _lossPrHistory[0].timeMs = now;
        _shortMaxLossPr255 = 0;
    }
}

WebRtc_UWord8
VCMLossProtectionLogic::MaxFilteredLossPr(WebRtc_Word64 nowMs) const
{
    WebRtc_UWord8 maxFound = _shortMaxLossPr255;
    if (_lossPrHistory[0].timeMs == -1)
    {
        return maxFound;
    }
    for (WebRtc_Word32 i = 0; i < kLossPrHistorySize; i++)
    {
        if (_lossPrHistory[i].timeMs == -1)
        {
            break;
        }
        if (nowMs - _lossPrHistory[i].timeMs >
            kLossPrHistorySize * kLossPrShortFilterWinMs)
        {
            // This sample (and all samples after this) is too old
            break;
        }
        if (_lossPrHistory[i].lossPr255 > maxFound)
        {
            // This sample is the largest one this far into the history
            maxFound = _lossPrHistory[i].lossPr255;
        }
    }
    return maxFound;
}

WebRtc_UWord8
VCMLossProtectionLogic::FilteredLoss() const
{
    //take the average received loss
    //return static_cast<WebRtc_UWord8>(_lossPr255.Value() + 0.5f);

    //TODO: Update for hybrid
    //take the windowed max of the received loss
    if (_selectedMethod != NULL && _selectedMethod->Type() == kFEC)
    {
        return MaxFilteredLossPr(VCMTickTime::MillisecondTimestamp());
    }
    else
    {
        return static_cast<WebRtc_UWord8> (_lossPr255.Value() + 0.5);
    }
}

void
VCMLossProtectionLogic::UpdateFilteredLossPr(WebRtc_UWord8 packetLossEnc)
{
    _lossPr = (float) packetLossEnc / (float) 255.0;
}

void
VCMLossProtectionLogic::UpdateBitRate(float bitRate)
{
    _bitRate = bitRate;
}

void
VCMLossProtectionLogic::UpdatePacketsPerFrame(float nPackets)
{
    const WebRtc_Word64 now = VCMTickTime::MillisecondTimestamp();
    _packetsPerFrame.Apply(static_cast<float>(now - _lastPacketPerFrameUpdateT),
                           nPackets);
    _lastPacketPerFrameUpdateT = now;
}

void
VCMLossProtectionLogic::UpdatePacketsPerFrameKey(float nPackets)
{
    const WebRtc_Word64 now = VCMTickTime::MillisecondTimestamp();
    _packetsPerFrameKey.Apply(static_cast<float>(now -
                              _lastPacketPerFrameUpdateTKey), nPackets);
    _lastPacketPerFrameUpdateTKey = now;
}

void
VCMLossProtectionLogic::UpdateKeyFrameSize(float keyFrameSize)
{
    _keyFrameSize = keyFrameSize;
}

bool
VCMLossProtectionLogic::UpdateMethod(VCMProtectionMethod *newMethod /*=NULL */)
{
    _currentParameters.rtt = _rtt;
    _currentParameters.lossPr = _lossPr;
    _currentParameters.bitRate = _bitRate;
    _currentParameters.frameRate = _frameRate; // rename actual frame rate?
    _currentParameters.keyFrameSize = _keyFrameSize;
    _currentParameters.fecRateDelta = _fecRateDelta;
    _currentParameters.fecRateKey = _fecRateKey;
    _currentParameters.packetsPerFrame = _packetsPerFrame.Value();
    _currentParameters.packetsPerFrameKey = _packetsPerFrameKey.Value();
    _currentParameters.residualPacketLoss = _residualPacketLoss;
    _currentParameters.fecType = _fecType;

    if (newMethod == NULL)
    {
        //_selectedMethod = _bestNotOkMethod = NULL;
        VCMProtectionMethod *method;
        ListItem *item;
        for (item = _availableMethods.First(); item != NULL;
             item = _availableMethods.Next(item))
        {
            method = static_cast<VCMProtectionMethod *> (item->GetItem());
            if (method != NULL)
            {
                if (method->Type() == kFEC)
                {
                      _selectedMethod = method;
                }
                if (method->Type() == kNACK)
                {
                    _selectedMethod = method;
                }
                if (method->Type() == kNackFec)
                {
                    _selectedMethod = method;
                }
                method->UpdateParameters(&_currentParameters);
            }
        }
        if (_selectedMethod != NULL && _selectedMethod->Type() != kFEC)
        {
            _selectedMethod = method;
        }
     }
     else
     {
         _selectedMethod = newMethod;
         _selectedMethod->UpdateParameters(&_currentParameters);
     }
    return true;
}

VCMProtectionMethod*
VCMLossProtectionLogic::SelectedMethod() const
{
    return _selectedMethod;
}

void VCMLossProtectionLogic::Reset()
{
    const WebRtc_Word64 now = VCMTickTime::MillisecondTimestamp();
    _lastPrUpdateT = now;
    _lastPacketPerFrameUpdateT = now;
    _lastPacketPerFrameUpdateTKey = now;
    _lossPr255.Reset(0.9999f);
    _packetsPerFrame.Reset(0.9999f);
    _fecRateDelta = _fecRateKey = 0;
    for (WebRtc_Word32 i = 0; i < kLossPrHistorySize; i++)
    {
        _lossPrHistory[i].lossPr255 = 0;
        _lossPrHistory[i].timeMs = -1;
    }
    _shortMaxLossPr255 = 0;
    ClearLossProtections();
}

}