/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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(sPDMLog, mozilla::LogLevel::Debug, ("OmxPromiseLayer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))

namespace mozilla {

OmxPromiseLayer::OmxPromiseLayer(TaskQueue* aTaskQueue,
                                 OmxDataDecoder* aDataDecoder,
                                 layers::ImageContainer* aImageContainer)
  : mTaskQueue(aTaskQueue)
{
  mPlatformLayer = 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 %p", 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(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 %p, size %d", 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(Move(failure), __func__);
  } else {
    if (aData->mRawData) {
      mRawDatas.AppendElement(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 == 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 %d, buffer %p", aType, 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 %d, no buffer", 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 %d", 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();
}

}
