// Copyright (c) 2019-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by caikelun on 2019-03-07.

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <jni.h>
#include <sys/types.h>
#include <sys/eventfd.h>
#include <android/log.h>
#include "xcc_errno.h"
#include "xcc_util.h"
#include "xc_jni.h"
#include "xc_common.h"
#include "xc_crash.h"
#include "xc_trace.h"
#include "xc_util.h"
#include "xc_test.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"

static int xc_jni_inited = 0;

static jint xc_jni_init(JNIEnv       *env,
                        jobject       thiz,
                        jint          api_level,
                        jstring       os_version,
                        jstring       abi_list,
                        jstring       manufacturer,
                        jstring       brand,
                        jstring       model,
                        jstring       build_fingerprint,
                        jstring       app_id,
                        jstring       app_version,
                        jstring       app_lib_dir,
                        jstring       log_dir,
                        jboolean      crash_enable,
                        jboolean      crash_rethrow,
                        jint          crash_logcat_system_lines,
                        jint          crash_logcat_events_lines,
                        jint          crash_logcat_main_lines,
                        jboolean      crash_dump_elf_hash,
                        jboolean      crash_dump_map,
                        jboolean      crash_dump_fds,
                        jboolean      crash_dump_network_info,
                        jboolean      crash_dump_all_threads,
                        jint          crash_dump_all_threads_count_max,
                        jobjectArray  crash_dump_all_threads_whitelist,
                        jboolean      trace_enable,
                        jboolean      trace_rethrow,
                        jint          trace_logcat_system_lines,
                        jint          trace_logcat_events_lines,
                        jint          trace_logcat_main_lines,
                        jboolean      trace_dump_fds,
                        jboolean      trace_dump_network_info)
{
    int              r_crash                                = XCC_ERRNO_JNI;
    int              r_trace                                = XCC_ERRNO_JNI;
    
    const char      *c_os_version                           = NULL;
    const char      *c_abi_list                             = NULL;
    const char      *c_manufacturer                         = NULL;
    const char      *c_brand                                = NULL;
    const char      *c_model                                = NULL;
    const char      *c_build_fingerprint                    = NULL;
    const char      *c_app_id                               = NULL;
    const char      *c_app_version                          = NULL;
    const char      *c_app_lib_dir                          = NULL;
    const char      *c_log_dir                              = NULL;
    
    const char     **c_crash_dump_all_threads_whitelist     = NULL;
    size_t           c_crash_dump_all_threads_whitelist_len = 0;
    
    size_t           len, i;
    jstring          tmp_str;
    const char      *tmp_c_str;

    (void)thiz;

    //only once
    if(xc_jni_inited) return XCC_ERRNO_JNI;
    xc_jni_inited = 1;

    if(!env || !(*env) || (!crash_enable && ! trace_enable) || api_level < 0 ||
       !os_version || !abi_list || !manufacturer || !brand || !model || !build_fingerprint ||
       !app_id || !app_version || !app_lib_dir || !log_dir ||
       crash_logcat_system_lines < 0 || crash_logcat_events_lines < 0 || crash_logcat_main_lines < 0 ||
       crash_dump_all_threads_count_max < 0 ||
       trace_logcat_system_lines < 0 || trace_logcat_events_lines < 0 || trace_logcat_main_lines < 0)
        return XCC_ERRNO_INVAL;

    if(NULL == (c_os_version        = (*env)->GetStringUTFChars(env, os_version,        0))) goto clean;
    if(NULL == (c_abi_list          = (*env)->GetStringUTFChars(env, abi_list,          0))) goto clean;
    if(NULL == (c_manufacturer      = (*env)->GetStringUTFChars(env, manufacturer,      0))) goto clean;
    if(NULL == (c_brand             = (*env)->GetStringUTFChars(env, brand,             0))) goto clean;
    if(NULL == (c_model             = (*env)->GetStringUTFChars(env, model,             0))) goto clean;
    if(NULL == (c_build_fingerprint = (*env)->GetStringUTFChars(env, build_fingerprint, 0))) goto clean;
    if(NULL == (c_app_id            = (*env)->GetStringUTFChars(env, app_id,            0))) goto clean;
    if(NULL == (c_app_version       = (*env)->GetStringUTFChars(env, app_version,       0))) goto clean;
    if(NULL == (c_app_lib_dir       = (*env)->GetStringUTFChars(env, app_lib_dir,       0))) goto clean;
    if(NULL == (c_log_dir           = (*env)->GetStringUTFChars(env, log_dir,           0))) goto clean;

    //common init
    if(0 != xc_common_init((int)api_level, 
                           c_os_version,
                           c_abi_list,
                           c_manufacturer,
                           c_brand,
                           c_model,
                           c_build_fingerprint,
                           c_app_id,
                           c_app_version,
                           c_app_lib_dir,
                           c_log_dir)) goto clean;
    
    r_crash = 0;
    r_trace = 0;
    
    if(crash_enable)
    {
        r_crash = XCC_ERRNO_JNI;
        
        if(crash_dump_all_threads_whitelist)
        {
            len = (size_t)(*env)->GetArrayLength(env, crash_dump_all_threads_whitelist);
            if(len > 0)
            {
                if(NULL != (c_crash_dump_all_threads_whitelist = calloc(len, sizeof(char *))))
                {
                    c_crash_dump_all_threads_whitelist_len = len;
                    for(i = 0; i < len; i++)
                    {
                        tmp_str = (jstring)((*env)->GetObjectArrayElement(env, crash_dump_all_threads_whitelist, (jsize)i));
                        c_crash_dump_all_threads_whitelist[i] = (tmp_str ? (*env)->GetStringUTFChars(env, tmp_str, 0) : NULL);
                    }
                }
            }
        }

        //crash init
        r_crash = xc_crash_init(env,
                                crash_rethrow ? 1 : 0,
                                (unsigned int)crash_logcat_system_lines,
                                (unsigned int)crash_logcat_events_lines,
                                (unsigned int)crash_logcat_main_lines,
                                crash_dump_elf_hash ? 1 : 0,
                                crash_dump_map ? 1 : 0,
                                crash_dump_fds ? 1 : 0,
                                crash_dump_network_info ? 1 : 0,
                                crash_dump_all_threads ? 1 : 0,
                                (unsigned int)crash_dump_all_threads_count_max,
                                c_crash_dump_all_threads_whitelist,
                                c_crash_dump_all_threads_whitelist_len);
    }
    
    if(trace_enable)
    {
        //trace init
        r_trace = xc_trace_init(env,
                            trace_rethrow ? 1 : 0,
                            (unsigned int)trace_logcat_system_lines,
                            (unsigned int)trace_logcat_events_lines,
                            (unsigned int)trace_logcat_main_lines,
                            trace_dump_fds ? 1 : 0,
                            trace_dump_network_info ? 1 : 0);
    }
    
 clean:
    if(os_version        && c_os_version)        (*env)->ReleaseStringUTFChars(env, os_version,        c_os_version);
    if(abi_list          && c_abi_list)          (*env)->ReleaseStringUTFChars(env, abi_list,          c_abi_list);
    if(manufacturer      && c_manufacturer)      (*env)->ReleaseStringUTFChars(env, manufacturer,      c_manufacturer);
    if(brand             && c_brand)             (*env)->ReleaseStringUTFChars(env, brand,             c_brand);
    if(model             && c_model)             (*env)->ReleaseStringUTFChars(env, model,             c_model);
    if(build_fingerprint && c_build_fingerprint) (*env)->ReleaseStringUTFChars(env, build_fingerprint, c_build_fingerprint);
    if(app_id            && c_app_id)            (*env)->ReleaseStringUTFChars(env, app_id,            c_app_id);
    if(app_version       && c_app_version)       (*env)->ReleaseStringUTFChars(env, app_version,       c_app_version);
    if(app_lib_dir       && c_app_lib_dir)       (*env)->ReleaseStringUTFChars(env, app_lib_dir,       c_app_lib_dir);
    if(log_dir           && c_log_dir)           (*env)->ReleaseStringUTFChars(env, log_dir,           c_log_dir);

    if(crash_dump_all_threads_whitelist && NULL != c_crash_dump_all_threads_whitelist)
    {
        for(i = 0; i < c_crash_dump_all_threads_whitelist_len; i++)
        {
            tmp_str = (jstring)((*env)->GetObjectArrayElement(env, crash_dump_all_threads_whitelist, (jsize)i));
            tmp_c_str = c_crash_dump_all_threads_whitelist[i];
            if(tmp_str && NULL != tmp_c_str) (*env)->ReleaseStringUTFChars(env, tmp_str, tmp_c_str);
        }
        free(c_crash_dump_all_threads_whitelist);
    }
    
    return (0 == r_crash && 0 == r_trace) ? 0 : XCC_ERRNO_JNI;
}

