/*
 * Copyright (C) 2016 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.
 */

#include <jni.h>
#include <stdio.h>

#ifndef NATIVE_METHOD
#define NATIVE_METHOD(className, functionName, signature) \
    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
#endif
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))

#define GLUE4(a, b, c, d) a ## b ## c ## d
#define GLUE4_(a, b, c, d) GLUE4(a, b, c, d)

#define CLASS_NAME "benchmarks/MicroNative/java/NativeMethods"
#define CLASS_INFIX benchmarks_MicroNative_java_NativeMethods

#define NAME_NORMAL_JNI_METHOD(name) GLUE4_(Java_, CLASS_INFIX, _, name)
#define NAME_CRITICAL_JNI_METHOD(name) GLUE4_(JavaCritical_, CLASS_INFIX, _, name)

#define DEFINE_NORMAL_JNI_METHOD(ret, name) extern "C" JNIEXPORT ret JNICALL GLUE4_(Java_, CLASS_INFIX, _, name)
#define DEFINE_CRITICAL_JNI_METHOD(ret, name) extern "C" JNIEXPORT ret JNICALL GLUE4_(JavaCritical_, CLASS_INFIX, _, name)

static void NativeMethods_emptyJniStaticSynchronizedMethod0(JNIEnv*, jclass) { }
static void NativeMethods_emptyJniSynchronizedMethod0(JNIEnv*, jclass) { }

static JNINativeMethod gMethods_NormalOnly[] = {
  NATIVE_METHOD(NativeMethods, emptyJniStaticSynchronizedMethod0, "()V"),
  NATIVE_METHOD(NativeMethods, emptyJniSynchronizedMethod0, "()V"),
};

static void NativeMethods_emptyJniMethod0(JNIEnv*, jobject) { }
static void NativeMethods_emptyJniMethod6(JNIEnv*, jobject, int, int, int, int, int, int) { }
static void NativeMethods_emptyJniMethod6L(JNIEnv*, jobject, jobject, jarray, jarray, jobject,
                                           jarray, jarray) { }
static void NativeMethods_emptyJniStaticMethod6L(JNIEnv*, jclass, jobject, jarray, jarray, jobject,
                                                 jarray, jarray) { }

static void NativeMethods_emptyJniStaticMethod0(JNIEnv*, jclass) { }
static void NativeMethods_emptyJniStaticMethod6(JNIEnv*, jclass, int, int, int, int, int, int) { }

static JNINativeMethod gMethods[] = {
  NATIVE_METHOD(NativeMethods, emptyJniMethod0, "()V"),
  NATIVE_METHOD(NativeMethods, emptyJniMethod6, "(IIIIII)V"),
  NATIVE_METHOD(NativeMethods, emptyJniMethod6L, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod6L, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod0, "()V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod6, "(IIIIII)V"),
};

static void NativeMethods_emptyJniMethod0_Fast(JNIEnv*, jobject) { }
static void NativeMethods_emptyJniMethod6_Fast(JNIEnv*, jobject, int, int, int, int, int, int) { }
static void NativeMethods_emptyJniMethod6L_Fast(JNIEnv*, jobject, jobject, jarray, jarray, jobject,
                                                jarray, jarray) { }
static void NativeMethods_emptyJniStaticMethod6L_Fast(JNIEnv*, jclass, jobject, jarray, jarray,
                                                      jobject, jarray, jarray) { }

static void NativeMethods_emptyJniStaticMethod0_Fast(JNIEnv*, jclass) { }
static void NativeMethods_emptyJniStaticMethod6_Fast(JNIEnv*, jclass, int, int, int, int, int, int) { }

static JNINativeMethod gMethods_Fast[] = {
  NATIVE_METHOD(NativeMethods, emptyJniMethod0_Fast, "()V"),
  NATIVE_METHOD(NativeMethods, emptyJniMethod6_Fast, "(IIIIII)V"),
  NATIVE_METHOD(NativeMethods, emptyJniMethod6L_Fast, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod6L_Fast, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod0_Fast, "()V"),
  NATIVE_METHOD(NativeMethods, emptyJniStaticMethod6_Fast, "(IIIIII)V"),
};

// Have both a Java_ and a JavaCritical_ version of the same empty method.
// The runtime automatically selects the right one when doing a dlsym-based native lookup.
DEFINE_NORMAL_JNI_METHOD(void,   emptyJniStaticMethod0_1Critical)(JNIEnv*, jclass) { }
DEFINE_CRITICAL_JNI_METHOD(void, emptyJniStaticMethod0_1Critical)() { }
DEFINE_NORMAL_JNI_METHOD(void,   emptyJniStaticMethod6_1Critical)(JNIEnv*, jclass, int, int, int, int, int, int) { }
DEFINE_CRITICAL_JNI_METHOD(void, emptyJniStaticMethod6_1Critical)(int, int, int, int, int, int) { }

static JNINativeMethod gMethods_Critical[] = {
  // Don't use NATIVE_METHOD because the name is mangled differently.
  { "emptyJniStaticMethod0_Critical", "()V",
        reinterpret_cast<void*>(NAME_CRITICAL_JNI_METHOD(emptyJniStaticMethod0_1Critical)) },
  { "emptyJniStaticMethod6_Critical", "(IIIIII)V",
        reinterpret_cast<void*>(NAME_CRITICAL_JNI_METHOD(emptyJniStaticMethod6_1Critical)) }
};

void jniRegisterNativeMethods(JNIEnv* env,
                              const char* className,
                              const JNINativeMethod* methods,
                              int numMethods) {
    jclass c = env->FindClass(className);
    if (c == nullptr) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp,
                     "Native registration unable to find class '%s'; aborting...",
                     className) == -1) {
            // Allocation failed, print default warning.
            msg = "Native registration unable to find class; aborting...";
        } else {
            msg = tmp;
        }
        env->FatalError(msg);
    }

    if (env->RegisterNatives(c, methods, numMethods) < 0) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
            // Allocation failed, print default warning.
            msg = "RegisterNatives failed; aborting...";
        } else {
            msg = tmp;
        }
        env->FatalError(msg);
    }
}

void register_micro_native_methods(JNIEnv* env) {
  jniRegisterNativeMethods(env, CLASS_NAME, gMethods_NormalOnly, NELEM(gMethods_NormalOnly));
  jniRegisterNativeMethods(env, CLASS_NAME, gMethods, NELEM(gMethods));
  jniRegisterNativeMethods(env, CLASS_NAME, gMethods_Fast, NELEM(gMethods_Fast));

  if (env->FindClass("dalvik/annotation/optimization/CriticalNative") != nullptr) {
    // Only register them explicitly if the annotation is present.
    jniRegisterNativeMethods(env, CLASS_NAME, gMethods_Critical, NELEM(gMethods_Critical));
  } else {
    if (env->ExceptionCheck()) {
      // It will throw NoClassDefFoundError
      env->ExceptionClear();
    }
  }
  // else let them be registered implicitly.
}