/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "video_capture_android.h"

#include "device_info_android.h"
#include "modules/utility/include/helpers_android.h"
#include "rtc_base/logging.h"
#include "rtc_base/ref_counted_object.h"
#include "rtc_base/time_utils.h"

#include "AndroidBridge.h"

static JavaVM* g_jvm_capture = NULL;
static jclass g_java_capturer_class = NULL;  // VideoCaptureAndroid.class.
static jobject g_context = NULL;             // Owned android.content.Context.

namespace webrtc {

jobject JniCommon_allocateNativeByteBuffer(JNIEnv* env, jclass, jint size) {
  void* new_data = ::operator new(size);
  jobject byte_buffer = env->NewDirectByteBuffer(new_data, size);
  return byte_buffer;
}

void JniCommon_freeNativeByteBuffer(JNIEnv* env, jclass, jobject byte_buffer) {
  void* data = env->GetDirectBufferAddress(byte_buffer);
  ::operator delete(data);
}

// Called by Java to get the global application context.
jobject JNICALL GetContext(JNIEnv* env, jclass) {
  assert(g_context);
  return g_context;
}

// Called by Java when the camera has a new frame to deliver.
void JNICALL ProvideCameraFrame(JNIEnv* env, jobject, jint width, jint height,
                                jobject javaDataY, jint strideY,
                                jobject javaDataU, jint strideU,
                                jobject javaDataV, jint strideV, jint rotation,
                                jlong timeStamp, jlong context) {
  if (!context) {
    return;
  }

  webrtc::videocapturemodule::VideoCaptureAndroid* captureModule =
      reinterpret_cast<webrtc::videocapturemodule::VideoCaptureAndroid*>(
          context);
  uint8_t* dataY =
      reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(javaDataY));
  uint8_t* dataU =
      reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(javaDataU));
  uint8_t* dataV =
      reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(javaDataV));

  rtc::scoped_refptr<I420Buffer> i420Buffer = I420Buffer::Copy(
      width, height, dataY, strideY, dataU, strideU, dataV, strideV);

  captureModule->OnIncomingFrame(i420Buffer, rotation, timeStamp);
}

int32_t SetCaptureAndroidVM(JavaVM* javaVM) {
  if (g_java_capturer_class) {
    return 0;
  }

  if (javaVM) {
    assert(!g_jvm_capture);
    g_jvm_capture = javaVM;
    AttachThreadScoped ats(g_jvm_capture);

    g_context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();

    videocapturemodule::DeviceInfoAndroid::Initialize(g_jvm_capture);

    {
      jclass clsRef = mozilla::jni::GetClassRef(
          ats.env(), "org/webrtc/videoengine/VideoCaptureAndroid");
      g_java_capturer_class =
          static_cast<jclass>(ats.env()->NewGlobalRef(clsRef));
      ats.env()->DeleteLocalRef(clsRef);
      assert(g_java_capturer_class);

      JNINativeMethod native_methods[] = {
          {"GetContext", "()Landroid/content/Context;",
           reinterpret_cast<void*>(&GetContext)},
          {"ProvideCameraFrame",
           "(IILjava/nio/ByteBuffer;ILjava/nio/ByteBuffer;ILjava/nio/"
           "ByteBuffer;IIJJ)V",
           reinterpret_cast<void*>(&ProvideCameraFrame)}};
      if (ats.env()->RegisterNatives(g_java_capturer_class, native_methods,
                                     2) != 0)
        assert(false);
    }

    {
      jclass clsRef =
          mozilla::jni::GetClassRef(ats.env(), "org/webrtc/JniCommon");

      JNINativeMethod native_methods[] = {
          {"nativeAllocateByteBuffer", "(I)Ljava/nio/ByteBuffer;",
           reinterpret_cast<void*>(&JniCommon_allocateNativeByteBuffer)},
          {"nativeFreeByteBuffer", "(Ljava/nio/ByteBuffer;)V",
           reinterpret_cast<void*>(&JniCommon_freeNativeByteBuffer)}};
      if (ats.env()->RegisterNatives(clsRef, native_methods, 2) != 0)
        assert(false);
    }
  } else {
    if (g_jvm_capture) {
      AttachThreadScoped ats(g_jvm_capture);
      ats.env()->UnregisterNatives(g_java_capturer_class);
      ats.env()->DeleteGlobalRef(g_java_capturer_class);
      g_java_capturer_class = NULL;
      g_context = NULL;
      videocapturemodule::DeviceInfoAndroid::DeInitialize();
      g_jvm_capture = NULL;
    }
  }

  return 0;
}

