/* The copyright in this software is being made available under the BSD
 * License, included below. This software may be subject to other third party
 * and contributor rights, including patent rights, and no such rights are
 * granted under this license.
 *
 * Copyright (c) 2010-2015, ITU/ISO/IEC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

/** \file     WeightPredAnalysis.cpp
    \brief    weighted prediction encoder class
*/

#include "../TLibCommon/CommonDef.h"
#include "../TLibCommon/TComSlice.h"
#include "../TLibCommon/TComPic.h"
#include "../TLibCommon/TComPicYuv.h"
#include "WeightPredAnalysis.h"

#define ABS(a)    ((a) < 0 ? - (a) : (a))
#define DTHRESH (0.99)

WeightPredAnalysis::WeightPredAnalysis()
{
  for ( UInt lst =0 ; lst<NUM_REF_PIC_LIST_01 ; lst++ )
  {
    for ( Int iRefIdx=0 ; iRefIdx<MAX_NUM_REF ; iRefIdx++ )
    {
      for ( Int comp=0 ; comp<MAX_NUM_COMPONENT ;comp++ )
      {
        WPScalingParam  *pwp   = &(m_wp[lst][iRefIdx][comp]);
        pwp->bPresentFlag      = false;
        pwp->uiLog2WeightDenom = 0;
        pwp->iWeight           = 1;
        pwp->iOffset           = 0;
      }
    }
  }
}


//! calculate AC and DC values for current original image
Void WeightPredAnalysis::xCalcACDCParamSlice(TComSlice *const slice)
{
  //===== calculate AC/DC value =====
  TComPicYuv*   pPic = slice->getPic()->getPicYuvOrg();

  WPACDCParam weightACDCParam[MAX_NUM_COMPONENT];

  for(Int componentIndex = 0; componentIndex < pPic->getNumberValidComponents(); componentIndex++)
  {
    const ComponentID compID = ComponentID(componentIndex);

    // calculate DC/AC value for channel

    const Int iStride = pPic->getStride(compID);
    const Int iWidth  = pPic->getWidth(compID);
    const Int iHeight = pPic->getHeight(compID);

    const Int iSample = iWidth*iHeight;

    Int64 iOrgDC = 0;
    {
      const Pel *pPel = pPic->getAddr(compID);

      for(Int y = 0; y < iHeight; y++, pPel+=iStride )
      {
        for(Int x = 0; x < iWidth; x++ )
        {
          iOrgDC += (Int)( pPel[x] );
        }
      }
    }

    const Int64 iOrgNormDC = ((iOrgDC+(iSample>>1)) / iSample);

    Int64 iOrgAC = 0;
    {
      const Pel *pPel = pPic->getAddr(compID);

      for(Int y = 0; y < iHeight; y++, pPel += iStride )
      {
        for(Int x = 0; x < iWidth; x++ )
        {
          iOrgAC += abs( (Int)pPel[x] - (Int)iOrgNormDC );
        }
      }
    }

    const Int fixedBitShift = (slice->getSPS()->getSpsRangeExtension().getHighPrecisionOffsetsEnabledFlag())?RExt__PREDICTION_WEIGHTING_ANALYSIS_DC_PRECISION:0;
    weightACDCParam[compID].iDC = (((iOrgDC<<fixedBitShift)+(iSample>>1)) / iSample);
    weightACDCParam[compID].iAC = iOrgAC;
  }

  slice->setWpAcDcParam(weightACDCParam);
}


//! check weighted pred or non-weighted pred
Void  WeightPredAnalysis::xCheckWPEnable(TComSlice *const slice)
{
  const TComPicYuv *pPic = slice->getPic()->getPicYuvOrg();

  Int iPresentCnt = 0;
  for ( UInt lst=0 ; lst<NUM_REF_PIC_LIST_01 ; lst++ )
  {
    for ( Int iRefIdx=0 ; iRefIdx<MAX_NUM_REF ; iRefIdx++ )
    {
      for(Int componentIndex = 0; componentIndex < pPic->getNumberValidComponents(); componentIndex++)
      {
        WPScalingParam  *pwp = &(m_wp[lst][iRefIdx][componentIndex]);
        iPresentCnt += (Int)pwp->bPresentFlag;
      }
    }
  }

  if(iPresentCnt==0)
  {
    slice->setTestWeightPred(false);
    slice->setTestWeightBiPred(false);

    for ( UInt lst=0 ; lst<NUM_REF_PIC_LIST_01 ; lst++ )
    {
      for ( Int iRefIdx=0 ; iRefIdx<MAX_NUM_REF ; iRefIdx++ )
      {
        for(Int componentIndex = 0; componentIndex < pPic->getNumberValidComponents(); componentIndex++)
        {
          WPScalingParam  *pwp = &(m_wp[lst][iRefIdx][componentIndex]);

          pwp->bPresentFlag      = false;
          pwp->uiLog2WeightDenom = 0;
          pwp->iWeight           = 1;
          pwp->iOffset           = 0;
        }
      }
    }
    slice->setWpScaling( m_wp );
  }
  else
  {
    slice->setTestWeightPred(slice->getPPS()->getUseWP());
    slice->setTestWeightBiPred(slice->getPPS()->getWPBiPred());
  }
}


