/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "GLContext.h"           // for GLContext, etc
#include "mozilla/Assertions.h"  // for MOZ_ASSERT, etc
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/layers/TextureClientOGL.h"
#include "mozilla/gfx/2D.h"     // for Factory
#include "mozilla/gfx/Point.h"  // for IntSize
#include "GLLibraryEGL.h"

#ifdef MOZ_WIDGET_ANDROID
#  include <jni.h>
#  include <android/native_window.h>
#  include <android/native_window_jni.h>
#  include <sys/socket.h>
#  include "mozilla/ipc/FileDescriptor.h"
#  include "mozilla/java/GeckoSurfaceWrappers.h"
#  include "mozilla/java/SurfaceAllocatorWrappers.h"
#  include "mozilla/layers/AndroidHardwareBuffer.h"
#  include "mozilla/UniquePtrExtensions.h"
#endif

using namespace mozilla::gl;

namespace mozilla {
namespace layers {

class CompositableForwarder;

////////////////////////////////////////////////////////////////////////
// AndroidSurface

#ifdef MOZ_WIDGET_ANDROID

already_AddRefed<TextureClient> AndroidSurfaceTextureData::CreateTextureClient(
    AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous,
    gl::OriginPos aOriginPos, bool aHasAlpha, bool aForceBT709ColorSpace,
    Maybe<gfx::Matrix4x4> aTransformOverride, LayersIPCChannel* aAllocator,
    TextureFlags aFlags) {
  if (aOriginPos == gl::OriginPos::BottomLeft) {
    aFlags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
  }

  return TextureClient::CreateWithData(
      new AndroidSurfaceTextureData(aHandle, aSize, aContinuous, aHasAlpha,
                                    aForceBT709ColorSpace, aTransformOverride),
      aFlags, aAllocator);
}

AndroidSurfaceTextureData::AndroidSurfaceTextureData(
    AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous,
    bool aHasAlpha, bool aForceBT709ColorSpace,
    Maybe<gfx::Matrix4x4> aTransformOverride)
    : mHandle(aHandle),
      mSize(aSize),
      mContinuous(aContinuous),
      mHasAlpha(aHasAlpha),
      mForceBT709ColorSpace(aForceBT709ColorSpace),
      mTransformOverride(aTransformOverride) {
  MOZ_ASSERT(mHandle);
}

AndroidSurfaceTextureData::~AndroidSurfaceTextureData() {}

void AndroidSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const {
  aInfo.size = mSize;
  aInfo.format = gfx::SurfaceFormat::UNKNOWN;
  aInfo.hasSynchronization = false;
  aInfo.supportsMoz2D = false;
  aInfo.canExposeMappedData = false;
}

bool AndroidSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) {
  aOutDescriptor = SurfaceTextureDescriptor(
      mHandle, mSize,
      mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8,
      mContinuous, mForceBT709ColorSpace, mTransformOverride);
  return true;
}

#endif  // MOZ_WIDGET_ANDROID

////////////////////////////////////////////////////////////////////////
// AndroidNativeWindow

#ifdef MOZ_WIDGET_ANDROID

AndroidNativeWindowTextureData* AndroidNativeWindowTextureData::Create(
    gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
  if (aFormat != gfx::SurfaceFormat::R8G8B8A8 &&
      aFormat != gfx::SurfaceFormat::R8G8B8X8 &&
      aFormat != gfx::SurfaceFormat::B8G8R8A8 &&
      aFormat != gfx::SurfaceFormat::B8G8R8X8 &&
      aFormat != gfx::SurfaceFormat::R5G6B5_UINT16) {
    return nullptr;
  }

  auto surface =
      java::GeckoSurface::LocalRef(java::SurfaceAllocator::AcquireSurface(
          aSize.width, aSize.height, true /* single-buffer mode */));
  if (surface) {
    return new AndroidNativeWindowTextureData(surface, aSize, aFormat);
  }

  return nullptr;
}

AndroidNativeWindowTextureData::AndroidNativeWindowTextureData(
    java::GeckoSurface::Param aSurface, gfx::IntSize aSize,
    gfx::SurfaceFormat aFormat)
    : mSurface(aSurface), mIsLocked(false), mSize(aSize), mFormat(aFormat) {
  mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
                                            mSurface->GetSurface().Get());
  MOZ_ASSERT(mNativeWindow, "Failed to create NativeWindow.");