static void xc_jni_notify_java_crashed(JNIEnv *env, jobject thiz)
{
    (void)env;
    (void)thiz;

    xc_common_java_crashed = 1;
}

static void xc_jni_test_crash(JNIEnv *env, jobject thiz, jint run_in_new_thread)
{
    (void)env;
    (void)thiz;

    xc_test_crash(run_in_new_thread);
}

static void xc_jni_dump_trace(JNIEnv *env, jobject thiz) {
    (void)env;
    (void)thiz;

    xc_trace_handler_dump_trace();
}

static JNINativeMethod xc_jni_methods[] = {
    {
        "nativeInit",
        "("
        "I"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Ljava/lang/String;"
        "Z"
        "Z"
        "I"
        "I"
        "I"
        "Z"
        "Z"
        "Z"
        "Z"
        "Z"
        "I"
        "[Ljava/lang/String;"
        "Z"
        "Z"
        "I"
        "I"
        "I"
        "Z"
        "Z"
        ")"
        "I",
        (void *)xc_jni_init
    },
    {
        "nativeNotifyJavaCrashed",
        "("
        ")"
        "V",
        (void *)xc_jni_notify_java_crashed
    },
    {
        "nativeTestCrash",
        "("
        "I"
        ")"
        "V",
        (void *)xc_jni_test_crash
    },
    {
        "nativeDumpTrace",
        "("
        ")"
        "V",
        (void *)xc_jni_dump_trace
    }
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env;
    jclass  cls;

    (void)reserved;

    if(NULL == vm) return -1;
    
    //register JNI methods
    if(JNI_OK != (*vm)->GetEnv(vm, (void**)&env, XC_JNI_VERSION)) return -1;
    if(NULL == env || NULL == *env) return -1;
    if(NULL == (cls = (*env)->FindClass(env, XC_JNI_CLASS_NAME))) return -1;
    if((*env)->RegisterNatives(env, cls, xc_jni_methods, sizeof(xc_jni_methods) / sizeof(xc_jni_methods[0]))) return -1;

    xc_common_set_vm(vm, env, cls);

    return XC_JNI_VERSION;
}

#pragma clang diagnostic pop
