/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "mozilla/TaskQueue.h"

#include "MediaSystemResourceManagerChild.h"
#include "MediaSystemResourceClient.h"

#include "mozilla/layers/ImageBridgeChild.h"

#include "MediaSystemResourceManager.h"

namespace mozilla {

using namespace mozilla::ipc;
using namespace mozilla::layers;

/* static */
StaticRefPtr<MediaSystemResourceManager> MediaSystemResourceManager::sSingleton;

/* static */
MediaSystemResourceManager* MediaSystemResourceManager::Get() {
  if (sSingleton) {
    return sSingleton;
  }
  MediaSystemResourceManager::Init();
  return sSingleton;
}

/* static */
void MediaSystemResourceManager::Shutdown() {
  MOZ_ASSERT(InImageBridgeChildThread());
  if (sSingleton) {
    sSingleton->CloseIPC();
    sSingleton = nullptr;
  }
}

/* static */
void MediaSystemResourceManager::Init() {
  RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
  if (!imageBridge) {
    NS_WARNING("ImageBridge does not exist");
    return;
  }

  if (InImageBridgeChildThread()) {
    if (!sSingleton) {
#ifdef DEBUG
      static int timesCreated = 0;
      timesCreated++;
      MOZ_ASSERT(timesCreated == 1);
#endif
      sSingleton = new MediaSystemResourceManager();
    }
    return;
  }

  ReentrantMonitor barrier MOZ_UNANNOTATED("MediaSystemResourceManager::Init");
  ReentrantMonitorAutoEnter mainThreadAutoMon(barrier);
  bool done = false;

  RefPtr<Runnable> runnable =
      NS_NewRunnableFunction("MediaSystemResourceManager::Init", [&]() {
        if (!sSingleton) {
          sSingleton = new MediaSystemResourceManager();
        }
        ReentrantMonitorAutoEnter childThreadAutoMon(barrier);
        done = true;
        barrier.NotifyAll();
      });

  imageBridge->GetThread()->Dispatch(runnable.forget());

  // should stop the thread until done.
  while (!done) {
    barrier.Wait();
  }
}

MediaSystemResourceManager::MediaSystemResourceManager()
    : mReentrantMonitor("MediaSystemResourceManager.mReentrantMonitor"),
      mShutDown(false),
      mChild(nullptr) {
  MOZ_ASSERT(InImageBridgeChildThread());
  OpenIPC();
}

MediaSystemResourceManager::~MediaSystemResourceManager() {
  MOZ_ASSERT(IsIpcClosed());
}

void MediaSystemResourceManager::OpenIPC() {
  MOZ_ASSERT(InImageBridgeChildThread());
  MOZ_ASSERT(!mChild);

  media::PMediaSystemResourceManagerChild* child =
      ImageBridgeChild::GetSingleton()
          ->SendPMediaSystemResourceManagerConstructor();
  mChild = static_cast<media::MediaSystemResourceManagerChild*>(child);
  mChild->SetManager(this);
}

void MediaSystemResourceManager::CloseIPC() {
  MOZ_ASSERT(InImageBridgeChildThread());

  if (!mChild) {
    return;
  }
  mChild->Destroy();
  mChild = nullptr;
  mShutDown = true;
}

void MediaSystemResourceManager::OnIpcClosed() { mChild = nullptr; }

bool MediaSystemResourceManager::IsIpcClosed() { return mChild ? true : false; }

void MediaSystemResourceManager::Register(MediaSystemResourceClient* aClient) {
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  MOZ_ASSERT(aClient);
  MOZ_ASSERT(!mResourceClients.Contains(aClient->mId));

  mResourceClients.InsertOrUpdate(aClient->mId, aClient);
}

void MediaSystemResourceManager::Unregister(
    MediaSystemResourceClient* aClient) {
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  MOZ_ASSERT(aClient);
  MOZ_ASSERT(mResourceClients.Contains(aClient->mId));
  MOZ_ASSERT(mResourceClients.Get(aClient->mId) == aClient);

  mResourceClients.Remove(aClient->mId);
}

bool MediaSystemResourceManager::SetListener(
    MediaSystemResourceClient* aClient,
    MediaSystemResourceReservationListener* aListener) {
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  MOZ_ASSERT(aClient);

  MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId);
  MOZ_ASSERT(client);

  if (!client) {
    return false;
  }
  // State Check
  if (aClient->mResourceState !=
      MediaSystemResourceClient::RESOURCE_STATE_START) {
    return false;
  }
  aClient->mListener = aListener;
  return true;
}

void MediaSystemResourceManager::Acquire(MediaSystemResourceClient* aClient) {
  MOZ_ASSERT(aClient);
  MOZ_ASSERT(!InImageBridgeChildThread());

  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId);
  MOZ_ASSERT(client);
  MOZ_ASSERT(client == aClient);

  aClient->mIsSync = false;  // async request

  if (!client) {
    HandleAcquireResult(aClient->mId, false);
    return;
  }
  // State Check
  if (aClient->mResourceState !=
      MediaSystemResourceClient::RESOURCE_STATE_START) {
    HandleAcquireResult(aClient->mId, false);
    return;
  }
  aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_WAITING;
  ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(
      NewRunnableMethod<uint32_t>("MediaSystemResourceManager::DoAcquire", this,
                                  &MediaSystemResourceManager::DoAcquire,
                                  aClient->mId));
}

