/* 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-2021, 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     EncModeCtrl.h
    \brief    Encoder controller for trying out specific modes
*/

#ifndef __ENCMODECTRL__
#define __ENCMODECTRL__

// Include files
#include "EncCfg.h"

#include "CommonLib/CommonDef.h"
#include "CommonLib/CodingStructure.h"
#include "InterSearch.h"

#include <typeinfo>
#include <vector>

//////////////////////////////////////////////////////////////////////////
// Encoder modes to try out
//////////////////////////////////////////////////////////////////////////


enum EncTestModeType
{
  ETM_HASH_INTER,
  ETM_MERGE_SKIP,
  ETM_INTER_ME,
  ETM_AFFINE,
  ETM_MERGE_GEO,
  ETM_INTRA,
  ETM_PALETTE,
  ETM_SPLIT_QT,
  ETM_SPLIT_BT_H,
  ETM_SPLIT_BT_V,
  ETM_SPLIT_TT_H,
  ETM_SPLIT_TT_V,
  ETM_POST_DONT_SPLIT, // dummy mode to collect the data from the unsplit coding
#if REUSE_CU_RESULTS
  ETM_RECO_CACHED,
#endif
  ETM_TRIGGER_IMV_LIST,
  ETM_IBC,    // ibc mode
  ETM_IBC_MERGE, // ibc merge mode
  ETM_INVALID
};

enum EncTestModeOpts
{
  ETO_STANDARD    =  0,                   // empty      (standard option)
  ETO_FORCE_MERGE =  1<<0,                // bit   0    (indicates forced merge)
  ETO_IMV_SHIFT   =     1,                // bits  1-3  (imv parameter starts at bit 1)
  ETO_IMV         =  7<<ETO_IMV_SHIFT,    // bits  1-3  (imv parameter uses 3 bits)
  ETO_DUMMY       =  1<<5,                // bit   5    (dummy)
  ETO_INVALID     = 0xffffffff            // bits 0-31  (invalid option)
};

static void getAreaIdx(const Area& area, const PreCalcValues &pcv, unsigned &idx1, unsigned &idx2, unsigned &idx3, unsigned &idx4)
{
  idx1 = (area.x & pcv.maxCUWidthMask)  >> MIN_CU_LOG2;
  idx2 = (area.y & pcv.maxCUHeightMask) >> MIN_CU_LOG2;
  idx3 = gp_sizeIdxInfo->idxFrom( area.width  );
  idx4 = gp_sizeIdxInfo->idxFrom( area.height );
}

struct EncTestMode
{
  EncTestMode()
    : type( ETM_INVALID ), opts( ETO_INVALID  ), qp( -1  ) {}
  EncTestMode( EncTestModeType _type )
    : type( _type       ), opts( ETO_STANDARD ), qp( -1  ) {}
  EncTestMode( EncTestModeType _type, int _qp )
    : type( _type       ), opts( ETO_STANDARD ), qp( _qp ) {}
  EncTestMode( EncTestModeType _type, EncTestModeOpts _opts, int _qp )
    : type( _type       ), opts( _opts        ), qp( _qp ) {}

  EncTestModeType type;
  EncTestModeOpts opts;
  int             qp;
  double          maxCostAllowed;
};


inline bool isModeSplit( const EncTestMode& encTestmode )
{
  switch( encTestmode.type )
  {
  case ETM_SPLIT_QT     :
  case ETM_SPLIT_BT_H   :
  case ETM_SPLIT_BT_V   :
  case ETM_SPLIT_TT_H   :
  case ETM_SPLIT_TT_V   :
    return true;
  default:
    return false;
  }
}

inline bool isModeNoSplit( const EncTestMode& encTestmode )
{
  return !isModeSplit( encTestmode ) && encTestmode.type != ETM_POST_DONT_SPLIT;
}

inline bool isModeInter( const EncTestMode& encTestmode ) // perhaps remove
{
  return (   encTestmode.type == ETM_INTER_ME
          || encTestmode.type == ETM_MERGE_SKIP
          || encTestmode.type == ETM_AFFINE
          || encTestmode.type == ETM_MERGE_GEO
          || encTestmode.type == ETM_HASH_INTER
         );
}

