/* 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 "OmxPromiseLayer.h"

#include "ImageContainer.h"
#include "OmxDataDecoder.h"
#include "OmxPlatformLayer.h"

#ifdef LOG
#  undef LOG
#endif

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

namespace mozilla {

OmxPromiseLayer::OmxPromiseLayer(TaskQueue* aTaskQueue,
                                 OmxDataDecoder* aDataDecoder,
                                 layers::ImageContainer* aImageContainer)
    : mTaskQueue(aTaskQueue) {
  mPlatformLayer.reset(OmxPlatformLayer::Create(aDataDecoder, this, aTaskQueue,
                                                aImageContainer));
  MOZ_ASSERT(!!mPlatformLayer);
}

RefPtr<OmxPromiseLayer::OmxCommandPromise> OmxPromiseLayer::Init(
    const TrackInfo* aInfo) {
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());

  OMX_ERRORTYPE err = mPlatformLayer->InitOmxToStateLoaded(aInfo);
  if (err != OMX_ErrorNone) {
    OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet);
    return OmxCommandPromise::CreateAndReject(failure, __func__);
  }

  OMX_STATETYPE state = GetState();
  if (state == OMX_StateLoaded) {
    return OmxCommandPromise::CreateAndResolve(OMX_CommandStateSet, __func__);
  }
  if (state == OMX_StateIdle) {
    return SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr);
  }

  OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet);
  return OmxCommandPromise::CreateAndReject(failure, __func__);
}

OMX_ERRORTYPE
OmxPromiseLayer::Config() {
  MOZ_ASSERT(GetState() == OMX_StateLoaded);

  return mPlatformLayer->Config();
}

RefPtr<OmxPromiseLayer::OmxBufferPromise> OmxPromiseLayer::FillBuffer(
    BufferData* aData) {
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
  LOG("buffer {}", fmt::ptr(aData->mBuffer));

  RefPtr<OmxBufferPromise> p = aData->mPromise.Ensure(__func__);

  OMX_ERRORTYPE err = mPlatformLayer->FillThisBuffer(aData);

  if (err != OMX_ErrorNone) {
    OmxBufferFailureHolder failure(err, aData);
    aData->mPromise.Reject(std::move(failure), __func__);
  } else {
    aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT;
    GetBufferHolders(OMX_DirOutput)->AppendElement(aData);
  }

  return p;
}

RefPtr<OmxPromiseLayer::OmxBufferPromise> OmxPromiseLayer::EmptyBuffer(
    BufferData* aData) {
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
  LOG("buffer {}, size {}", fmt::ptr(aData->mBuffer),
      aData->mBuffer->nFilledLen);

  RefPtr<OmxBufferPromise> p = aData->mPromise.Ensure(__func__);

  OMX_ERRORTYPE err = mPlatformLayer->EmptyThisBuffer(aData);

  if (err != OMX_ErrorNone) {
    OmxBufferFailureHolder failure(err, aData);
    aData->mPromise.Reject(std::move(failure), __func__);
  } else {
    if (aData->mRawData) {
      mRawDatas.AppendElement(std::move(aData->mRawData));
    }
    aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT;
    GetBufferHolders(OMX_DirInput)->AppendElement(aData);
  }

  return p;
}

OmxPromiseLayer::BUFFERLIST* OmxPromiseLayer::GetBufferHolders(
    OMX_DIRTYPE aType) {
  MOZ_ASSERT(aType == OMX_DirInput || aType == OMX_DirOutput);

  if (aType == OMX_DirInput) {
    return &mInbufferHolders;
  }

  return &mOutbufferHolders;
}

already_AddRefed<MediaRawData> OmxPromiseLayer::FindAndRemoveRawData(
    OMX_TICKS aTimecode) {
  for (auto raw : mRawDatas) {
    if (raw->mTime.ToMicroseconds() == aTimecode) {
      mRawDatas.RemoveElement(raw);
      return raw.forget();
    }
  }
  return nullptr;
}

already_AddRefed<BufferData> OmxPromiseLayer::FindAndRemoveBufferHolder(
    OMX_DIRTYPE aType, BufferData::BufferID aId) {
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());

  RefPtr<BufferData> holder;
  BUFFERLIST* holders = GetBufferHolders(aType);

  for (uint32_t i = 0; i < holders->Length(); i++) {
    if (holders->ElementAt(i)->ID() == aId) {
      holder = holders->ElementAt(i);
      holders->RemoveElementAt(i);
      return holder.forget();
    }
  }

  return nullptr;
}

already_AddRefed<BufferData> OmxPromiseLayer::FindBufferById(
    OMX_DIRTYPE aType, BufferData::BufferID aId) {
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());

  RefPtr<BufferData> holder;
  BUFFERLIST* holders = GetBufferHolders(aType);

  for (uint32_t i = 0; i < holders->Length(); i++) {
    if (holders->ElementAt(i)->ID() == aId) {
      holder = holders->ElementAt(i);
      return holder.forget();
    }
  }

  return nullptr;
}

void OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType,
                                          BufferData* aData) {
  if (aData) {
    LOG("type {}, buffer {}", static_cast<int>(aType),
        fmt::ptr(aData->mBuffer));
    if (aType == OMX_DirOutput) {
      aData->mRawData = nullptr;
      aData->mRawData = FindAndRemoveRawData(aData->mBuffer->nTimeStamp);
    }
    aData->mStatus = BufferData::BufferStatus::OMX_CLIENT;
    aData->mPromise.Resolve(aData, __func__);
  } else {
    LOG("type {}, no buffer", static_cast<int>(aType));
  }
}

void OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType,
                                          BufferData::BufferID aID) {
  RefPtr<BufferData> holder = FindAndRemoveBufferHolder(aType, aID);
  EmptyFillBufferDone(aType, holder);
}

RefPtr<OmxPromiseLayer::OmxCommandPromise> OmxPromiseLayer::SendCommand(
    OMX_COMMANDTYPE aCmd, OMX_U32 aParam1, OMX_PTR aCmdData) {
  if (aCmd == OMX_CommandFlush) {
    // It doesn't support another flush commands before previous one is
    // completed.
    MOZ_RELEASE_ASSERT(!mFlushCommands.Length());

    // Some coomponents don't send event with OMX_ALL, they send flush complete
    // event with input port and another event for output port.
    // In prupose of better compatibility, we interpret the OMX_ALL to
    // OMX_DirInput and OMX_DirOutput flush separately.
    OMX_DIRTYPE types[] = {OMX_DIRTYPE::OMX_DirInput,
                           OMX_DIRTYPE::OMX_DirOutput};
    for (const auto type : types) {
      if ((aParam1 == type) || (aParam1 == OMX_ALL)) {
        mFlushCommands.AppendElement(FlushCommand({type, aCmdData}));
      }

      if (type == OMX_DirInput) {
        // Clear all buffered raw data.
        mRawDatas.Clear();
      }
    }

    // Don't overlay more than one flush command, some components can't overlay
    // flush commands. So here we send another flush after receiving the
    // previous flush completed event.
    if (mFlushCommands.Length()) {
      OMX_ERRORTYPE err = mPlatformLayer->SendCommand(
          OMX_CommandFlush, mFlushCommands.ElementAt(0).type,
          mFlushCommands.ElementAt(0).cmd);
      if (err != OMX_ErrorNone) {
        OmxCommandFailureHolder failure(OMX_ErrorNotReady, OMX_CommandFlush);
        return OmxCommandPromise::CreateAndReject(failure, __func__);
      }
    } else {
      LOG("OMX_CommandFlush parameter error");
      OmxCommandFailureHolder failure(OMX_ErrorNotReady, OMX_CommandFlush);
      return OmxCommandPromise::CreateAndReject(failure, __func__);
    }
  } else {
    OMX_ERRORTYPE err = mPlatformLayer->SendCommand(aCmd, aParam1, aCmdData);
    if (err != OMX_ErrorNone) {
      OmxCommandFailureHolder failure(OMX_ErrorNotReady, aCmd);
      return OmxCommandPromise::CreateAndReject(failure, __func__);
    }
  }

  RefPtr<OmxCommandPromise> p;
  if (aCmd == OMX_CommandStateSet) {
    p = mCommandStatePromise.Ensure(__func__);
  } else if (aCmd == OMX_CommandFlush) {
    p = mFlushPromise.Ensure(__func__);
  } else if (aCmd == OMX_CommandPortEnable) {
    p = mPortEnablePromise.Ensure(__func__);
  } else if (aCmd == OMX_CommandPortDisable) {
    p = mPortDisablePromise.Ensure(__func__);
  } else {
    LOG("error unsupport command");
    MOZ_ASSERT(0);
  }

  return p;
}

bool OmxPromiseLayer::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1,
                            OMX_U32 aData2) {
  OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE)aData1;
  switch (aEvent) {
    case OMX_EventCmdComplete: {
      if (cmd == OMX_CommandStateSet) {
        mCommandStatePromise.Resolve(OMX_CommandStateSet, __func__);
      } else if (cmd == OMX_CommandFlush) {
        MOZ_RELEASE_ASSERT(mFlushCommands.ElementAt(0).type == aData2);
        LOG("OMX_CommandFlush completed port type {}", aData2);
        mFlushCommands.RemoveElementAt(0);

        // Sending next flush command.
        if (mFlushCommands.Length()) {
          OMX_ERRORTYPE err = mPlatformLayer->SendCommand(
              OMX_CommandFlush, mFlushCommands.ElementAt(0).type,
              mFlushCommands.ElementAt(0).cmd);
          if (err != OMX_ErrorNone) {
            OmxCommandFailureHolder failure(OMX_ErrorNotReady,
                                            OMX_CommandFlush);
            mFlushPromise.Reject(failure, __func__);
          }
        } else {
          mFlushPromise.Resolve(OMX_CommandFlush, __func__);
        }
      } else if (cmd == OMX_CommandPortDisable) {
        mPortDisablePromise.Resolve(OMX_CommandPortDisable, __func__);
      } else if (cmd == OMX_CommandPortEnable) {
        mPortEnablePromise.Resolve(OMX_CommandPortEnable, __func__);
      }
      break;
    }
    case OMX_EventError: {
      if (cmd == OMX_CommandStateSet) {
        OmxCommandFailureHolder failure(OMX_ErrorUndefined,
                                        OMX_CommandStateSet);
        mCommandStatePromise.Reject(failure, __func__);
      } else if (cmd == OMX_CommandFlush) {
        OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandFlush);
        mFlushPromise.Reject(failure, __func__);
      } else if (cmd == OMX_CommandPortDisable) {
        OmxCommandFailureHolder failure(OMX_ErrorUndefined,
                                        OMX_CommandPortDisable);
        mPortDisablePromise.Reject(failure, __func__);
      } else if (cmd == OMX_CommandPortEnable) {
        OmxCommandFailureHolder failure(OMX_ErrorUndefined,
                                        OMX_CommandPortEnable);
        mPortEnablePromise.Reject(failure, __func__);
      } else {
        return false;
      }
      break;
    }
    default: {
      return false;
    }
  }
  return true;
}

nsresult OmxPromiseLayer::AllocateOmxBuffer(OMX_DIRTYPE aType,
                                            BUFFERLIST* aBuffers) {
  return mPlatformLayer->AllocateOmxBuffer(aType, aBuffers);
}

nsresult OmxPromiseLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType,
                                           BUFFERLIST* aBuffers) {
  return mPlatformLayer->ReleaseOmxBuffer(aType, aBuffers);
}

OMX_STATETYPE
OmxPromiseLayer::GetState() {
  OMX_STATETYPE state;
  OMX_ERRORTYPE err = mPlatformLayer->GetState(&state);
  return err == OMX_ErrorNone ? state : OMX_StateInvalid;
}

OMX_ERRORTYPE
OmxPromiseLayer::GetParameter(OMX_INDEXTYPE aParamIndex,
                              OMX_PTR aComponentParameterStructure,
                              OMX_U32 aComponentParameterSize) {
  return mPlatformLayer->GetParameter(aParamIndex, aComponentParameterStructure,
                                      aComponentParameterSize);
}

OMX_ERRORTYPE
OmxPromiseLayer::SetParameter(OMX_INDEXTYPE aParamIndex,
                              OMX_PTR aComponentParameterStructure,
                              OMX_U32 aComponentParameterSize) {
  return mPlatformLayer->SetParameter(aParamIndex, aComponentParameterStructure,
                                      aComponentParameterSize);
}

OMX_U32
OmxPromiseLayer::InputPortIndex() { return mPlatformLayer->InputPortIndex(); }

OMX_U32
OmxPromiseLayer::OutputPortIndex() { return mPlatformLayer->OutputPortIndex(); }

nsresult OmxPromiseLayer::Shutdown() {
  LOG("");
  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
  MOZ_ASSERT(!GetBufferHolders(OMX_DirInput)->Length());
  MOZ_ASSERT(!GetBufferHolders(OMX_DirOutput)->Length());
  return mPlatformLayer->Shutdown();
}

}  // namespace mozilla
