// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/android/surface_callback.h"

#include <android/native_window_jni.h>

#include "base/android/jni_android.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "ui/gl/android_native_window.h"
#include "jni/SurfaceCallback_jni.h"

using base::android::AttachCurrentThread;
using base::android::CheckException;
using base::android::GetMethodID;
using base::WaitableEvent;
using content::SurfaceTexturePeer;

namespace content {

namespace {

struct GlobalState {
  base::Lock registration_lock;
  // We hold a reference to a message loop proxy which handles message loop
  // destruction gracefully, which is important since we post tasks from an
  // arbitrary binder thread while the main thread might be shutting down.
  // Also, in single-process mode we have two ChildThread objects for render
  // and gpu thread, so we need to store the msg loops separately.
  scoped_refptr<base::MessageLoopProxy> native_window_loop;
  scoped_refptr<base::MessageLoopProxy> video_surface_loop;
  NativeWindowCallback native_window_callback;
  VideoSurfaceCallback video_surface_callback;
};

base::LazyInstance<GlobalState>::Leaky g_state = LAZY_INSTANCE_INITIALIZER;

void RunNativeWindowCallback(int32 routing_id,
                             int32 renderer_id,
                             ANativeWindow* native_window,
                             WaitableEvent* completion) {
  g_state.Pointer()->native_window_callback.Run(
      routing_id, renderer_id, native_window, completion);
}

void RunVideoSurfaceCallback(int32 routing_id,
                             int32 renderer_id,
                             jobject surface) {
  g_state.Pointer()->video_surface_callback.Run(
      routing_id, renderer_id, surface);
}

}  // namespace <anonymous>

static void SetSurface(JNIEnv* env, jclass clazz,
                       jint type,
                       jobject surface,
                       jint primaryID,
                       jint secondaryID) {
  SetSurfaceAsync(env, surface,
                  static_cast<SurfaceTexturePeer::SurfaceTextureTarget>(type),
                  primaryID, secondaryID, NULL);
}

void ReleaseSurface(jobject surface) {
  if (surface == NULL)
    return;

  JNIEnv* env = AttachCurrentThread();
  CHECK(env);

  jclass cls = env->FindClass("android/view/Surface");
  DCHECK(cls);

  jmethodID method = env->GetMethodID(cls, "release", "()V");
  DCHECK(method);

  env->CallVoidMethod(surface, method);
  DCHECK(env);

  env->DeleteLocalRef(cls);
}

void SetSurfaceAsync(JNIEnv* env,
                     jobject jsurface,
                     SurfaceTexturePeer::SurfaceTextureTarget type,
                     int primary_id,
                     int secondary_id,
                     WaitableEvent* completion) {
  base::AutoLock lock(g_state.Pointer()->registration_lock);

  ANativeWindow* native_window = NULL;

  if (jsurface &&
      type != SurfaceTexturePeer::SET_VIDEO_SURFACE_TEXTURE) {
    native_window = ANativeWindow_fromSurface(env, jsurface);
    ReleaseSurface(jsurface);
  }
  GlobalState* const global_state = g_state.Pointer();

  switch (type) {
    case SurfaceTexturePeer::SET_GPU_SURFACE_TEXTURE: {
      // This should only be sent as a reaction to the renderer
      // activating compositing. If the GPU process crashes, we expect this
      // to be resent after the new thread is initialized.
      DCHECK(global_state->native_window_loop);
      global_state->native_window_loop->PostTask(
          FROM_HERE,
          base::Bind(&RunNativeWindowCallback,
              primary_id,
              static_cast<uint32_t>(secondary_id),
              native_window,
              completion));
      // ANativeWindow_release will be called in SetNativeWindow()
      break;
    }
    case SurfaceTexturePeer::SET_VIDEO_SURFACE_TEXTURE: {
      jobject surface = env->NewGlobalRef(jsurface);
      DCHECK(global_state->video_surface_loop);
      global_state->video_surface_loop->PostTask(
          FROM_HERE,
          base::Bind(&RunVideoSurfaceCallback,
                     primary_id,
                     secondary_id,
                     surface));
      break;
    }
  }
}

void RegisterVideoSurfaceCallback(base::MessageLoopProxy* loop,
                                  VideoSurfaceCallback& callback) {
  GlobalState* const global_state = g_state.Pointer();
  base::AutoLock lock(global_state->registration_lock);
  global_state->video_surface_loop = loop;
  global_state->video_surface_callback = callback;
}

void RegisterNativeWindowCallback(base::MessageLoopProxy* loop,
                                  NativeWindowCallback& callback) {
  GlobalState* const global_state = g_state.Pointer();
  base::AutoLock lock(global_state->registration_lock);
  global_state->native_window_loop = loop;
  global_state->native_window_callback = callback;
}

bool RegisterSurfaceCallback(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

}  // namespace content