inline PartSplit getPartSplit( const EncTestMode& encTestmode )
{
  switch( encTestmode.type )
  {
  case ETM_SPLIT_QT     : return CU_QUAD_SPLIT;
  case ETM_SPLIT_BT_H   : return CU_HORZ_SPLIT;
  case ETM_SPLIT_BT_V   : return CU_VERT_SPLIT;
  case ETM_SPLIT_TT_H   : return CU_TRIH_SPLIT;
  case ETM_SPLIT_TT_V   : return CU_TRIV_SPLIT;
  default:                return CU_DONT_SPLIT;
  }
}

inline EncTestMode getCSEncMode( const CodingStructure& cs )
{
  return EncTestMode( EncTestModeType( (unsigned)cs.features[ENC_FT_ENC_MODE_TYPE] ),
                      EncTestModeOpts( (unsigned)cs.features[ENC_FT_ENC_MODE_OPTS] ),
                      false);
}



//////////////////////////////////////////////////////////////////////////
// EncModeCtrl controls if specific modes should be tested
//////////////////////////////////////////////////////////////////////////

struct ComprCUCtx
{
  ComprCUCtx() : testModes(), extraFeatures()
  {
  }

  ComprCUCtx( const CodingStructure& cs, const uint32_t _minDepth, const uint32_t _maxDepth, const uint32_t numExtraFeatures )
    : minDepth      ( _minDepth  )
    , maxDepth      ( _maxDepth  )
    , testModes     (            )
    , lastTestMode  (            )
    , earlySkip     ( false      )
    , isHashPerfectMatch
                    ( false      )
    , bestCS        ( nullptr    )
    , bestCU        ( nullptr    )
    , bestTU        ( nullptr    )
    , extraFeatures (            )
    , extraFeaturesd(            )
    , bestInterCost ( MAX_DOUBLE )
    , bestMtsSize2Nx2N1stPass
                    ( MAX_DOUBLE )
    , skipSecondMTSPass
                    ( false )
    , interHad      (std::numeric_limits<Distortion>::max())
    , bestCostWithoutSplitFlags( MAX_DOUBLE )
    , bestCostMtsFirstPassNoIsp( MAX_DOUBLE )
    , bestCostIsp   ( MAX_DOUBLE )
    , ispWasTested  ( false )
    , bestPredModeDCT2
                    ( UINT8_MAX )
    , relatedCuIsValid
                    ( false )
    , ispPredModeVal( 0 )
    , bestDCT2NonISPCost
                    ( MAX_DOUBLE )
    , bestNonDCT2Cost
                    ( MAX_DOUBLE )
    , bestISPIntraMode
                    ( UINT8_MAX )
    , mipFlag       ( false )
    , ispMode       ( NOT_INTRA_SUBPARTITIONS )
    , ispLfnstIdx   ( 0 )
    , stopNonDCT2Transforms
                    ( false )
  {
    getAreaIdx( cs.area.Y(), *cs.pcv, cuX, cuY, cuW, cuH );
    partIdx = ( ( cuX << 8 ) | cuY );

    extraFeatures.reserve( numExtraFeatures );
    extraFeatures.resize ( numExtraFeatures, 0 );

    extraFeaturesd.reserve( numExtraFeatures );
    extraFeaturesd.resize ( numExtraFeatures, 0.0 );
  }

  unsigned                          minDepth;
  unsigned                          maxDepth;
  unsigned                          cuX, cuY, cuW, cuH, partIdx;
  std::vector<EncTestMode>          testModes;
  EncTestMode                       lastTestMode;
  bool                              earlySkip;
  bool                              isHashPerfectMatch;
  CodingStructure                  *bestCS;
  CodingUnit                       *bestCU;
  TransformUnit                    *bestTU;
  static_vector<int64_t,  30>         extraFeatures;
  static_vector<double, 30>         extraFeaturesd;
  double                            bestInterCost;
  double                            bestMtsSize2Nx2N1stPass;
  bool                              skipSecondMTSPass;
  Distortion                        interHad;
  double                            bestCostWithoutSplitFlags;
  double                            bestCostMtsFirstPassNoIsp;
  double                            bestCostIsp;
  bool                              ispWasTested;
  uint16_t                          bestPredModeDCT2;
  bool                              relatedCuIsValid;
  uint16_t                          ispPredModeVal;
  double                            bestDCT2NonISPCost;
  double                            bestNonDCT2Cost;
  uint8_t                           bestISPIntraMode;
  bool                              mipFlag;
  uint8_t                           ispMode;
  uint8_t                           ispLfnstIdx;
  bool                              stopNonDCT2Transforms;

