/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// #define LOG_NDEBUG 0

#include <stdint.h>
#include <android/native_window_jni.h>

#include "jni/jni_gl_environment.h"
#include "jni/jni_util.h"
#include <media/mediarecorder.h>
#include "native/core/gl_env.h"

#include <gui/ISurfaceTexture.h>
#include <gui/SurfaceTextureClient.h>
#include <utils/Errors.h>
#include <system/window.h>


using android::filterfw::GLEnv;
using android::filterfw::WindowHandle;
using android::MediaRecorder;
using android::sp;
using android::ISurfaceTexture;
using android::SurfaceTextureClient;


class NativeWindowHandle : public WindowHandle {
  public:
    NativeWindowHandle(ANativeWindow* window) : window_(window) {
    }

    virtual ~NativeWindowHandle() {
    }

    virtual void Destroy() {
      LOGI("Releasing ANativeWindow!");
      ANativeWindow_release(window_);
    }

    virtual const void* InternalHandle() const {
      return window_;
    }

    virtual void* InternalHandle() {
      return window_;
    }

  private:
    ANativeWindow* window_;
};

jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) {
  return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true));
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) {
  return ToJBool(DeleteNativeObject<GLEnv>(env, thiz));
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env,
                                                                           jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->InitWithNewContext()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env,
                                                                               jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->InitWithCurrentContext()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->IsActive()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->IsContextActive()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env,
                                                                           jclass clazz) {
  return ToJBool(GLEnv::IsAnyContextActive());
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->Activate()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->Deactivate()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->SwapBuffers()) : JNI_FALSE;
}

// Get the native mediarecorder object corresponding to the java object
static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject jmediarecorder) {
    jclass clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return NULL;
    }

    jfieldID context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (context == NULL) {
        return NULL;
    }

    MediaRecorder* const p = (MediaRecorder*)env->GetIntField(jmediarecorder, context);
    env->DeleteLocalRef(clazz);
    return sp<MediaRecorder>(p);
}


jint Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env,
                                                               jobject thiz,
                                                               jobject surface) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  if (!surface) {
    LOGE("GLEnvironment: Null Surface passed!");
    return -1;
  } else if (gl_env) {
    // Get the ANativeWindow
    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
    if (!window) {
      LOGE("GLEnvironment: Error creating window!");
      return -1;
    }

    NativeWindowHandle* winHandle = new NativeWindowHandle(window);
    int result = gl_env->FindSurfaceIdForWindow(winHandle);
    if (result == -1) {
      // Configure surface
      EGLConfig config;
      EGLint numConfigs = -1;
      EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_RECORDABLE_ANDROID, EGL_TRUE,
        EGL_NONE
      };



      eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
      if (numConfigs < 1) {
        LOGE("GLEnvironment: No suitable EGL configuration found for surface!");
        return -1;
      }

      // Create the EGL surface
      EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
                                                      config,
                                                      window,
                                                      NULL);

      if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
        LOGE("GLEnvironment: Error creating window surface!");
        return -1;
      }

      // Add it to GL Env and assign ID
      result = gl_env->AddWindowSurface(egl_surface, winHandle);
    } else {
      delete winHandle;
    }
    return result;
  }
  return -1;
}

jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceTexture(JNIEnv* env,
                                                                      jobject thiz,
                                                                      jobject surfaceTexture,
                                                                      jint width,
                                                                      jint height) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  if (!surfaceTexture) {
    LOGE("GLEnvironment: Null SurfaceTexture passed!");
    return -1;
  } else if (gl_env) {
    // Get the ANativeWindow
    ANativeWindow* window = ANativeWindow_fromSurfaceTexture(env, surfaceTexture);
    if (!window) {
      LOGE("GLEnvironment: Error creating window!");
      return -1;
    }

    // Don't care about format (will get overridden by SurfaceTexture
    // anyway), but do care about width and height
    // TODO: Probably, this should be just be
    // ANativeWindow_setBuffersDimensions. The pixel format is
    // set during the eglCreateWindowSurface
    ANativeWindow_setBuffersGeometry(window, width, height, 0);

    NativeWindowHandle* winHandle = new NativeWindowHandle(window);
    int result = gl_env->FindSurfaceIdForWindow(winHandle);
    if (result == -1) {
      // Configure surface
      EGLConfig config;
      EGLint numConfigs = -1;
      EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_RECORDABLE_ANDROID, EGL_TRUE,
        EGL_NONE
      };



      eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
      if (numConfigs < 1) {
        LOGE("GLEnvironment: No suitable EGL configuration found for surface texture!");
        return -1;
      }

      // Create the EGL surface
      EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
                                                      config,
                                                      window,
                                                      NULL);

      if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
        LOGE("GLEnvironment: Error creating window surface!");
        return -1;
      }

      // Add it to GL Env and assign ID
      result = gl_env->AddWindowSurface(egl_surface, winHandle);
    } else {
      delete winHandle;
    }
    return result;
  }
  return -1;
}