  // SurfaceTextures don't technically support BGR, but we can just pretend to
  // be RGB.
  int32_t format = WINDOW_FORMAT_RGBA_8888;
  switch (aFormat) {
    case gfx::SurfaceFormat::R8G8B8A8:
    case gfx::SurfaceFormat::B8G8R8A8:
      format = WINDOW_FORMAT_RGBA_8888;
      break;

    case gfx::SurfaceFormat::R8G8B8X8:
    case gfx::SurfaceFormat::B8G8R8X8:
      format = WINDOW_FORMAT_RGBX_8888;
      break;

    case gfx::SurfaceFormat::R5G6B5_UINT16:
      format = WINDOW_FORMAT_RGB_565;
      break;

    default:
      MOZ_ASSERT(false, "Unsupported AndroidNativeWindowTextureData format.");
  }

  DebugOnly<int32_t> r = ANativeWindow_setBuffersGeometry(
      mNativeWindow, mSize.width, mSize.height, format);
  MOZ_ASSERT(r == 0, "ANativeWindow_setBuffersGeometry failed.");

  // Ideally here we'd call ANativeWindow_setBuffersTransform() with the
  // identity transform, but that is only available on api level >= 26.
  // Instead use SurfaceDescriptor's transformOverride flag when serializing.
}

void AndroidNativeWindowTextureData::FillInfo(TextureData::Info& aInfo) const {
  aInfo.size = mSize;
  aInfo.format = mFormat;
  aInfo.hasSynchronization = false;
  aInfo.supportsMoz2D = true;
  aInfo.canExposeMappedData = false;
  aInfo.canConcurrentlyReadLock = false;
}

bool AndroidNativeWindowTextureData::Serialize(
    SurfaceDescriptor& aOutDescriptor) {
  aOutDescriptor = SurfaceTextureDescriptor(
      mSurface->GetHandle(), mSize, mFormat, false /* not continuous */,
      false /* do not override colorspace */,
      Some(gfx::Matrix4x4()) /* always use identity transform */);
  return true;
}

bool AndroidNativeWindowTextureData::Lock(OpenMode) {
  // ANativeWindows can only be locked and unlocked a single time, after which
  // we must wait until they receive ownership back from the host.
  // Therefore we must only actually call ANativeWindow_lock() once per cycle.
  if (!mIsLocked) {
    int32_t r = ANativeWindow_lock(mNativeWindow, &mBuffer, nullptr);
    if (r == -ENOMEM) {
      return false;
    } else if (r < 0) {
      MOZ_CRASH("ANativeWindow_lock failed.");
    }
    mIsLocked = true;
  }
  return true;
}

void AndroidNativeWindowTextureData::Unlock() {
  // The TextureClient may want to call Lock again before handing ownership
  // to the host, so we cannot call ANativeWindow_unlockAndPost yet.
}

void AndroidNativeWindowTextureData::Forget(LayersIPCChannel*) {
  MOZ_ASSERT(!mIsLocked,
             "ANativeWindow should not be released while locked.\n");
  ANativeWindow_release(mNativeWindow);
  mNativeWindow = nullptr;
  java::SurfaceAllocator::DisposeSurface(mSurface);
  mSurface = nullptr;
}

already_AddRefed<gfx::DrawTarget>
AndroidNativeWindowTextureData::BorrowDrawTarget() {
  const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4;

  return gfx::Factory::CreateDrawTargetForData(
      gfx::BackendType::SKIA, static_cast<unsigned char*>(mBuffer.bits),
      gfx::IntSize(mBuffer.width, mBuffer.height), mBuffer.stride * bpp,
      mFormat, true);
}

void AndroidNativeWindowTextureData::OnForwardedToHost() {
  if (mIsLocked) {
    int32_t r = ANativeWindow_unlockAndPost(mNativeWindow);
    if (r < 0) {
      MOZ_CRASH("ANativeWindow_unlockAndPost failed\n.");
    }
    mIsLocked = false;
  }
}