//! estimate wp tables for explicit wp
Void WeightPredAnalysis::xEstimateWPParamSlice(TComSlice *const slice)
{
  Int  iDenom         = 6;
  Bool validRangeFlag = false;

  if(slice->getNumRefIdx(REF_PIC_LIST_0)>3)
  {
    iDenom = 7;
  }

  do
  {
    validRangeFlag = xUpdatingWPParameters(slice, iDenom);
    if (!validRangeFlag)
    {
      iDenom--; // decrement to satisfy the range limitation
    }
  } while (validRangeFlag == false);

  // selecting whether WP is used, or not (fast search)
  // NOTE: This is not operating on a slice, but the entire picture.
  xSelectWP(slice, iDenom);

  slice->setWpScaling( m_wp );
}


//! update wp tables for explicit wp w.r.t range limitation
Bool WeightPredAnalysis::xUpdatingWPParameters(TComSlice *const slice, const Int log2Denom)
{
  const Int  numComp                    = slice->getPic()->getPicYuvOrg()->getNumberValidComponents();
  const Bool bUseHighPrecisionWeighting = slice->getSPS()->getSpsRangeExtension().getHighPrecisionOffsetsEnabledFlag();
  const Int numPredDir                  = slice->isInterP() ? 1 : 2;

  assert (numPredDir <= Int(NUM_REF_PIC_LIST_01));

  for ( Int refList = 0; refList < numPredDir; refList++ )
  {
    const RefPicList eRefPicList = ( refList ? REF_PIC_LIST_1 : REF_PIC_LIST_0 );

    for ( Int refIdxTemp = 0; refIdxTemp < slice->getNumRefIdx(eRefPicList); refIdxTemp++ )
    {
      WPACDCParam *currWeightACDCParam, *refWeightACDCParam;
      slice->getWpAcDcParam(currWeightACDCParam);
      slice->getRefPic(eRefPicList, refIdxTemp)->getSlice(0)->getWpAcDcParam(refWeightACDCParam);

      for ( Int comp = 0; comp < numComp; comp++ )
      {
        const ComponentID compID        = ComponentID(comp);
        const Int         bitDepth      = slice->getSPS()->getBitDepth(toChannelType(compID));
        const Int         range         = bUseHighPrecisionWeighting ? (1<<bitDepth)/2 : 128;
        const Int         realLog2Denom = log2Denom + (bUseHighPrecisionWeighting ? RExt__PREDICTION_WEIGHTING_ANALYSIS_DC_PRECISION : (bitDepth - 8));
        const Int         realOffset    = ((Int)1<<(realLog2Denom-1));

        // current frame
        const Int64 currDC = currWeightACDCParam[comp].iDC;
        const Int64 currAC = currWeightACDCParam[comp].iAC;
        // reference frame
        const Int64 refDC  = refWeightACDCParam[comp].iDC;
        const Int64 refAC  = refWeightACDCParam[comp].iAC;

        // calculating iWeight and iOffset params
        const Double dWeight = (refAC==0) ? (Double)1.0 : Clip3( -16.0, 15.0, ((Double)currAC / (Double)refAC) );
        const Int weight     = (Int)( 0.5 + dWeight * (Double)(1<<log2Denom) );
        const Int offset     = (Int)( ((currDC<<log2Denom) - ((Int64)weight * refDC) + (Int64)realOffset) >> realLog2Denom );

        Int clippedOffset;
        if(isChroma(compID)) // Chroma offset range limination
        {
          const Int pred        = ( range - ( ( range*weight)>>(log2Denom) ) );
          const Int deltaOffset = Clip3( -4*range, 4*range-1, (offset - pred) ); // signed 10bit

          clippedOffset = Clip3( -range, range-1, (deltaOffset + pred) );  // signed 8bit
        }
        else // Luma offset range limitation
        {
          clippedOffset = Clip3( -range, range-1, offset);
        }

        // Weighting factor limitation
        const Int defaultWeight = (1<<log2Denom);
        const Int deltaWeight   = (defaultWeight - weight);

        if(deltaWeight >= range || deltaWeight < -range)
        {
          return false;
        }

        m_wp[refList][refIdxTemp][comp].bPresentFlag      = true;
        m_wp[refList][refIdxTemp][comp].iWeight           = weight;
        m_wp[refList][refIdxTemp][comp].iOffset           = clippedOffset;
        m_wp[refList][refIdxTemp][comp].uiLog2WeightDenom = log2Denom;
      }
    }
  }
  return true;
}


