/* 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-2017, 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     dtrace_blockstatistics.cpp
 *  \brief    DTrace block statistcis support for next software
 */

#include "dtrace_blockstatistics.h"
#include "dtrace.h"
#include "dtrace_next.h"
#include "CommonLib/Unit.h"
#include "CommonLib/Picture.h"
#include "CommonLib/UnitTools.h"
//#include "CommonLib/CodingStructure.h"

#if ENABLE_BLOCK_STATS
std::string GetBlockStatisticName(BlockStatistic statistic)
{
  auto statisticIterator = blockstatistic2description.find(statistic);
  // enforces that all delcared statistic enum items are also part of the map
  assert(statisticIterator != blockstatistic2description.end() && "A block statistics declared in the enum is missing in the map for statistic description.");

  return std::get<0>(statisticIterator->second);
}

std::string GetBlockStatisticTypeString(BlockStatistic statistic)
{
  auto statisticIterator = blockstatistic2description.find(statistic);
  // enforces that all delcared statistic enum items are also part of the map
  assert(statisticIterator != blockstatistic2description.end() && "A block statistics declared in the enum is missing in the map for statistic description.");

  BlockStatisticType statisticType = std::get<1>(statisticIterator->second);
  switch (statisticType) {
  case BlockStatisticType::Flag:
    return std::string("Flag");
    break;
  case BlockStatisticType::Vector:
    return std::string("Vector");
    break;
  case BlockStatisticType::Integer:
    return std::string("Integer");
    break;
  case BlockStatisticType::AffineTFVectors:
    return std::string("AffineTFVectors");
    break;
  default:
    assert(0);
    break;
  }
  return std::string();
}

std::string GetBlockStatisticTypeSpecificInfo(BlockStatistic statistic)
{
  auto statisticIterator = blockstatistic2description.find(statistic);
  // enforces that all delcared statistic enum items are also part of the map
  assert(statisticIterator != blockstatistic2description.end() && "A block statistics declared in the enum is missing in the map for statistic description.");

  return std::get<2>(statisticIterator->second);
}

void CDTrace::dtrace_block_scalar( int k, const CodingStructure &cs, std::string stat_type, signed value )
{
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%d\n", cs.picture->poc, cs.area.lx(), cs.area.ly(), cs.area.lwidth(), cs.area.lheight(), stat_type.c_str(), value );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s=%d\n", cs.picture->poc, cs.area.lx(), cs.area.ly(), cs.area.lwidth(), cs.area.lheight(), stat_type.c_str(), value );
#endif
}

void CDTrace::dtrace_block_scalar( int k, const CodingUnit &cu, std::string stat_type, signed value )
{
  const CodingStructure& cs = *cu.cs;
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%d\n", cs.picture->poc, cu.lx(), cu.ly(), cu.lwidth(), cu.lheight(), stat_type.c_str(), value );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s=%d\n", cs.picture->poc, cu.lx(), cu.ly(), cu.lwidth(), cu.lheight(), stat_type.c_str(), value );
#endif
}

void CDTrace::dtrace_block_vector( int k, const CodingUnit &cu, std::string stat_type, signed val_x, signed val_y )
{
  const CodingStructure& cs = *cu.cs;
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%4d;%4d\n", cs.picture->poc, cu.lx(), cu.ly(), cu.lwidth(), cu.lheight(), stat_type.c_str(), val_x, val_y );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s={%4d,%4d}\n", cs.picture->poc, cu.lx(), cu.ly(), cu.lwidth(), cu.lheight(), stat_type.c_str(), val_x, val_y );
#endif
}

void CDTrace::dtrace_block_scalar( int k, const PredictionUnit &pu, std::string stat_type, signed value )
{
  const CodingStructure& cs = *pu.cs;
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%d\n", cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(), value );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s=%d\n", cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(), value );
#endif
}

void CDTrace::dtrace_block_vector( int k, const PredictionUnit &pu, std::string stat_type, signed val_x, signed val_y )
{
  const CodingStructure& cs = *pu.cs;
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%4d;%4d\n", cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(), val_x, val_y );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s={%4d,%4d}\n", cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(), val_x, val_y );
#endif
}

void CDTrace::dtrace_block_affinetf( int k, const PredictionUnit &pu, std::string stat_type, signed val_x0, signed val_y0, signed val_x1, signed val_y1, signed val_x2, signed val_y2 )
{
  const CodingStructure& cs = *pu.cs;
#if BLOCK_STATS_AS_CSV
  dtrace<false>( k, "BlockStat;%d;%4d;%4d;%2d;%2d;%s;%4d;%4d;%4d;%4d;%4d;%4d\n",
                 cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(),
                 val_x0, val_y0, val_x1, val_y1 , val_x2, val_y2  );
#else
  dtrace<false>( k, "BlockStat: POC %d @(%4d,%4d) [%2dx%2d] %s={%4d,%4d,%4d,%4d,%4d,%4d}\n",
                 cs.picture->poc, pu.lx(), pu.ly(), pu.lwidth(), pu.lheight(), stat_type.c_str(),
                 val_x0, val_y0, val_x1, val_y1 , val_x2, val_y2  );
#endif
}