  template<typename T> T    get( int ft )       const { return typeid(T) == typeid(double) ? (T&)extraFeaturesd[ft] : T(extraFeatures[ft]); }
  template<typename T> void set( int ft, T val )      { extraFeatures [ft] = int64_t( val ); }
  void                      set( int ft, double val ) { extraFeaturesd[ft] = val; }
};

//////////////////////////////////////////////////////////////////////////
// EncModeCtrl - abstract class specifying the general flow of mode control
//////////////////////////////////////////////////////////////////////////

class EncModeCtrl
{
protected:

  const EncCfg         *m_pcEncCfg;
  const class RateCtrl *m_pcRateCtrl;
        class RdCost   *m_pcRdCost;
  const Slice          *m_slice;
#if SHARP_LUMA_DELTA_QP
  int                   m_lumaLevelToDeltaQPLUT[LUMA_LEVEL_TO_DQP_LUT_MAXSIZE];
  int                   m_lumaQPOffset;
#endif
  bool                  m_fastDeltaQP;
  static_vector<ComprCUCtx, ( MAX_CU_DEPTH << 2 )> m_ComprCUCtxList;
  InterSearch*          m_pcInterSearch;

  bool                  m_doPlt;

public:

  virtual ~EncModeCtrl              () {}

  virtual void create               ( const EncCfg& cfg )                                                                   = 0;
  virtual void destroy              ()                                                                                      = 0;
  virtual void initCTUEncoding      ( const Slice &slice )                                                                  = 0;
  virtual void initCULevel          ( Partitioner &partitioner, const CodingStructure& cs )                                 = 0;
  virtual void finishCULevel        ( Partitioner &partitioner )                                                            = 0;

protected:

  virtual bool tryMode              ( const EncTestMode& encTestmode, const CodingStructure &cs, Partitioner& partitioner ) = 0;

public:

  virtual bool useModeResult        ( const EncTestMode& encTestmode, CodingStructure*& tempCS,  Partitioner& partitioner ) = 0;
  virtual bool checkSkipOtherLfnst  ( const EncTestMode& encTestmode, CodingStructure*& tempCS,  Partitioner& partitioner ) = 0;

  void         init                 ( EncCfg *pCfg, RateCtrl *pRateCtrl, RdCost *pRdCost );
  bool         tryModeMaster        ( const EncTestMode& encTestmode, const CodingStructure &cs, Partitioner& partitioner );
  bool         nextMode             ( const CodingStructure &cs, Partitioner &partitioner );
  EncTestMode  currTestMode         () const;
  EncTestMode  lastTestMode         () const;
  void         setEarlySkipDetected ();
  void         setIsHashPerfectMatch( bool b ) { m_ComprCUCtxList.back().isHashPerfectMatch = b; }
  bool         getIsHashPerfectMatch() { return m_ComprCUCtxList.back().isHashPerfectMatch; }
  virtual void setBest              ( CodingStructure& cs );
  bool         anyMode              () const;

  const ComprCUCtx& getComprCUCtx   () { CHECK( m_ComprCUCtxList.empty(), "Accessing empty list!"); return m_ComprCUCtxList.back(); }

#if SHARP_LUMA_DELTA_QP
  void                  initLumaDeltaQpLUT();
  int                   calculateLumaDQP  ( const CPelBuf& rcOrg );
#endif
#if JVET_W0043
  int                                 calculateLumaDQPsmooth(const CPelBuf& rcOrg, int baseQP, double threshold, double scale, double offset, int limit);
#else
  int                                 calculateLumaDQPsmooth(const CPelBuf& rcOrg, int baseQP);
#endif
  void setFastDeltaQp                 ( bool b )                {        m_fastDeltaQP = b;                               }
  bool getFastDeltaQp                 ()                  const { return m_fastDeltaQP;                                   }