//! select whether weighted pred enables or not.
Bool WeightPredAnalysis::xSelectWP(TComSlice *const slice, const Int log2Denom)
{
        TComPicYuv *const pPic                                = slice->getPic()->getPicYuvOrg();
  const Int               iDefaultWeight                      = ((Int)1<<log2Denom);
  const Int               iNumPredDir                         = slice->isInterP() ? 1 : 2;
  const Bool              useHighPrecisionPredictionWeighting = slice->getSPS()->getSpsRangeExtension().getHighPrecisionOffsetsEnabledFlag();

  assert (iNumPredDir <= Int(NUM_REF_PIC_LIST_01));

  for ( Int iRefList = 0; iRefList < iNumPredDir; iRefList++ )
  {
    const RefPicList eRefPicList = ( iRefList ? REF_PIC_LIST_1 : REF_PIC_LIST_0 );

    for ( Int iRefIdxTemp = 0; iRefIdxTemp < slice->getNumRefIdx(eRefPicList); iRefIdxTemp++ )
    {
      Int64 iSADWP = 0, iSADnoWP = 0;

      for(Int comp=0; comp<pPic->getNumberValidComponents(); comp++)
      {
        const ComponentID  compID     = ComponentID(comp);
              Pel         *pOrg       = pPic->getAddr(compID);
              Pel         *pRef       = slice->getRefPic(eRefPicList, iRefIdxTemp)->getPicYuvRec()->getAddr(compID);
        const Int          iOrgStride = pPic->getStride(compID);
        const Int          iRefStride = slice->getRefPic(eRefPicList, iRefIdxTemp)->getPicYuvRec()->getStride(compID);
        const Int          iWidth     = pPic->getWidth(compID);
        const Int          iHeight    = pPic->getHeight(compID);
        const Int          bitDepth   = slice->getSPS()->getBitDepth(toChannelType(compID));

        // calculate SAD costs with/without wp for luma
        iSADWP   += xCalcSADvalueWP(bitDepth, pOrg, pRef, iWidth, iHeight, iOrgStride, iRefStride, log2Denom, m_wp[iRefList][iRefIdxTemp][compID].iWeight, m_wp[iRefList][iRefIdxTemp][compID].iOffset, useHighPrecisionPredictionWeighting);
        iSADnoWP += xCalcSADvalueWP(bitDepth, pOrg, pRef, iWidth, iHeight, iOrgStride, iRefStride, log2Denom, iDefaultWeight, 0, useHighPrecisionPredictionWeighting);
      }

      const Double dRatio = ((Double)iSADWP / (Double)iSADnoWP);
      if(dRatio >= (Double)DTHRESH)
      {
        for(Int comp=0; comp<pPic->getNumberValidComponents(); comp++)
        {
          m_wp[iRefList][iRefIdxTemp][comp].bPresentFlag      = false;
          m_wp[iRefList][iRefIdxTemp][comp].iOffset           = 0;
          m_wp[iRefList][iRefIdxTemp][comp].iWeight           = iDefaultWeight;
          m_wp[iRefList][iRefIdxTemp][comp].uiLog2WeightDenom = log2Denom;
        }
      }
    }
  }

  return true;
}


//! calculate SAD values for both WP version and non-WP version.
Int64 WeightPredAnalysis::xCalcSADvalueWP(const Int   bitDepth,
                                          const Pel  *pOrgPel,
                                          const Pel  *pRefPel,
                                          const Int   iWidth,
                                          const Int   iHeight,
                                          const Int   iOrgStride,
                                          const Int   iRefStride,
                                          const Int   iLog2Denom,
                                          const Int   iWeight,
                                          const Int   iOffset,
                                          const Bool  useHighPrecisionPredictionWeighting)
{
  const Int64 iSize          = iWidth*iHeight;
  const Int64 iRealLog2Denom = useHighPrecisionPredictionWeighting ? iLog2Denom : (iLog2Denom + (bitDepth - 8));

  Int64 iSAD = 0;
  for( Int y = 0; y < iHeight; y++ )
  {
    for( Int x = 0; x < iWidth; x++ )
    {
      iSAD += ABS(( ((Int64)pOrgPel[x]<<(Int64)iLog2Denom) - ( (Int64)pRefPel[x] * (Int64)iWeight + ((Int64)iOffset<<iRealLog2Denom) ) ) );
    }
    pOrgPel += iOrgStride;
    pRefPel += iRefStride;
  }

  return (iSAD/iSize);
}