void writeBlockStatisticsHeader(const SPS *sps)
{
  static bool has_header_been_written = false;
  if (has_header_been_written)
  {
    return;
  }

  DTRACE_HEADER( g_trace_ctx, "# VTMBMS Block Statistics\n");
  // sequence info
  DTRACE_HEADER( g_trace_ctx, "# Sequence size: [%dx %d]\n", sps->getPicWidthInLumaSamples(), sps->getPicHeightInLumaSamples());
  // list statistics
  for( auto i = static_cast<int>(BlockStatistic::PredMode); i < static_cast<int>(BlockStatistic::NumBlockStatistics); i++)
  {
    BlockStatistic statistic = BlockStatistic(i);
    std::string statitic_name = GetBlockStatisticName(statistic);
    std::string statitic_type = GetBlockStatisticTypeString(statistic);
    std::string statitic_type_specific_info = GetBlockStatisticTypeSpecificInfo(statistic);
    DTRACE_HEADER( g_trace_ctx, "# Block Statistic Type: %s; %s; %s\n", statitic_name.c_str(), statitic_type.c_str(), statitic_type_specific_info.c_str());
  }

  has_header_been_written = true;
}

// TODO: for which statistics do we distinguish Y/Cb/Cr?
void getAndStoreBlockStatistics(const CodingStructure& cs, const UnitArea& ctuArea)
{
  const int maxNumChannelType = cs.pcv->chrFormat != CHROMA_400 && CS::isDualITree( cs ) ? 2 : 1;

  for( int ch = 0; ch < maxNumChannelType; ch++ )
  {
    const ChannelType chType = ChannelType( ch );
    const SPS& sps = *cs.sps;

    for( const auto &cu : cs.traverseCUs( CS::getArea( cs, ctuArea, chType ), chType ) )
    {
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::PredMode), cu.predMode);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::PartSize), cu.partSize);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::Depth), cu.depth);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::QT_Depth), cu.qtDepth);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::BT_Depth), cu.btDepth);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::MT_Depth), cu.mtDepth);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::ChromaQPAdj), cu.chromaQpAdj);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::QP), cu.qp);
      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::SplitSeries), (Int)cu.splitSeries);

      if(cu.transQuantBypass)
      {
        DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::TransQuantBypassFlag), cu.transQuantBypass);
      }
#if JEM_TOOLS
      if(cu.emtFlag)
      {
        DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::EMTFlag), cu.emtFlag);
      }
#endif

      switch( cu.predMode )
      {
      case MODE_INTER:
        {

          if( cu.skip)
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::SkipFlag), cu.skip);
          }
#if JEM_TOOLS
          if( cu.obmcFlag)
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::OBMCFlag), cu.obmcFlag);
          }
          if( cu.LICFlag)
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::LICFlag), cu.LICFlag);
          }
          if( cu.imv )
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::IMVFlag), cu.imv);
          }
#endif
          if( cu.rootCbf)
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::RootCBF), cu.rootCbf);
          }


          for( const auto &pu : CU::traversePUs( cu ) )
          {

            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::InterDir), pu.interDir);


            if( pu.mergeFlag )
            {
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MergeFlag), pu.mergeFlag);
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MergeIdx), pu.mergeIdx);
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MergeType), pu.mergeType);
            }
#if JEM_TOOLS
            if( pu.frucMrgMode )
            {
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::FrucMrgMode), pu.frucMrgMode);
            }
            if( pu.cu->affine )
            {
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::AffineFlag), pu.cu->affine);
            }
            if( pu.mvRefine )
            {
              DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVRefineFlag), pu.mvRefine);
            }