  double getBestInterCost             ()                  const { return m_ComprCUCtxList.back().bestInterCost;           }
  Distortion getInterHad              ()                  const { return m_ComprCUCtxList.back().interHad;                }
  void enforceInterHad                ( Distortion had )        {        m_ComprCUCtxList.back().interHad = had;          }
  double getMtsSize2Nx2NFirstPassCost ()                  const { return m_ComprCUCtxList.back().bestMtsSize2Nx2N1stPass; }
  bool   getSkipSecondMTSPass         ()                  const { return m_ComprCUCtxList.back().skipSecondMTSPass;       }
  void   setSkipSecondMTSPass         ( bool b )                { m_ComprCUCtxList.back().skipSecondMTSPass = b;          }
  double getBestCostWithoutSplitFlags ()                  const { return m_ComprCUCtxList.back().bestCostWithoutSplitFlags;         }
  void   setBestCostWithoutSplitFlags ( double cost )           { m_ComprCUCtxList.back().bestCostWithoutSplitFlags = cost;         }
  double getMtsFirstPassNoIspCost     ()                  const { return m_ComprCUCtxList.back().bestCostMtsFirstPassNoIsp;         }
  void   setMtsFirstPassNoIspCost     ( double cost )           { m_ComprCUCtxList.back().bestCostMtsFirstPassNoIsp = cost;         }
  double getIspCost                   ()                  const { return m_ComprCUCtxList.back().bestCostIsp; }
  void   setIspCost                   ( double val )            { m_ComprCUCtxList.back().bestCostIsp = val; }
  bool   getISPWasTested              ()                  const { return m_ComprCUCtxList.back().ispWasTested; }
  void   setISPWasTested              ( bool val )              { m_ComprCUCtxList.back().ispWasTested = val; }
  void   setBestPredModeDCT2          ( uint16_t val )          { m_ComprCUCtxList.back().bestPredModeDCT2 = val; }
  uint16_t getBestPredModeDCT2        ()                  const { return m_ComprCUCtxList.back().bestPredModeDCT2; }
  bool   getRelatedCuIsValid          ()                  const { return m_ComprCUCtxList.back().relatedCuIsValid; }
  void   setRelatedCuIsValid          ( bool val )              { m_ComprCUCtxList.back().relatedCuIsValid = val; }
  uint16_t getIspPredModeValRelCU     ()                  const { return m_ComprCUCtxList.back().ispPredModeVal; }
  void   setIspPredModeValRelCU       ( uint16_t val )          { m_ComprCUCtxList.back().ispPredModeVal = val; }
  double getBestDCT2NonISPCostRelCU   ()                  const { return m_ComprCUCtxList.back().bestDCT2NonISPCost; }
  void   setBestDCT2NonISPCostRelCU   ( double val )            { m_ComprCUCtxList.back().bestDCT2NonISPCost = val; }
  double getBestNonDCT2Cost           ()                  const { return m_ComprCUCtxList.back().bestNonDCT2Cost; }
  void   setBestNonDCT2Cost           ( double val )            { m_ComprCUCtxList.back().bestNonDCT2Cost = val; }
  uint8_t getBestISPIntraModeRelCU    ()                  const { return m_ComprCUCtxList.back().bestISPIntraMode; }
  void   setBestISPIntraModeRelCU     ( uint8_t val )           { m_ComprCUCtxList.back().bestISPIntraMode = val; }
  void   setMIPFlagISPPass            ( bool val )              { m_ComprCUCtxList.back().mipFlag = val; }
  void   setISPMode                   ( uint8_t val )           { m_ComprCUCtxList.back().ispMode = val; }
  void   setISPLfnstIdx               ( uint8_t val )           { m_ComprCUCtxList.back().ispLfnstIdx = val; }
  bool   getStopNonDCT2Transforms     ()                  const { return m_ComprCUCtxList.back().stopNonDCT2Transforms; }
  void   setStopNonDCT2Transforms     ( bool val )              { m_ComprCUCtxList.back().stopNonDCT2Transforms = val; }
  void setInterSearch                 (InterSearch* pcInterSearch)   { m_pcInterSearch = pcInterSearch; }
  void   setPltEnc                    ( bool b )                { m_doPlt = b; }
  bool   getPltEnc()                                      const { return m_doPlt; }

#if GDR_ENABLED
void forceIntraMode()
{ 
  // remove all inter or split to force make intra      
  int n = (int)m_ComprCUCtxList.back().testModes.size();   
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (isModeInter(etm.type)) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;          
    }
  }  
}