// nativeAddSurfaceFromMediaRecorder gets an EGLSurface
// using a MediaRecorder object.
// When Mediarecorder is used for recording GL Frames, it
// will have a reference to a Native Handle (a SurfaceTexureClient)
// which talks to the StageFrightRecorder in mediaserver via
// a binder interface.
jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder(
                                                      JNIEnv* env,
                                                      jobject thiz,
                                                      jobject jmediarecorder) {
    LOGV("GLEnv Jni: nativeAddSurfaceFromMediaRecorder");
    GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
    if (!gl_env) {
        return -1;
    }
    // get a native mediarecorder object from the java object
    sp<MediaRecorder> mr = getMediaRecorder(env, jmediarecorder);
    if (mr == NULL) {
        LOGE("GLEnvironment: Error- MediaRecorder could not be initialized!");
        return -1;
    }

    // Ask the mediarecorder to return a handle to a surfacemediasource
    // This will talk to the StageFrightRecorder via MediaRecorderClient
    // over binder calls
    sp<ISurfaceTexture> surfaceMS = mr->querySurfaceMediaSourceFromMediaServer();
    if (surfaceMS == NULL) {
      LOGE("GLEnvironment: Error- MediaRecorder returned a null \
              <ISurfaceTexture> handle.");
      return -1;
    }
    sp<SurfaceTextureClient> surfaceTC = new SurfaceTextureClient(surfaceMS);
    // Get the ANativeWindow
    sp<ANativeWindow> window = surfaceTC;


    if (window == NULL) {
      LOGE("GLEnvironment: Error creating window!");
      return -1;
    }
    window->incStrong((void*)ANativeWindow_acquire);

    // In case of encoding, no need to set the dimensions
    // on the buffers. The dimensions for the final encoding are set by
    // the consumer side.
    // The pixel format is dictated by the GL, and set during the
    // eglCreateWindowSurface

    NativeWindowHandle* winHandle = new NativeWindowHandle(window.get());
    int result = gl_env->FindSurfaceIdForWindow(winHandle);
    // If we find a surface with that window handle, just return that id
    if (result != -1) {
        delete winHandle;
        return result;
    }
    // If we do not find a surface with that window handle, create
    // one and assign to it the handle
    // Configure surface
    EGLConfig config;
    EGLint numConfigs = -1;
    EGLint configAttribs[] = {
          EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
          EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
          EGL_RED_SIZE, 8,
          EGL_GREEN_SIZE, 8,
          EGL_BLUE_SIZE, 8,
          EGL_RECORDABLE_ANDROID, EGL_TRUE,
          EGL_NONE
    };


    eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
    if (numConfigs < 1) {
      LOGE("GLEnvironment: No suitable EGL configuration found for surface texture!");
      delete winHandle;
      return -1;
    }

    // Create the EGL surface
    EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
                                                    config,
                                                    window.get(),
                                                    NULL);

    if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
      LOGE("GLEnvironment: Error creating window surface!");
      delete winHandle;
      return -1;
    }

    // Add it to GL Env and assign ID
    result = gl_env->AddWindowSurface(egl_surface, winHandle);
    return result;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env,
                                                                          jobject thiz,
                                                                          jint surfaceId) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->SwitchToSurfaceId(surfaceId) && gl_env->Activate()) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env,
                                                                        jobject thiz,
                                                                        jint surfaceId) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  return gl_env ? ToJBool(gl_env->ReleaseSurfaceId(surfaceId)) : JNI_FALSE;
}

jboolean Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env,
                                                                            jobject thiz,
                                                                            jlong timestamp) {
  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
  int64_t timestamp_native = timestamp;
  return gl_env ? ToJBool(gl_env->SetSurfaceTimestamp(timestamp_native)) : JNI_FALSE;
}
