use std::sync::{Arc, Once, ONCE_INIT};

use error_chain::ChainedError;
use jni::{AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM};
use jni::errors::Result;


pub fn jvm() -> &'static Arc<JavaVM> {
    static mut JVM: Option<Arc<JavaVM>> = None;
    static INIT: Once = ONCE_INIT;

    INIT.call_once(|| {
        let jvm_args = InitArgsBuilder::new()
            .version(JNIVersion::V8)
            .option("-Xcheck:jni")
            .option("-Xdebug")
            .build()
            .unwrap_or_else(|e| panic!("{}", e.display_chain().to_string()));

        let jvm = JavaVM::new(jvm_args)
            .unwrap_or_else(|e| panic!("{}", e.display_chain().to_string()));

        unsafe {
            JVM = Some(Arc::new(jvm));
        }
    });

    unsafe { JVM.as_ref().unwrap() }
}

pub fn attach_current_thread() -> AttachGuard<'static> {
    jvm().attach_current_thread().expect("failed to attach jvm thread")
}

pub fn print_exception(env: &JNIEnv) {
    let exception_occurred = env.exception_check()
        .unwrap_or_else(|e| panic!("{:?}", e));
    if exception_occurred {
        env.exception_describe()
            .unwrap_or_else(|e| panic!("{:?}", e));
    }
}

#[allow(dead_code)]
pub fn unwrap<T>(env: &JNIEnv, res: Result<T>) -> T {
    res.unwrap_or_else(|e| {
        print_exception(&env);
        panic!("{}", e.display_chain().to_string());
    })
}
