/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * 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 <android/log.h>
#include <dlfcn.h>
#include <prthread.h>

#include "mozilla/DebugOnly.h"
#include "mozilla/Assertions.h"
#include "mozilla/SyncRunnable.h"
#include "nsThreadUtils.h"
#include "AndroidBridge.h"

extern "C" {
  jclass __jsjni_GetGlobalClassRef(const char *className);
}

class GetGlobalClassRefRunnable : public mozilla::Runnable {
  public:
    GetGlobalClassRefRunnable(const char *className, jclass *foundClass) :
        mClassName(className), mResult(foundClass) {}
    NS_IMETHOD Run() override {
        *mResult = __jsjni_GetGlobalClassRef(mClassName);
        return NS_OK;
    }
  private:
    const char *mClassName;
    jclass *mResult;
};

extern "C" {
  __attribute__ ((visibility("default")))
  jclass
  jsjni_FindClass(const char *className) {
    // FindClass outside the main thread will run into problems due
    // to missing the classpath
    MOZ_ASSERT(NS_IsMainThread());
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
    return env->FindClass(className);
  }

  jclass
  __jsjni_GetGlobalClassRef(const char *className) {
    // root class globally
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
    jclass globalRef = static_cast<jclass>(env->NewGlobalRef(env->FindClass(className)));
    if (!globalRef)
      return nullptr;

    // return the newly create global reference
    return globalRef;
  }

  __attribute__ ((visibility("default")))
  jclass
  jsjni_GetGlobalClassRef(const char *className) {
    if (NS_IsMainThread()) {
      return __jsjni_GetGlobalClassRef(className);
    }

    nsCOMPtr<nsIThread> mainThread;
    mozilla::DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(mainThread));
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    jclass foundClass;
    nsCOMPtr<nsIRunnable> runnable_ref(new GetGlobalClassRefRunnable(className,
                                                                     &foundClass));
    RefPtr<mozilla::SyncRunnable> sr = new mozilla::SyncRunnable(runnable_ref);
    sr->DispatchToThread(mainThread);
    if (!foundClass)
      return nullptr;

    return foundClass;
  }

  __attribute__ ((visibility("default")))
  jmethodID
  jsjni_GetStaticMethodID(jclass methodClass,
                          const char *methodName,
                          const char *signature) {
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
    return env->GetStaticMethodID(methodClass, methodName, signature);
  }

  __attribute__ ((visibility("default")))
  bool
  jsjni_ExceptionCheck() {
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
    return env->ExceptionCheck();
  }

  __attribute__ ((visibility("default")))
  void
  jsjni_CallStaticVoidMethodA(jclass cls,
                              jmethodID method,
                              jvalue *values) {
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();

    mozilla::AutoLocalJNIFrame jniFrame(env);
    env->CallStaticVoidMethodA(cls, method, values);
  }

  __attribute__ ((visibility("default")))
  int
  jsjni_CallStaticIntMethodA(jclass cls,
                             jmethodID method,
                             jvalue *values) {
    JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();

    mozilla::AutoLocalJNIFrame jniFrame(env);
    return env->CallStaticIntMethodA(cls, method, values);
  }

  __attribute__ ((visibility("default")))
  jobject jsjni_GetGlobalContextRef() {
    return mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
  }

  __attribute__ ((visibility("default")))
  JavaVM* jsjni_GetVM() {
    JavaVM* jvm;
    JNIEnv* const env = mozilla::jni::GetGeckoThreadEnv();
    MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
    return jvm;
  }

  __attribute__ ((visibility("default")))
  JNIEnv* jsjni_GetJNIForThread() {
    return mozilla::jni::GetEnvForThread();
  }

  // For compatibility with JNI.jsm; some addons bundle their own JNI.jsm,
  // so we cannot just change the function name used in JNI.jsm.
  __attribute__ ((visibility("default")))
  JNIEnv* GetJNIForThread() {
    return mozilla::jni::GetEnvForThread();
  }
}