/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "PureOmxPlatformLayer.h"

#include "OmxCoreLibLinker.h"
#include "OmxDataDecoder.h"
#include "OmxPromiseLayer.h"

#ifdef LOG
#  undef LOG
#endif

#define LOG(arg, ...)                                                         \
  MOZ_LOG_FMT(sPDMLog, mozilla::LogLevel::Debug,                              \
              "PureOmxPlatformLayer({})::{}: " arg, fmt::ptr(this), __func__, \
              ##__VA_ARGS__)
#define LOG_BUF(arg, ...)                                                  \
  MOZ_LOG_FMT(sPDMLog, mozilla::LogLevel::Debug,                           \
              "PureOmxBufferData({})::{}: " arg, fmt::ptr(this), __func__, \
              ##__VA_ARGS__)

namespace mozilla {

#define OMX_FUNC(func) extern typeof(func)* func;
#include "OmxFunctionList.inc"
#undef OMX_FUNC

PureOmxBufferData::PureOmxBufferData(
    const PureOmxPlatformLayer& aPlatformLayer,
    const OMX_PARAM_PORTDEFINITIONTYPE& aPortDef)
    : BufferData(nullptr), mPlatformLayer(aPlatformLayer), mPortDef(aPortDef) {
  LOG_BUF("");

  if (ShouldUseEGLImage()) {
    // TODO
    LOG_BUF(
        "OMX_UseEGLImage() seems available but using it isn't implemented "
        "yet.");
  }

  OMX_ERRORTYPE err;
  err = OMX_AllocateBuffer(mPlatformLayer.GetComponent(), &mBuffer,
                           mPortDef.nPortIndex, this, mPortDef.nBufferSize);
  if (err != OMX_ErrorNone) {
    LOG_BUF("Failed to allocate the buffer!: 0x{:08x}", err);
  }
}

PureOmxBufferData::~PureOmxBufferData() {
  LOG_BUF("");
  ReleaseBuffer();
}

void PureOmxBufferData::ReleaseBuffer() {
  LOG_BUF("");

  if (mBuffer) {
    OMX_ERRORTYPE err;
    err = OMX_FreeBuffer(mPlatformLayer.GetComponent(), mPortDef.nPortIndex,
                         mBuffer);
    if (err != OMX_ErrorNone) {
      LOG_BUF("Failed to free the buffer!: 0x{:08x}", err);
    }
    mBuffer = nullptr;
  }
}

bool PureOmxBufferData::ShouldUseEGLImage() {
  OMX_ERRORTYPE err;
  err = OMX_UseEGLImage(mPlatformLayer.GetComponent(), nullptr,
                        mPortDef.nPortIndex, nullptr, nullptr);
  return (err != OMX_ErrorNotImplemented);
}

/* static */
bool PureOmxPlatformLayer::Init(void) {
  if (!OmxCoreLibLinker::Link()) {
    return false;
  }

  OMX_ERRORTYPE err = OMX_Init();
  if (err != OMX_ErrorNone) {
    MOZ_LOG_FMT(
        sPDMLog, mozilla::LogLevel::Debug,
        "PureOmxPlatformLayer::{}: Failed to initialize OMXCore: 0x{:08x}",
        __func__, err);
    return false;
  }

  return true;
}

/* static */
OMX_CALLBACKTYPE PureOmxPlatformLayer::sCallbacks = {
    EventHandler, EmptyBufferDone, FillBufferDone};

PureOmxPlatformLayer::PureOmxPlatformLayer(
    OmxDataDecoder* aDataDecoder, OmxPromiseLayer* aPromiseLayer,
    TaskQueue* aTaskQueue, layers::ImageContainer* aImageContainer)
    : mComponent(nullptr),
      mDataDecoder(aDataDecoder),
      mPromiseLayer(aPromiseLayer),
      mTaskQueue(aTaskQueue),
      mImageContainer(aImageContainer) {
  LOG("");
}

PureOmxPlatformLayer::~PureOmxPlatformLayer() { LOG(""); }

OMX_ERRORTYPE
PureOmxPlatformLayer::InitOmxToStateLoaded(const TrackInfo* aInfo) {
  LOG("");

  if (!aInfo) {
    return OMX_ErrorUndefined;
  }
  mInfo = aInfo;

  return CreateComponent();
}

OMX_ERRORTYPE
PureOmxPlatformLayer::EmptyThisBuffer(BufferData* aData) {
  LOG("");
  return OMX_EmptyThisBuffer(mComponent, aData->mBuffer);
}

OMX_ERRORTYPE
PureOmxPlatformLayer::FillThisBuffer(BufferData* aData) {
  LOG("");
  return OMX_FillThisBuffer(mComponent, aData->mBuffer);
}

OMX_ERRORTYPE
PureOmxPlatformLayer::SendCommand(OMX_COMMANDTYPE aCmd, OMX_U32 aParam1,
                                  OMX_PTR aCmdData) {
  LOG("aCmd: 0x{:08x}", aCmd);
  if (!mComponent) {
    return OMX_ErrorUndefined;
  }
  return OMX_SendCommand(mComponent, aCmd, aParam1, aCmdData);
}

nsresult PureOmxPlatformLayer::FindPortDefinition(
    OMX_DIRTYPE aType, OMX_PARAM_PORTDEFINITIONTYPE& portDef) {
  nsTArray<uint32_t> portIndex;
  GetPortIndices(portIndex);
  for (auto idx : portIndex) {
    InitOmxParameter(&portDef);
    portDef.nPortIndex = idx;

    OMX_ERRORTYPE err;
    err = GetParameter(OMX_IndexParamPortDefinition, &portDef,
                       sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
    if (err != OMX_ErrorNone) {
      return NS_ERROR_FAILURE;
    } else if (portDef.eDir == aType) {
      LOG("Found OMX_IndexParamPortDefinition: port: {}, type: {}",
          portDef.nPortIndex, portDef.eDir);
      return NS_OK;
    }
  }
  return NS_ERROR_FAILURE;
}

nsresult PureOmxPlatformLayer::AllocateOmxBuffer(OMX_DIRTYPE aType,
                                                 BUFFERLIST* aBufferList) {
  LOG("aType: {}", static_cast<int>(aType));

  OMX_PARAM_PORTDEFINITIONTYPE portDef;
  nsresult result = FindPortDefinition(aType, portDef);
  if (result != NS_OK) {
    return result;
  }

  LOG("nBufferCountActual: {}, nBufferSize: {}", portDef.nBufferCountActual,
      portDef.nBufferSize);

  for (OMX_U32 i = 0; i < portDef.nBufferCountActual; ++i) {
    RefPtr<PureOmxBufferData> buffer = new PureOmxBufferData(*this, portDef);
    aBufferList->AppendElement(buffer);
  }

  return NS_OK;
}

nsresult PureOmxPlatformLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType,
                                                BUFFERLIST* aBufferList) {
  LOG("aType: 0x{:08x}", static_cast<uint32_t>(aType));

  uint32_t len = aBufferList->Length();
  for (uint32_t i = 0; i < len; i++) {
    PureOmxBufferData* buffer =
        static_cast<PureOmxBufferData*>(aBufferList->ElementAt(i).get());

    // All raw OpenMAX buffers have to be released here to flush
    // OMX_CommandStateSet for switching the state to OMX_StateLoaded.
    // See OmxDataDecoder::DoAsyncShutdown() for more detail.
    buffer->ReleaseBuffer();
  }
  aBufferList->Clear();

  return NS_OK;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::GetState(OMX_STATETYPE* aType) {
  LOG("");

  if (mComponent) {
    return OMX_GetState(mComponent, aType);
  }

  return OMX_ErrorUndefined;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::GetParameter(OMX_INDEXTYPE aParamIndex,
                                   OMX_PTR aComponentParameterStructure,
                                   OMX_U32 aComponentParameterSize) {
  LOG("aParamIndex: 0x{:08x}", aParamIndex);

  if (!mComponent) {
    return OMX_ErrorUndefined;
  }

  return OMX_GetParameter(mComponent, aParamIndex,
                          aComponentParameterStructure);
}

OMX_ERRORTYPE
PureOmxPlatformLayer::SetParameter(OMX_INDEXTYPE aParamIndex,
                                   OMX_PTR aComponentParameterStructure,
                                   OMX_U32 aComponentParameterSize) {
  LOG("aParamIndex: 0x{:08x}", aParamIndex);

  if (!mComponent) {
    return OMX_ErrorUndefined;
  }

  return OMX_SetParameter(mComponent, aParamIndex,
                          aComponentParameterStructure);
}

nsresult PureOmxPlatformLayer::Shutdown() {
  LOG("");
  if (mComponent) {
    OMX_FreeHandle(mComponent);
    mComponent = nullptr;
  }
  mPromiseLayer = nullptr;
  mDataDecoder = nullptr;
  return NS_OK;
}

/* static */
OMX_ERRORTYPE PureOmxPlatformLayer::EventHandler(OMX_HANDLETYPE hComponent,
                                                 OMX_PTR pAppData,
                                                 OMX_EVENTTYPE eEventType,
                                                 OMX_U32 nData1, OMX_U32 nData2,
                                                 OMX_PTR pEventData) {
  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "mozilla::PureOmxPlatformLayer::EventHandler",
      [self, eEventType, nData1, nData2, pEventData]() {
        self->EventHandler(eEventType, nData1, nData2, pEventData);
      });
  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
}

/* static */
OMX_ERRORTYPE PureOmxPlatformLayer::EmptyBufferDone(
    OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData,
    OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "mozilla::PureOmxPlatformLayer::EmptyBufferDone",
      [self, pBuffer]() { self->EmptyBufferDone(pBuffer); });
  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
}

/* static */
OMX_ERRORTYPE PureOmxPlatformLayer::FillBufferDone(
    OMX_OUT OMX_HANDLETYPE hComponent, OMX_OUT OMX_PTR pAppData,
    OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer) {
  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "mozilla::PureOmxPlatformLayer::FillBufferDone",
      [self, pBuffer]() { self->FillBufferDone(pBuffer); });
  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::EventHandler(OMX_EVENTTYPE eEventType, OMX_U32 nData1,
                                   OMX_U32 nData2, OMX_PTR pEventData) {
  bool handled = mPromiseLayer->Event(eEventType, nData1, nData2);
  LOG("eEventType: 0x{:08x}, handled: {}", eEventType, handled);
  return OMX_ErrorNone;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::EmptyBufferDone(OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
  PureOmxBufferData* buffer =
      static_cast<PureOmxBufferData*>(pBuffer->pAppPrivate);
  OMX_DIRTYPE portDirection = buffer->GetPortDirection();
  LOG("PortDirection: {}", portDirection);
  mPromiseLayer->EmptyFillBufferDone(portDirection, buffer);
  return OMX_ErrorNone;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::FillBufferDone(OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer) {
  PureOmxBufferData* buffer =
      static_cast<PureOmxBufferData*>(pBuffer->pAppPrivate);
  OMX_DIRTYPE portDirection = buffer->GetPortDirection();
  LOG("PortDirection: {}", portDirection);
  mPromiseLayer->EmptyFillBufferDone(portDirection, buffer);
  return OMX_ErrorNone;
}

bool PureOmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType) {
  return FindStandardComponent(aMimeType, nullptr);
}

static bool GetStandardComponentRole(const nsACString& aMimeType,
                                     nsACString& aRole) {
  if (aMimeType.EqualsLiteral("video/avc") ||
      aMimeType.EqualsLiteral("video/mp4") ||
      aMimeType.EqualsLiteral("video/mp4v-es")) {
    aRole.Assign("video_decoder.avc");
    return true;
  } else if (aMimeType.EqualsLiteral("audio/mp4a-latm") ||
             aMimeType.EqualsLiteral("audio/mp4") ||
             aMimeType.EqualsLiteral("audio/aac")) {
    aRole.Assign("audio_decoder.aac");
    return true;
  }
  return false;
}

/* static */
bool PureOmxPlatformLayer::FindStandardComponent(const nsACString& aMimeType,
                                                 nsACString* aComponentName) {
  nsAutoCString role;
  if (!GetStandardComponentRole(aMimeType, role)) return false;

  OMX_U32 nComponents = 0;
  OMX_ERRORTYPE err;
  err = OMX_GetComponentsOfRole(const_cast<OMX_STRING>(role.Data()),
                                &nComponents, nullptr);
  if (err != OMX_ErrorNone || nComponents <= 0) {
    return false;
  }
  if (!aComponentName) {
    return true;
  }

  // TODO:
  // Only the first component will be used.
  // We should detect the most preferred component.
  OMX_U8* componentNames[1];
  UniquePtr<OMX_U8[]> componentName;
  componentName = MakeUniqueFallible<OMX_U8[]>(OMX_MAX_STRINGNAME_SIZE);
  componentNames[0] = componentName.get();
  nComponents = 1;
  err = OMX_GetComponentsOfRole(const_cast<OMX_STRING>(role.Data()),
                                &nComponents, componentNames);
  if (err == OMX_ErrorNone) {
    MOZ_LOG_FMT(
        sPDMLog, mozilla::LogLevel::Debug,
        "PureOmxPlatformLayer::{}: A component has been found for {}: {}",
        __func__, aMimeType.Data(), reinterpret_cast<char*>(componentNames[0]));
    aComponentName->Assign(reinterpret_cast<char*>(componentNames[0]));
  }

  return err == OMX_ErrorNone;
}

OMX_ERRORTYPE
PureOmxPlatformLayer::CreateComponent(const nsACString* aComponentName) {
  nsAutoCString componentName;
  if (aComponentName) {
    componentName = *aComponentName;
  } else if (!FindStandardComponent(mInfo->mMimeType, &componentName)) {
    return OMX_ErrorComponentNotFound;
  }

  OMX_ERRORTYPE err;
  err = OMX_GetHandle(&mComponent, const_cast<OMX_STRING>(componentName.Data()),
                      this, &sCallbacks);

  const char* mime = mInfo->mMimeType.Data();
  if (err == OMX_ErrorNone) {
    LOG("Succeeded to create the component for {}", mime);
  } else {
    LOG("Failed to create the component for {}: 0x{:08x}", mime,
        static_cast<uint32_t>(err));
  }

  return err;
}

}  // namespace mozilla