void forceIntraNoSplit()
{
  // remove all inter or split to force make intra        
  int n = (int)m_ComprCUCtxList.back().testModes.size();

  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (isModeInter(etm.type) || isModeSplit(etm.type)) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }  
}

// Note: ForceInterMode
void forceInterMode()
{    
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_INTRA) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;        
    }
  }  
}

void removeHashInter()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_HASH_INTER) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeMergeSkip()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_MERGE_SKIP) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeInterME()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_INTER_ME) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeAffine()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_AFFINE) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeMergeGeo()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_MERGE_GEO) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeIntra()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    if (etm.type == ETM_INTRA) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void removeBadMode()
{  
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_INTER_ME && ((etm.opts & ETO_IMV) >> ETO_IMV_SHIFT) > 2) 
    {  
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
      break;
    }
  }  
}

bool anyPredModeLeft()
{ 
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_HASH_INTER ||
        etm.type == ETM_MERGE_SKIP || 
        etm.type == ETM_INTER_ME   || 
        etm.type == ETM_AFFINE     || 
        etm.type == ETM_MERGE_GEO  || 
        etm.type == ETM_INTRA      ||
        etm.type == ETM_PALETTE    || 
        etm.type == ETM_IBC        ||
        etm.type == ETM_IBC_MERGE) {
      return true;
    }
  }

  return false;
}

bool anyIntraIBCMode()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_INTRA || etm.type == ETM_IBC) 
    {
      return true;
    }
  }

  return false;
}

void forceRemoveDontSplit()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_POST_DONT_SPLIT) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceVerSplitOnly()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];       
 
    if (etm.type != ETM_SPLIT_QT && etm.type != ETM_SPLIT_BT_V && etm.type != ETM_SPLIT_TT_V) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }   
}

void forceRemoveTTV()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    
    if (etm.type == ETM_SPLIT_TT_V) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceRemoveBTV()
{  
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_SPLIT_BT_V) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceRemoveQT()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
 
    if (etm.type == ETM_SPLIT_QT) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }  
}

void forceRemoveHT()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_SPLIT_BT_H || etm.type == ETM_SPLIT_TT_H) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceRemoveQTHT()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];
    
    if (etm.type == ETM_SPLIT_QT || etm.type == ETM_SPLIT_BT_H || etm.type == ETM_SPLIT_TT_H) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceRemoveAllSplit()
{
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type == ETM_SPLIT_QT || etm.type == ETM_SPLIT_BT_H || etm.type == ETM_SPLIT_BT_V || etm.type == ETM_SPLIT_TT_H || etm.type == ETM_SPLIT_TT_V) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;
    }
  }
}

void forceQTonlyMode()
{
  // remove all split except QT  
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];

    if (etm.type != ETM_SPLIT_QT) 
    {
      m_ComprCUCtxList.back().testModes.erase(m_ComprCUCtxList.back().testModes.begin() + j);
      j--;
      n--;        
    }
  }    
}

const char* printType(EncTestModeType type)
{
  char *ret;

  switch (type) {
  case  0: ret = strdup("Hash"); break;
  case  1: ret = strdup("Mkip"); break;
  case  2: ret = strdup("Inter"); break;
  case  3: ret = strdup("Affi"); break;
  case  4: ret = strdup("Tria"); break;
  case  5: ret = strdup("Intra"); break;
  case  6: ret = strdup("Palet"); break;

  case  7: ret = strdup("QT"); break;
  case  8: ret = strdup("BTH"); break;
  case  9: ret = strdup("BTV"); break;
  case 10: ret = strdup("TTH"); break;
  case 11: ret = strdup("TTV"); break;
  case 12: ret = strdup("|"); break;
  case 13: ret = strdup("CACHE"); break;
  case 14: ret = strdup("IMV"); break;
  case 15: ret = strdup("IBC"); break;
  case 16: ret = strdup("IBCM"); break;
  default:
    ret = strdup("INVALID");
  }

  return ret;
}