AndroidHardwareBufferTextureData* AndroidHardwareBufferTextureData::Create(
    gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
  RefPtr<AndroidHardwareBuffer> buffer =
      AndroidHardwareBuffer::Create(aSize, aFormat);
  if (!buffer) {
    return nullptr;
  }
  return new AndroidHardwareBufferTextureData(buffer, aSize, aFormat);
}

AndroidHardwareBufferTextureData::AndroidHardwareBufferTextureData(
    AndroidHardwareBuffer* aAndroidHardwareBuffer, gfx::IntSize aSize,
    gfx::SurfaceFormat aFormat)
    : mAndroidHardwareBuffer(aAndroidHardwareBuffer),
      mSize(aSize),
      mFormat(aFormat),
      mAddress(nullptr),
      mIsLocked(false) {}

AndroidHardwareBufferTextureData::~AndroidHardwareBufferTextureData() {}

void AndroidHardwareBufferTextureData::FillInfo(
    TextureData::Info& aInfo) const {
  aInfo.size = mSize;
  aInfo.format = mFormat;
  aInfo.hasSynchronization = true;
  aInfo.supportsMoz2D = true;
  aInfo.canExposeMappedData = false;
  aInfo.canConcurrentlyReadLock = true;
}

bool AndroidHardwareBufferTextureData::Serialize(
    SurfaceDescriptor& aOutDescriptor) {
  aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer(
      mAndroidHardwareBuffer->mId, mSize, mFormat);
  return true;
}

bool AndroidHardwareBufferTextureData::Lock(OpenMode aMode) {
  if (!mIsLocked) {
    MOZ_ASSERT(!mAddress);

    uint64_t usage = 0;
    if (aMode & OpenMode::OPEN_READ) {
      usage |= AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN;
    }
    if (aMode & OpenMode::OPEN_WRITE) {
      usage |= AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
    }

    int ret = mAndroidHardwareBuffer->Lock(usage, 0, &mAddress);
    if (ret) {
      mAddress = nullptr;
      return false;
    }
    mIsLocked = true;
  }
  return true;
}

void AndroidHardwareBufferTextureData::Unlock() {
  // The TextureClient may want to call Lock again before handing ownership
  // to the host, so we cannot call AHardwareBuffer_unlock yet.
}

void AndroidHardwareBufferTextureData::Forget(LayersIPCChannel*) {
  MOZ_ASSERT(!mIsLocked);
  mAndroidHardwareBuffer = nullptr;
  mAddress = nullptr;
}

already_AddRefed<gfx::DrawTarget>
AndroidHardwareBufferTextureData::BorrowDrawTarget() {
  MOZ_ASSERT(mIsLocked);

  const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4;

  return gfx::Factory::CreateDrawTargetForData(
      gfx::BackendType::SKIA, static_cast<unsigned char*>(mAddress),
      gfx::IntSize(mAndroidHardwareBuffer->mSize.width,
                   mAndroidHardwareBuffer->mSize.height),
      mAndroidHardwareBuffer->mStride * bpp, mFormat, true);
}

void AndroidHardwareBufferTextureData::OnForwardedToHost() {
  if (mIsLocked) {
    mAndroidHardwareBuffer->Unlock();
    mAddress = nullptr;
    mIsLocked = false;
  }
}

TextureFlags AndroidHardwareBufferTextureData::GetTextureFlags() const {
  return TextureFlags::WAIT_HOST_USAGE_END;
}

Maybe<uint64_t> AndroidHardwareBufferTextureData::GetBufferId() const {
  return Some(mAndroidHardwareBuffer->mId);
}

UniqueFileHandle AndroidHardwareBufferTextureData::GetAcquireFence() {
  if (!mAndroidHardwareBuffer) {
    return UniqueFileHandle();
  }

  return mAndroidHardwareBuffer->GetAcquireFence();
}

#endif  // MOZ_WIDGET_ANDROID

}  // namespace layers
}  // namespace mozilla
