// SPDX-FileCopyrightText: © 2024 Allegro DVT <github-ip@allegrodvt.com>
// SPDX-License-Identifier: MIT

#include "omx_expertise_avc.h"
#include "omx_convert_omx_media.h"

using namespace std;

ExpertiseAVC::~ExpertiseAVC() = default;

static OMX_ERRORTYPE SetMediaProfileLevel(OMX_VIDEO_AVCPROFILETYPE const& profile, OMX_VIDEO_AVCLEVELTYPE const& level, std::shared_ptr<SettingsInterface> media)
{
  ProfileLevel p;
  p.profile.avc = ConvertOMXToMediaAVCProfileLevel(profile, level).profile.avc;
  p.level = ConvertOMXToMediaAVCProfileLevel(profile, level).level;

  auto ret = media->Set(SETTINGS_INDEX_PROFILE_LEVEL, &p);
  OMX_CHECK_MEDIA_SET(ret);
  return OMX_ErrorNone;
}

static OMX_ERRORTYPE SetMediaGop(OMX_U32 bFrames, OMX_U32 pFrames, std::shared_ptr<SettingsInterface> media)
{
  Gop gop;
  auto ret = media->Get(SETTINGS_INDEX_GROUP_OF_PICTURES, &gop);

  if(ret == SettingsInterface::BAD_INDEX)
    return OMX_ErrorUnsupportedIndex;

  gop.b = ConvertOMXToMediaBFrames(bFrames, pFrames);
  gop.length = ConvertOMXToMediaGopLength(bFrames, pFrames);

  ret = media->Set(SETTINGS_INDEX_GROUP_OF_PICTURES, &gop);
  OMX_CHECK_MEDIA_SET(ret);
  return OMX_ErrorNone;
}

static OMX_ERRORTYPE SetMediaEntropyCoding(OMX_BOOL entropyCoding, std::shared_ptr<SettingsInterface> media)
{
  EntropyCodingType e = ConvertOMXToMediaEntropyCoding(entropyCoding);

  auto ret = media->Set(SETTINGS_INDEX_ENTROPY_CODING, &e);
  OMX_CHECK_MEDIA_SET(ret);
  return OMX_ErrorNone;
}

static OMX_ERRORTYPE SetMediaConstrainedIntraPrediction(OMX_BOOL constrainedIntraPrediction, std::shared_ptr<SettingsInterface> media)
{
  bool b = ConvertOMXToMediaBool(constrainedIntraPrediction);

  auto ret = media->Set(SETTINGS_INDEX_CONSTRAINED_INTRA_PREDICTION, &b);
  OMX_CHECK_MEDIA_SET(ret);
  return OMX_ErrorNone;
}

static OMX_ERRORTYPE SetMediaLoopFilter(OMX_VIDEO_AVCLOOPFILTERTYPE const& loopFilter, std::shared_ptr<SettingsInterface> media)
{
  LoopFilterType l = ConvertOMXToMediaAVCLoopFilter(loopFilter);

  auto ret = media->Set(SETTINGS_INDEX_LOOP_FILTER, &l);
  OMX_CHECK_MEDIA_SET(ret);
  return OMX_ErrorNone;
}

OMX_ERRORTYPE ExpertiseAVC::GetProfileLevelSupported(OMX_PTR param, std::shared_ptr<SettingsInterface> media)
{
  vector<ProfileLevel> supported;
  auto ret = media->Get(SETTINGS_INDEX_PROFILES_LEVELS_SUPPORTED, &supported);
  OMX_CHECK_MEDIA_GET(ret);
  auto& pl = *(OMX_VIDEO_PARAM_PROFILELEVELTYPE*)param;

  if(pl.nProfileIndex >= supported.size())
    return OMX_ErrorNoMore;

  pl.eProfile = ConvertMediaToOMXAVCProfile(supported[pl.nProfileIndex]);
  pl.eLevel = ConvertMediaToOMXAVCLevel(supported[pl.nProfileIndex]);

  return OMX_ErrorNone;
}

OMX_ERRORTYPE ExpertiseAVC::GetProfileLevel(OMX_PTR param, Port const& port, std::shared_ptr<SettingsInterface> media)
{
  ProfileLevel profileLevel;
  auto ret = media->Get(SETTINGS_INDEX_PROFILE_LEVEL, &profileLevel);
  OMX_CHECK_MEDIA_GET(ret);
  auto& pl = *(OMX_VIDEO_PARAM_PROFILELEVELTYPE*)param;
  pl.nPortIndex = port.index;
  pl.eProfile = ConvertMediaToOMXAVCProfile(profileLevel);
  pl.eLevel = ConvertMediaToOMXAVCLevel(profileLevel);
  return OMX_ErrorNone;
}

OMX_ERRORTYPE ExpertiseAVC::SetProfileLevel(OMX_PTR param, Port const& port, std::shared_ptr<SettingsInterface> media)
{
  OMX_VIDEO_PARAM_PROFILELEVELTYPE rollback;
  GetProfileLevel(&rollback, port, media);
  auto pl = *(OMX_VIDEO_PARAM_PROFILELEVELTYPE*)param;
  auto profile = static_cast<OMX_VIDEO_AVCPROFILETYPE>(pl.eProfile);
  auto level = static_cast<OMX_VIDEO_AVCLEVELTYPE>(pl.eLevel);

  auto ret = SetMediaProfileLevel(profile, level, media);

  if(ret != OMX_ErrorNone)
  {
    SetProfileLevel(&rollback, port, media);
    throw ret;
  }

  return OMX_ErrorNone;
}