bool MediaSystemResourceManager::AcquireSyncNoWait(
    MediaSystemResourceClient* aClient) {
  MOZ_ASSERT(aClient);
  MOZ_ASSERT(!InImageBridgeChildThread());

  ReentrantMonitor barrier MOZ_UNANNOTATED(
      "MediaSystemResourceManager::AcquireSyncNoWait");
  ReentrantMonitorAutoEnter autoMon(barrier);
  bool done = false;
  {
    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId);
    MOZ_ASSERT(client);
    MOZ_ASSERT(client == aClient);

    aClient->mIsSync = true;  // sync request

    if (InImageBridgeChildThread()) {
      HandleAcquireResult(aClient->mId, false);
      return false;
    }
    if (!client || client != aClient) {
      HandleAcquireResult(aClient->mId, false);
      return false;
    }
    // State Check
    if (aClient->mResourceState !=
        MediaSystemResourceClient::RESOURCE_STATE_START) {
      HandleAcquireResult(aClient->mId, false);
      return false;
    }
    // Hold barrier Monitor until acquire task end.
    aClient->mAcquireSyncWaitMonitor = &barrier;
    aClient->mAcquireSyncWaitDone = &done;
    aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_WAITING;
  }

  ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(
      NewRunnableMethod<uint32_t>("MediaSystemResourceManager::DoAcquire", this,
                                  &MediaSystemResourceManager::DoAcquire,
                                  aClient->mId));

  // should stop the thread until done.
  while (!done) {
    barrier.Wait();
  }

  {
    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    if (aClient->mResourceState !=
        MediaSystemResourceClient::RESOURCE_STATE_ACQUIRED) {
      return false;
    }
    return true;
  }
}

void MediaSystemResourceManager::DoAcquire(uint32_t aId) {
  MOZ_ASSERT(InImageBridgeChildThread());
  if (mShutDown || !mChild) {
    HandleAcquireResult(aId, false);
    return;
  }
  {
    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    MediaSystemResourceClient* client = mResourceClients.Get(aId);
    MOZ_ASSERT(client);

    if (!client || client->mResourceState !=
                       MediaSystemResourceClient::RESOURCE_STATE_WAITING) {
      HandleAcquireResult(aId, false);
      return;
    }
    MOZ_ASSERT(aId == client->mId);
    bool willWait = !client->mAcquireSyncWaitMonitor ? true : false;
    mChild->SendAcquire(client->mId, client->mResourceType, willWait);
  }
}

void MediaSystemResourceManager::ReleaseResource(
    MediaSystemResourceClient* aClient) {
  MOZ_ASSERT(aClient);
  {
    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId);
    MOZ_ASSERT(client);
    MOZ_ASSERT(client == aClient);

    if (!client || client != aClient ||
        aClient->mResourceState ==
            MediaSystemResourceClient::RESOURCE_STATE_START ||
        aClient->mResourceState ==
            MediaSystemResourceClient::RESOURCE_STATE_END) {
      aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_END;
      return;
    }

    aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_END;

    ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(
        NewRunnableMethod<uint32_t>(
            "MediaSystemResourceManager::DoRelease", this,
            &MediaSystemResourceManager::DoRelease, aClient->mId));
  }
}

void MediaSystemResourceManager::DoRelease(uint32_t aId) {
  MOZ_ASSERT(InImageBridgeChildThread());
  if (mShutDown || !mChild) {
    return;
  }
  mChild->SendRelease(aId);
}

void MediaSystemResourceManager::RecvResponse(uint32_t aId, bool aSuccess) {
  HandleAcquireResult(aId, aSuccess);
}

void MediaSystemResourceManager::HandleAcquireResult(uint32_t aId,
                                                     bool aSuccess) {
  if (!InImageBridgeChildThread()) {
    ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(
        NewRunnableMethod<uint32_t, bool>(
            "MediaSystemResourceManager::HandleAcquireResult", this,
            &MediaSystemResourceManager::HandleAcquireResult, aId, aSuccess));
    return;
  }

  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  MediaSystemResourceClient* client = mResourceClients.Get(aId);
  if (!client) {
    // Client was already unregistered.
    return;
  }
  if (client->mResourceState !=
      MediaSystemResourceClient::RESOURCE_STATE_WAITING) {
    return;
  }

  // Update state
  if (aSuccess) {
    client->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_ACQUIRED;
  } else {
    client->mResourceState =
        MediaSystemResourceClient::RESOURCE_STATE_NOT_ACQUIRED;
  }

  if (client->mIsSync) {
    if (client->mAcquireSyncWaitMonitor) {
      // Notify AcquireSync() complete
      MOZ_ASSERT(client->mAcquireSyncWaitDone);
      ReentrantMonitorAutoEnter autoMon(*client->mAcquireSyncWaitMonitor);
      *client->mAcquireSyncWaitDone = true;
      client->mAcquireSyncWaitMonitor->NotifyAll();
      client->mAcquireSyncWaitMonitor = nullptr;
      client->mAcquireSyncWaitDone = nullptr;
    }
  } else {
    // Notify Acquire() result
    if (client->mListener) {
      if (aSuccess) {
        client->mListener->ResourceReserved();
      } else {
        client->mListener->ResourceReserveFailed();
      }
    }
  }
}

}  // namespace mozilla