#endif
            // for different refpiclist get refidx and mv
            // i think if refidx is not used it is set to -1
            // only check mv for refidx != -1
            for (UInt i = 0; i < NUM_REF_PIC_LIST_01; i++)
            {                            
              if (i == 0 && (pu.refIdx[i] != -1)) // L0
              {
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVPIdxL0), pu.mvpIdx[i]);
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVPNumL0), pu.mvpNum[i]);
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::RefIdxL0), pu.refIdx[i]);
                DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVDL0), pu.mvd[i].hor, pu.mvd[i].ver);
              }
              else if (i == 1 && (pu.refIdx[i] != -1)) // L1
              {
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVPIdxL1), pu.mvpIdx[i]);
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVPNumL1), pu.mvpNum[i]);
                DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::RefIdxL1), pu.refIdx[i]);
                DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVDL1), pu.mvd[i].hor, pu.mvd[i].ver);
              }

              Int iRefIdx = pu.refIdx[i];
              Mv mv[3];

              if (iRefIdx != -1)
              {
  #if JEM_TOOLS
                if( pu.cu->affine )
                {
                  const CMotionBuf &mb = pu.getMotionBuf();
                  mv[0] = mb.at( 0,            0             ).mv[i];
                  mv[1] = mb.at( mb.width - 1, 0             ).mv[i];
                  mv[2] = mb.at( 0,            mb.height - 1 ).mv[i];
                  clipMv(mv[0], pu.cu->lumaPos(), sps);
                  clipMv(mv[1], pu.cu->lumaPos(), sps);
                  clipMv(mv[2], pu.cu->lumaPos(), sps);
                  // motion vectors should use low precision or they will appear to large
                  mv[0].setLowPrec();
                  mv[1].setLowPrec();
                  mv[2].setLowPrec();
                  if (i == 0)
                  {
                    DTRACE_BLOCK_AFFINETF(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::AffineMVL0), mv[0].hor, mv[0].ver, mv[1].hor, mv[1].ver, mv[2].hor, mv[2].ver);
                  }
                  else if (i == 1)
                  {
                    DTRACE_BLOCK_AFFINETF(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::AffineMVL1), mv[0].hor, mv[0].ver, mv[1].hor, mv[1].ver, mv[2].hor, mv[2].ver);
                  }

                }
                else
                {
                  mv[0] = pu.mv[i];
                  // motion vectors should use low precision or they will appear to large
                  mv[0].setLowPrec();
                  clipMv(mv[0], pu.cu->lumaPos(), sps);

                  if (i == 0)
                  {
                    DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVL0), mv[0].hor, mv[0].ver);
                  }
                  else if (i == 1)
                  {
                    DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVL1), mv[0].hor, mv[0].ver);
                  }
                }
  #else
                mv[0] = pu.mv[i];
                clipMv(mv[0], pu.cu->lumaPos(), sps);

                if (i == 0)
                {
                  DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVL0), mv[0].hor, mv[0].ver);
                }
                else if (i == 1)
                {
                  DTRACE_BLOCK_VECTOR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::MVL1), mv[0].hor, mv[0].ver);
                }
  #endif
              }
            }
          }


        }
        break;
      case MODE_INTRA:
        {
          if( cu.ipcm )
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::IPCM), cu.ipcm);
          }

#if JEM_TOOLS
          if( cu.pdpc )
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::PDPCFlag), cu.pdpc);
          }

          if( cu.nsstIdx )
          {
            DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, cu, GetBlockStatisticName(BlockStatistic::NSSTIdx), cu.nsstIdx);
          }
#endif
          const UInt numChType = ::getNumberValidChannels( cu.chromaFormat );

          for( UInt chType = CHANNEL_TYPE_LUMA; chType < numChType; chType++ )
          {
            if( cu.blocks[chType].valid() )
            {
              for( auto &currTU : CU::traverseTUs( cu ) )
              {
                if( isLuma( ChannelType( chType ) ) )
                {
                  const CompArea &area      = currTU.blocks[COMPONENT_Y];
                  const PredictionUnit &pu  = *currTU.cs->getPU( area.pos(), ChannelType( chType ) );
                  const UInt uiChFinalMode  = PU::getFinalIntraMode( pu, ChannelType( chType ) );
                  DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::Y_IntraMode), uiChFinalMode);
                }
                else
                {
                  const UInt numValidComp = getNumberValidComponents( cu.chromaFormat );

                  for( UInt compID = COMPONENT_Cb; compID < numValidComp; compID++ )
                  {
                    const CompArea &area      = currTU.blocks[ChannelType(compID)];
                    const PredictionUnit &pu  = *currTU.cs->getPU( area.pos(), ChannelType( chType ) );
                    const UInt uiChFinalMode  = PU::getFinalIntraMode( pu, ChannelType( chType ) );
                    if (compID == COMPONENT_Cb)
                    {
                      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::Cb_IntraMode), uiChFinalMode);
                    }
                    else
                    {
                      DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS, pu, GetBlockStatisticName(BlockStatistic::Cr_IntraMode), uiChFinalMode);
                    }
            #if ENABLE_CHROMA_422
                    assert(0);
            #endif
                  }
                }


              }
            }
          }
        }
        break;
      default:
        THROW( "Invalid prediction mode" );
        break;
      }
    }
  }
}
#endif
