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

#include "mozilla/layers/AndroidImageConsumer.h"
#include "mozilla/layers/AndroidImageReader.h"
#include "mozilla/layers/AndroidHardwareBuffer.h"
#include "mozilla/layers/TextureHostOGL.h"
#include "mozilla/webrender/RenderThread.h"
#include "mozilla/gfx/2D.h"
#include "GLContextEGL.h"
#include "GLLibraryEGL.h"
#include "GLReadTexImageHelper.h"
#include "OGLShaderConfig.h"

namespace mozilla {
namespace wr {

RenderAndroidImageReaderImageTextureHost::
    RenderAndroidImageReaderImageTextureHost(
        const layers::GpuProcessAndroidImageReaderId aImageReaderId,
        const layers::AndroidMediaCodecFrameId aFrameId,
        const gfx::IntSize aSize, const gfx::SurfaceFormat aFormat)
    : mImageReaderId(aImageReaderId),
      mFrameId(aFrameId),
      mSize(aSize),
      mFormat(aFormat) {
  MOZ_COUNT_CTOR_INHERITED(RenderAndroidImageReaderImageTextureHost,
                           RenderTextureHost);
}

RenderAndroidImageReaderImageTextureHost::
    ~RenderAndroidImageReaderImageTextureHost() {
  MOZ_COUNT_DTOR_INHERITED(RenderAndroidImageReaderImageTextureHost,
                           RenderTextureHost);
}

gfx::IntSize RenderAndroidImageReaderImageTextureHost::GetSize() const {
  return mSize;
}

bool RenderAndroidImageReaderImageTextureHost::EnsureLockable() {
  MOZ_ASSERT(mGL);

  if (!mAndroidImageConsumer) {
    auto* imageReaderMap = layers::GpuProcessAndroidImageReaderMap::Get();
    if (!imageReaderMap) {
      return false;
    }

    mAndroidImageConsumer =
        imageReaderMap->GetImageConsumer(mImageReaderId, mGL);
    if (!mAndroidImageConsumer) {
      return false;
    }
  }

  MOZ_ASSERT(mAndroidImageConsumer);

  mAndroidImageConsumer->UpdateTexImage(mFrameId);

  if (!mAndroidImageConsumer->HasImage()) {
    // XXX
    return false;
  }

  MOZ_RELEASE_ASSERT(mAndroidImageConsumer->GetSize() == mSize);
  MOZ_RELEASE_ASSERT(mAndroidImageConsumer->GetFormat() == mFormat);

  const auto handle = mAndroidImageConsumer->GetGlTextureHandle();
  ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
                               LOCAL_GL_TEXTURE_EXTERNAL_OES, handle);
  return true;
}

wr::WrExternalImage RenderAndroidImageReaderImageTextureHost::Lock(
    uint8_t aChannelIndex, gl::GLContext* aGL) {
  MOZ_ASSERT(aChannelIndex == 0);

  if (mGL.get() != aGL) {
    if (mGL) {
      // This should not happen.
      MOZ_ASSERT_UNREACHABLE("Unexpected GL context");
      return InvalidToWrExternalImage();
    }
    mGL = aGL;
  }

  if (!mGL || !mGL->MakeCurrent()) {
    return InvalidToWrExternalImage();
  }

  if (!EnsureLockable()) {
    return InvalidToWrExternalImage();
  }

  const auto handle = mAndroidImageConsumer->GetGlTextureHandle();
  const gfx::IntSize size = GetSize();
  return NativeTextureToWrExternalImage(handle, 0.0, 0.0,
                                        static_cast<float>(size.width),
                                        static_cast<float>(size.height));
}

void RenderAndroidImageReaderImageTextureHost::Unlock() {}

size_t RenderAndroidImageReaderImageTextureHost::Bytes() {
  return GetSize().width * GetSize().height * BytesPerPixel(GetFormat());
}

gfx::SurfaceFormat RenderAndroidImageReaderImageTextureHost::GetFormat() const {
  return mFormat;
}

already_AddRefed<gfx::DataSourceSurface>
RenderAndroidImageReaderImageTextureHost::ReadTexImage() {
  if (!mGL) {
    mGL = RenderThread::Get()->SingletonGL();
    if (!mGL) {
      return nullptr;
    }
  }

  if (!EnsureLockable()) {
    return nullptr;
  }

  /* Allocate resulting image surface */
  int32_t stride = GetSize().width * BytesPerPixel(GetFormat());
  RefPtr<gfx::DataSourceSurface> surf =
      gfx::Factory::CreateDataSourceSurfaceWithStride(GetSize(), GetFormat(),
                                                      stride);
  if (!surf) {
    return nullptr;
  }

  layers::ShaderConfigOGL config = layers::ShaderConfigFromTargetAndFormat(
      LOCAL_GL_TEXTURE_EXTERNAL, GetFormat());
  int shaderConfig = config.mFeatures;

  const auto handle = mAndroidImageConsumer->GetGlTextureHandle();
  bool ret = mGL->ReadTexImageHelper()->ReadTexImage(
      surf, handle, LOCAL_GL_TEXTURE_EXTERNAL, GetSize(), gfx::Matrix4x4(),
      shaderConfig, /* aYInvert */ false);
  if (!ret) {
    return nullptr;
  }

  return surf.forget();
}

bool RenderAndroidImageReaderImageTextureHost::MapPlane(
    RenderCompositor* aCompositor, uint8_t aChannelIndex,
    PlaneInfo& aPlaneInfo) {
  RefPtr<gfx::DataSourceSurface> readback = ReadTexImage();
  if (!readback) {
    return false;
  }

  gfx::DataSourceSurface::MappedSurface map;
  if (!readback->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
    return false;
  }

  mReadback = readback;
  aPlaneInfo.mSize = GetSize();
  aPlaneInfo.mStride = map.mStride;
  aPlaneInfo.mData = map.mData;
  return true;
}

void RenderAndroidImageReaderImageTextureHost::UnmapPlanes() {
  if (mReadback) {
    mReadback->Unmap();
    mReadback = nullptr;
  }
}

RefPtr<layers::TextureSource>
RenderAndroidImageReaderImageTextureHost::CreateTextureSource(
    layers::TextureSourceProvider* aProvider) {
  if (!mGL) {
    mGL = RenderThread::Get()->SingletonGL();
    if (!mGL) {
      return nullptr;
    }
  }
  if (!EnsureLockable()) {
    return nullptr;
  }
  if (!mAndroidImageConsumer) {
    return nullptr;
  }
  return new layers::AndroidImageReaderImageTextureSource(
      aProvider, mAndroidImageConsumer, GetFormat(), LOCAL_GL_TEXTURE_EXTERNAL,
      LOCAL_GL_CLAMP_TO_EDGE, GetSize());
}

}  // namespace wr
}  // namespace mozilla
