use std::path::Path;

use jni::{JValue, jni_sig, jni_str, objects::JObject};

use crate::{
    Error, Result,
    runtime::{self, GhidraRuntime, LaunchOptions},
};

use super::support::{bridge_jar_path, find_bridge_class, jni_error, path_string};

pub fn start(mut options: LaunchOptions) -> Result<GhidraRuntime> {
    let bridge_jar = bridge_jar_path()?;
    options.extra_classpaths.push(bridge_jar);
    let runtime = runtime::start(options)
        .map_err(|source| Error::runtime("failed to start embedded Ghidra runtime", source))?;
    attach_bridge(&runtime)?;
    Ok(runtime)
}

pub fn started() -> bool {
    runtime::started()
}

pub(crate) fn attach_bridge(runtime: &GhidraRuntime) -> Result<()> {
    let bridge_jar = bridge_jar_path()?;
    add_bridge_jar(runtime, &bridge_jar)?;
    initialize_ghidra(runtime)?;
    Ok(())
}

fn initialize_ghidra(runtime: &GhidraRuntime) -> Result<()> {
    runtime.with_attached(|env| -> Result<()> {
        let install_dir = env.new_string(path_string(runtime.install_dir()))?;
        let install_dir = JObject::from(install_dir);
        let class = find_bridge_class(env)?;
        if let Err(error) = env.call_static_method(
            class,
            jni_str!("initialize"),
            jni_sig!("(Ljava/lang/String;)V"),
            &[JValue::Object(&install_dir)],
        ) {
            return Err(jni_error(env, "failed to initialize Ghidra", error));
        }
        Ok(())
    })
}

fn add_bridge_jar(runtime: &GhidraRuntime, bridge_jar: &Path) -> Result<()> {
    runtime.with_attached(|env| -> Result<()> {
        let loader = env
            .call_static_method(
                jni_str!("java/lang/ClassLoader"),
                jni_str!("getSystemClassLoader"),
                jni_sig!("()Ljava/lang/ClassLoader;"),
                &[],
            )?
            .l()?;
        let bridge_jar = env.new_string(path_string(bridge_jar))?;
        let bridge_jar = JObject::from(bridge_jar);
        env.call_method(
            &loader,
            jni_str!("addPath"),
            jni_sig!("(Ljava/lang/String;)Z"),
            &[JValue::Object(&bridge_jar)],
        )?;
        Ok(())
    })
}