OMX_ERRORTYPE ExpertiseAVC::GetExpertise(OMX_PTR param, Port const& port, std::shared_ptr<SettingsInterface> media)
{
  ProfileLevel profileLevel;
  Gop gop;
  EntropyCodingType entropyCoding;
  bool isConstrainedIntraPrediction;
  LoopFilterType loopFilter;
  auto ret = media->Get(SETTINGS_INDEX_PROFILE_LEVEL, &profileLevel);
  OMX_CHECK_MEDIA_GET(ret);
  bool bGop = (media->Get(SETTINGS_INDEX_GROUP_OF_PICTURES, &gop) == SettingsInterface::SUCCESS);
  bool bEntropyCoding = (media->Get(SETTINGS_INDEX_ENTROPY_CODING, &entropyCoding) == SettingsInterface::SUCCESS);
  bool bIntraPred = (media->Get(SETTINGS_INDEX_CONSTRAINED_INTRA_PREDICTION, &isConstrainedIntraPrediction) == SettingsInterface::SUCCESS);
  bool bLoopFilter = (media->Get(SETTINGS_INDEX_LOOP_FILTER, &loopFilter) == SettingsInterface::SUCCESS);
  auto& avc = *(OMX_VIDEO_PARAM_AVCTYPE*)param;
  avc.nPortIndex = port.index;
  avc.nBFrames = bGop ? ConvertMediaToOMXBFrames(gop) : 0;
  avc.nPFrames = bGop ? ConvertMediaToOMXPFrames(gop) : 0;
  avc.bUseHadamard = OMX_TRUE; // XXX
  avc.nRefFrames = 1; // XXX
  avc.nRefIdx10ActiveMinus1 = 0; // XXX
  avc.nRefIdx11ActiveMinus1 = 0; // XXX
  avc.bEnableUEP = OMX_FALSE; // XXX
  avc.bEnableFMO = OMX_FALSE; // XXX
  avc.bEnableASO = OMX_FALSE; // XXX
  avc.bEnableRS = OMX_FALSE; // XXX
  avc.eProfile = ConvertMediaToOMXAVCProfile(profileLevel);
  avc.eLevel = ConvertMediaToOMXAVCLevel(profileLevel);
  avc.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP | OMX_VIDEO_PictureTypeB; // XXX
  avc.bFrameMBsOnly = OMX_TRUE; // XXX
  avc.bMBAFF = OMX_FALSE; // XXX
  avc.bEntropyCodingCABAC = bEntropyCoding ? ConvertMediaToOMXEntropyCoding(entropyCoding) : OMX_FALSE;
  avc.bWeightedPPrediction = OMX_FALSE; // XXX
  avc.nWeightedBipredicitonMode = OMX_FALSE; // XXX
  avc.bconstIpred = bIntraPred ? ConvertMediaToOMXBool(isConstrainedIntraPrediction) : OMX_FALSE;
  avc.bDirect8x8Inference = OMX_TRUE; // XXX
  avc.bDirectSpatialTemporal = OMX_TRUE; // XXX
  avc.nCabacInitIdc = 0; // XXX
  avc.eLoopFilterMode = bLoopFilter ? ConvertMediaToOMXAVCLoopFilter(loopFilter) : OMX_VIDEO_AVCLoopFilterMax;
  return OMX_ErrorNone;
}

OMX_ERRORTYPE ExpertiseAVC::SetExpertise(OMX_PTR param, Port const& port, std::shared_ptr<SettingsInterface> media)
{
  OMX_VIDEO_PARAM_AVCTYPE rollback;
  GetExpertise(&rollback, port, media);
  auto avc = *(OMX_VIDEO_PARAM_AVCTYPE*)param;

  auto ret = SetMediaProfileLevel(avc.eProfile, avc.eLevel, media);

  if(ret != OMX_ErrorNone)
  {
    SetExpertise(&rollback, port, media);
    throw ret;
  }

  // only encoder

  ret = SetMediaGop(avc.nBFrames, avc.nPFrames, media);

  if(ret == OMX_ErrorBadParameter)
  {
    SetExpertise(&rollback, port, media);
    throw ret;
  }

  ret = SetMediaEntropyCoding(avc.bEntropyCodingCABAC, media);

  if(ret == OMX_ErrorBadParameter)
  {
    SetExpertise(&rollback, port, media);
    throw ret;
  }

  ret = SetMediaConstrainedIntraPrediction(avc.bconstIpred, media);

  if(ret == OMX_ErrorBadParameter)
  {
    SetExpertise(&rollback, port, media);
    throw ret;
  }

  ret = SetMediaLoopFilter(avc.eLoopFilterMode, media);

  if(ret == OMX_ErrorBadParameter)
  {
    SetExpertise(&rollback, port, media);
    throw ret;
  }

  return OMX_ErrorNone;
}