void printMode()
{
  // remove all inter or split to force make intra          
  int n = (int)m_ComprCUCtxList.back().testModes.size();
  printf("-:[");
  for (int j = 0; j < n; j++) 
  {
    const EncTestMode etm = m_ComprCUCtxList.back().testModes[j];      
    printf(" %s", printType(etm.type));      
  }
  printf("]\n");   
}
#endif

protected:
  void xExtractFeatures ( const EncTestMode encTestmode, CodingStructure& cs );
  void xGetMinMaxQP     ( int& iMinQP, int& iMaxQP, const CodingStructure& cs, const Partitioner &pm, const int baseQP, const SPS& sps, const PPS& pps, const PartSplit splitMode );
  int  xComputeDQP      ( const CodingStructure &cs, const Partitioner &pm );
};


//////////////////////////////////////////////////////////////////////////
// some utility interfaces that expose some functionality that can be used without concerning about which particular controller is used
//////////////////////////////////////////////////////////////////////////
struct SaveLoadStructSbt
{
  uint8_t  numPuInfoStored;
  uint32_t puSse[SBT_NUM_SL];
  uint8_t  puSbt[SBT_NUM_SL];
  uint8_t  puTrs[SBT_NUM_SL];
};

class SaveLoadEncInfoSbt
{
protected:
  void init( const Slice &slice );
  void create();
  void destroy();

private:
  SaveLoadStructSbt ****m_saveLoadSbt;
  Slice const       *m_sliceSbt;

public:
  virtual  ~SaveLoadEncInfoSbt() { }
  void     resetSaveloadSbt( int maxSbtSize );
  uint16_t findBestSbt( const UnitArea& area, const uint32_t curPuSse );
  bool     saveBestSbt( const UnitArea& area, const uint32_t curPuSse, const uint8_t curPuSbt, const uint8_t curPuTrs );
};

static const int MAX_STORED_CU_INFO_REFS = 4;

struct CodedCUInfo
{
  bool isInter;
  bool isIntra;
  bool isSkip;
  bool isMMVDSkip;
  bool isIBC;
  bool validMv[NUM_REF_PIC_LIST_01][MAX_STORED_CU_INFO_REFS];
  Mv   saveMv [NUM_REF_PIC_LIST_01][MAX_STORED_CU_INFO_REFS];

  uint8_t BcwIdx;
  char    selectColorSpaceOption;  // 0 - test both two color spaces; 1 - only test the first color spaces; 2 - only test the second color spaces
  uint16_t ispPredModeVal;
  double   bestDCT2NonISPCost;
  double   bestCost;
  double   bestNonDCT2Cost;
  bool     relatedCuIsValid;
  uint8_t  bestISPIntraMode;
};

class CacheBlkInfoCtrl
{
private:

  unsigned         m_numWidths, m_numHeights;
  Slice const     *m_slice_chblk;
  // x in CTU, y in CTU, width, height
  CodedCUInfo   ***m_codedCUInfo[MAX_CU_SIZE >> MIN_CU_LOG2][MAX_CU_SIZE >> MIN_CU_LOG2];

protected:

  void create   ();
  void destroy  ();
  void init     ( const Slice &slice );

  CodedCUInfo& getBlkInfo( const UnitArea& area );

public:

  virtual ~CacheBlkInfoCtrl() {}

  bool isSkip ( const UnitArea& area );
  bool isMMVDSkip(const UnitArea& area);
  bool getMv  ( const UnitArea& area, const RefPicList refPicList, const int iRefIdx,       Mv& rMv ) const;
  void setMv  ( const UnitArea& area, const RefPicList refPicList, const int iRefIdx, const Mv& rMv );

  bool  getInter( const UnitArea& area );
  void  setBcwIdx( const UnitArea& area, uint8_t gBiIdx );
  uint8_t getBcwIdx( const UnitArea& area );