namespace videocapturemodule {

rtc::scoped_refptr<VideoCaptureModule> VideoCaptureImpl::Create(
    const char* deviceUniqueIdUTF8) {
  rtc::scoped_refptr<VideoCaptureAndroid> implementation(
      new rtc::RefCountedObject<VideoCaptureAndroid>());
  if (implementation->Init(deviceUniqueIdUTF8) != 0) {
    implementation = nullptr;
  }
  return implementation;
}

void VideoCaptureAndroid::OnIncomingFrame(rtc::scoped_refptr<I420Buffer> buffer,
                                          int32_t degrees,
                                          int64_t captureTime) {
  MutexLock lock(&api_lock_);

  VideoRotation rotation =
      (degrees <= 45 || degrees > 315)    ? kVideoRotation_0
      : (degrees > 45 && degrees <= 135)  ? kVideoRotation_90
      : (degrees > 135 && degrees <= 225) ? kVideoRotation_180
      : (degrees > 225 && degrees <= 315) ? kVideoRotation_270
                                          : kVideoRotation_0;  // Impossible.

  // Historically, we have ignored captureTime. Why?
  VideoFrame captureFrame(I420Buffer::Rotate(*buffer, rotation), 0,
                          rtc::TimeMillis(), rotation);

  DeliverCapturedFrame(captureFrame);
}

VideoCaptureAndroid::VideoCaptureAndroid()
    : VideoCaptureImpl(),
      _deviceInfo(),
      _jCapturer(NULL),
      _captureStarted(false) {}

int32_t VideoCaptureAndroid::Init(const char* deviceUniqueIdUTF8) {
  const int nameLength = strlen(deviceUniqueIdUTF8);
  if (nameLength >= kVideoCaptureUniqueNameLength) return -1;

  // Store the device name
  RTC_LOG(LS_INFO) << "VideoCaptureAndroid::Init: " << deviceUniqueIdUTF8;
  _deviceUniqueId = new char[nameLength + 1];
  memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1);

  AttachThreadScoped ats(g_jvm_capture);
  JNIEnv* env = ats.env();
  jmethodID ctor = env->GetMethodID(g_java_capturer_class, "<init>",
                                    "(Ljava/lang/String;)V");
  assert(ctor);
  jstring j_deviceName = env->NewStringUTF(_deviceUniqueId);
  _jCapturer = env->NewGlobalRef(
      env->NewObject(g_java_capturer_class, ctor, j_deviceName));
  assert(_jCapturer);
  return 0;
}

VideoCaptureAndroid::~VideoCaptureAndroid() {
  // Ensure Java camera is released even if our caller didn't explicitly Stop.
  if (_captureStarted) StopCapture();
  AttachThreadScoped ats(g_jvm_capture);
  JNIEnv* env = ats.env();
  env->DeleteGlobalRef(_jCapturer);
}

int32_t VideoCaptureAndroid::StartCapture(
    const VideoCaptureCapability& capability) {
  AttachThreadScoped ats(g_jvm_capture);
  JNIEnv* env = ats.env();
  int width = 0;
  int height = 0;
  int min_mfps = 0;
  int max_mfps = 0;
  {
    MutexLock lock(&api_lock_);

    if (_deviceInfo.GetBestMatchedCapability(_deviceUniqueId, capability,
                                             _captureCapability) < 0) {
      RTC_LOG(LS_ERROR) << __FUNCTION__
                        << "s: GetBestMatchedCapability failed: "
                        << capability.width << "x" << capability.height;
      return -1;
    }

    width = _captureCapability.width;
    height = _captureCapability.height;
    _deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS,
                             &min_mfps, &max_mfps);

    // Exit critical section to avoid blocking camera thread inside
    // onIncomingFrame() call.
  }

  jmethodID j_start =
      env->GetMethodID(g_java_capturer_class, "startCapture", "(IIIIJ)Z");
  assert(j_start);
  jlong j_this = reinterpret_cast<intptr_t>(this);
  bool started = env->CallBooleanMethod(_jCapturer, j_start, width, height,
                                        min_mfps, max_mfps, j_this);
  if (started) {
    MutexLock lock(&api_lock_);
    _requestedCapability = capability;
    _captureStarted = true;
  }
  return started ? 0 : -1;
}

int32_t VideoCaptureAndroid::StopCapture() {
  AttachThreadScoped ats(g_jvm_capture);
  JNIEnv* env = ats.env();
  {
    MutexLock lock(&api_lock_);

    memset(&_requestedCapability, 0, sizeof(_requestedCapability));
    memset(&_captureCapability, 0, sizeof(_captureCapability));
    _captureStarted = false;
    // Exit critical section to avoid blocking camera thread inside
    // onIncomingFrame() call.
  }

  // try to stop the capturer.
  jmethodID j_stop =
      env->GetMethodID(g_java_capturer_class, "stopCapture", "()Z");
  return env->CallBooleanMethod(_jCapturer, j_stop) ? 0 : -1;
}

bool VideoCaptureAndroid::CaptureStarted() {
  MutexLock lock(&api_lock_);
  return _captureStarted;
}

int32_t VideoCaptureAndroid::CaptureSettings(VideoCaptureCapability& settings) {
  MutexLock lock(&api_lock_);
  settings = _requestedCapability;
  return 0;
}

}  // namespace videocapturemodule
}  // namespace webrtc
