/* -*- Mode: C++; tab-width: 20; 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 "mozilla/layers/NativeLayerRootRemoteMacParent.h"
#include "xpcpublic.h"

namespace mozilla {
namespace layers {

NativeLayerRootRemoteMacParent::NativeLayerRootRemoteMacParent(
    RefPtr<NativeLayerRootCA> aRealNativeLayerRoot)
    : mRealNativeLayerRoot(aRealNativeLayerRoot) {
  MOZ_ASSERT(mRealNativeLayerRoot);
}

mozilla::ipc::IPCResult
NativeLayerRootRemoteMacParent::RecvCommitNativeLayerCommands(
    nsTArray<NativeLayerCommand>&& aCommands) {
  for (auto& command : aCommands) {
    switch (command.type()) {
      case NativeLayerCommand::TCommandCreateLayer: {
        auto& createLayer = command.get_CommandCreateLayer();
        HandleCreateLayer(createLayer.ID(), createLayer.Size(),
                          createLayer.Opaque());
        break;
      }

      case NativeLayerCommand::TCommandCreateLayerForExternalTexture: {
        auto& createLayerForExternalTexture =
            command.get_CommandCreateLayerForExternalTexture();
        HandleCreateLayerForExternalTexture(
            createLayerForExternalTexture.ID(),
            createLayerForExternalTexture.Opaque());
        break;
      }

      case NativeLayerCommand::TCommandCreateLayerForColor: {
        auto& createLayerForColor = command.get_CommandCreateLayerForColor();
        HandleCreateLayerForColor(createLayerForColor.ID(),
                                  createLayerForColor.Color());
        break;
      }

      case NativeLayerCommand::TCommandLayerDestroyed: {
        auto& layerDestroyed = command.get_CommandLayerDestroyed();
        HandleLayerDestroyed(layerDestroyed.ID());
        break;
      }

      case NativeLayerCommand::TCommandSetLayers: {
        auto& setLayers = command.get_CommandSetLayers();
        HandleSetLayers(setLayers.IDs());
        break;
      }

      case NativeLayerCommand::TCommandLayerInfo: {
        auto& layerInfo = command.get_CommandLayerInfo();
        HandleLayerInfo(layerInfo.ID(), layerInfo.Position(),
                        layerInfo.DisplayRect(), layerInfo.ClipRect(),
                        layerInfo.RoundedClipRect(), layerInfo.Transform(),
                        layerInfo.SamplingFilter(),
                        layerInfo.SurfaceIsFlipped());
        break;
      }

      case NativeLayerCommand::TCommandChangedSurface: {
        auto& changedSurface = command.get_CommandChangedSurface();
        HandleChangedSurface(changedSurface.ID(),
                             std::move(changedSurface.Surface()),
                             changedSurface.IsDRM(), changedSurface.IsHDR(),
                             changedSurface.Size());
        break;
      }

      default: {
        gfxWarning() << "Unknown NativeLayerCommand.";
        break;
      }
    }
  }

  mRealNativeLayerRoot->CommitToScreen();

  return IPC_OK();
}

mozilla::ipc::IPCResult NativeLayerRootRemoteMacParent::RecvRequestReadback(
    IntSize aSize, Shmem* const aPixels) {
  if (!xpc::IsInAutomation()) {
    return IPC_FAIL(this, "Should only be called from automation.");
  }

  // Actually do a snapshot on mRealNativeLayerRoot.
  // TODO: we'll probably have to handle higher bit depth formats at some point
  // with the upcoming HDR work, but for now assume B8G8R8A8.
  auto readbackFormat = gfx::SurfaceFormat::B8G8R8A8;
  size_t readbackSize =
      aSize.width * aSize.height * gfx::BytesPerPixel(readbackFormat);
  Shmem buffer;
  if (!AllocShmem(readbackSize, &buffer)) {
    return IPC_FAIL(this, "Can't allocate shmem.");
  }

  if (!mSnapshotter) {
    mSnapshotter = mRealNativeLayerRoot->CreateSnapshotter();
    if (!mSnapshotter) {
      return IPC_FAIL(this, "Can't create parent-side snapshotter.");
    }
  }

  if (!mSnapshotter->ReadbackPixels(aSize, readbackFormat,
                                    buffer.Range<uint8_t>())) {
    return IPC_FAIL(this, "Failed readback.");
  }

  *aPixels = buffer;
  return IPC_OK();
}

mozilla::ipc::IPCResult NativeLayerRootRemoteMacParent::RecvFlush() {
  // No-op message; when this returns, the other side knows that any
  // preceding messages have finished processing.
  return IPC_OK();
}

void NativeLayerRootRemoteMacParent::HandleCreateLayer(uint64_t aID,
                                                       IntSize aSize,
                                                       bool aOpaque) {
  RefPtr<NativeLayerCA> layer =
      mRealNativeLayerRoot->CreateLayerForSurfacePresentation(aSize, aOpaque);
  mKnownLayers.InsertOrUpdate(aID, layer);
}

void NativeLayerRootRemoteMacParent::HandleCreateLayerForExternalTexture(
    uint64_t aID, bool aOpaque) {
  RefPtr<NativeLayer> layer =
      mRealNativeLayerRoot->CreateLayerForExternalTexture(aOpaque);
  mKnownLayers.InsertOrUpdate(aID, layer);
}

void NativeLayerRootRemoteMacParent::HandleCreateLayerForColor(
    uint64_t aID, DeviceColor aColor) {
  RefPtr<NativeLayer> layer = mRealNativeLayerRoot->CreateLayerForColor(aColor);
  mKnownLayers.InsertOrUpdate(aID, layer);
}

void NativeLayerRootRemoteMacParent::HandleLayerDestroyed(uint64_t aID) {
  mKnownLayers.Remove(aID);
}

void NativeLayerRootRemoteMacParent::HandleSetLayers(
    const nsTArray<uint64_t>& aIDs) {
  nsTArray<RefPtr<NativeLayer>> layers;
  for (auto ID : aIDs) {
    auto entry = mKnownLayers.MaybeGet(ID);
    if (!entry) {
      gfxWarning() << "Got a SetLayers for an unknown layer.";
      continue;
    }

    RefPtr<NativeLayer> layer = *entry;
    layers.AppendElement(layer);
  }
  mRealNativeLayerRoot->SetLayers(layers);
}

void NativeLayerRootRemoteMacParent::HandleLayerInfo(
    uint64_t aID, IntPoint aPosition, IntRect aDisplayRect,
    Maybe<IntRect> aClipRect, Maybe<RoundedRect> aRoundedClipRect,
    Matrix4x4 aTransform, int8_t aSamplingFilter, bool aSurfaceIsFlipped) {
  auto entry = mKnownLayers.MaybeGet(aID);
  if (!entry) {
    gfxWarning() << "Got a LayerInfo for an unknown layer.";
    return;
  }

  RefPtr<NativeLayerCA> layer = (*entry)->AsNativeLayerCA();
  MOZ_ASSERT(layer, "All of our known layers should be NativeLayerCA.");

  // Set the other properties of layer.
  layer->SetPosition(aPosition);
  layer->SetDisplayRect(aDisplayRect);
  layer->SetClipRect(aClipRect);
  layer->SetRoundedClipRect(aRoundedClipRect);
  layer->SetTransform(aTransform);
  layer->SetSamplingFilter(static_cast<gfx::SamplingFilter>(aSamplingFilter));
  layer->SetSurfaceIsFlipped(aSurfaceIsFlipped);
}

void NativeLayerRootRemoteMacParent::HandleChangedSurface(
    uint64_t aID, IOSurfacePort aSurfacePort, bool aIsDRM, bool aIsHDR,
    IntSize aSize) {
  auto entry = mKnownLayers.MaybeGet(aID);
  if (!entry) {
    gfxWarning() << "Got a ChangedSurface for an unknown layer.";
    return;
  }

  RefPtr<NativeLayerCA> layer = (*entry)->AsNativeLayerCA();
  MOZ_ASSERT(layer, "All of our known layers should be NativeLayerCA.");

  if (auto surface = aSurfacePort.GetSurface()) {
    layer->SetSurfaceToPresent(surface, aSize, aIsDRM, aIsHDR);
  }
}

}  // namespace layers
}  // namespace mozilla