  char  getSelectColorSpaceOption(const UnitArea& area);
};

#if REUSE_CU_RESULTS
struct BestEncodingInfo
{
  CodingUnit     cu;
  PredictionUnit pu;
#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
  TransformUnit  tus[MAX_NUM_TUS];
  size_t         numTus;
#else
  TransformUnit  tu;
#endif
  EncTestMode    testMode;

  int            poc;
};

class BestEncInfoCache
{
private:

  unsigned            m_numWidths, m_numHeights;
  const Slice        *m_slice_bencinf;
  BestEncodingInfo ***m_bestEncInfo[MAX_CU_SIZE >> MIN_CU_LOG2][MAX_CU_SIZE >> MIN_CU_LOG2];
  TCoeff             *m_pCoeff;
  Pel                *m_pPcmBuf;
  bool               *m_runType;
  CodingStructure     m_dummyCS;
  XUCache             m_dummyCache;

protected:

  void create   ( const ChromaFormat chFmt );
  void destroy  ();

  bool setFromCs( const CodingStructure& cs, const Partitioner& partitioner );
  bool isValid  ( const CodingStructure &cs, const Partitioner &partitioner, int qp );
public:

  BestEncInfoCache() : m_slice_bencinf( nullptr ), m_dummyCS( m_dummyCache.cuCache, m_dummyCache.puCache, m_dummyCache.tuCache ) {}
  virtual ~BestEncInfoCache() {}
  void     init     ( const Slice &slice );
  bool     setCsFrom( CodingStructure& cs, EncTestMode& testMode, const Partitioner& partitioner ) const;
};

#endif
//////////////////////////////////////////////////////////////////////////
// EncModeCtrlMTnoRQT - allows and controls modes introduced by QTBT (inkl. multi-type-tree)
//                    - only 2Nx2N, no RQT, additional binary/triary CU splits
//////////////////////////////////////////////////////////////////////////

class EncModeCtrlMTnoRQT : public EncModeCtrl, public CacheBlkInfoCtrl
#if REUSE_CU_RESULTS
  , public BestEncInfoCache
#endif
  , public SaveLoadEncInfoSbt
{
  enum ExtraFeatures
  {
    DID_HORZ_SPLIT = 0,
    DID_VERT_SPLIT,
    DID_QUAD_SPLIT,
    BEST_HORZ_SPLIT_COST,
    BEST_VERT_SPLIT_COST,
    BEST_TRIH_SPLIT_COST,
    BEST_TRIV_SPLIT_COST,
    DO_TRIH_SPLIT,
    DO_TRIV_SPLIT,
    BEST_NON_SPLIT_COST,
    BEST_NO_IMV_COST,
    BEST_IMV_COST,
    QT_BEFORE_BT,
    IS_BEST_NOSPLIT_SKIP,
    MAX_QT_SUB_DEPTH,
#if REUSE_CU_RESULTS
    IS_REUSING_CU,
#endif
    NUM_EXTRA_FEATURES
  };

  unsigned m_skipThreshold;
#if GDR_ENABLED
  EncCfg m_encCfg;
#endif

public:

  virtual void create             ( const EncCfg& cfg );
  virtual void destroy            ();
  virtual void initCTUEncoding    ( const Slice &slice );
  virtual void initCULevel        ( Partitioner &partitioner, const CodingStructure& cs );
  virtual void finishCULevel      ( Partitioner &partitioner );

  virtual bool tryMode            ( const EncTestMode& encTestmode, const CodingStructure &cs, Partitioner& partitioner );
  virtual bool useModeResult      ( const EncTestMode& encTestmode, CodingStructure*& tempCS,  Partitioner& partitioner );

  virtual bool checkSkipOtherLfnst( const EncTestMode& encTestmode, CodingStructure*& tempCS, Partitioner& partitioner );

#if JVET_X0061_TT_ENC_SPEEDUP
  bool xSkipTreeCandidate(const PartSplit split, const double* splitRdCostBest, const SliceType& sliceType) const;
#endif
};


//! \}

#endif // __ENCMODECTRL__
