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

use crate::{DecompileOptions, Result, runtime::GhidraRuntime};

use super::{
    lifecycle::attach_bridge,
    support::{
        JavaHandle, find_bridge_class, java_int, java_monitor_timeout_seconds, java_string,
        jni_error,
    },
};

pub(crate) fn open_decompiler(runtime: &GhidraRuntime, program: JavaHandle) -> Result<JavaHandle> {
    attach_bridge(runtime)?;
    runtime.with_attached(|env| -> Result<JavaHandle> {
        let class = find_bridge_class(env)?;
        let value = match env.call_static_method(
            class,
            jni_str!("openDecompiler"),
            jni_sig!("(J)J"),
            &[JValue::Long(program)],
        ) {
            Ok(value) => value,
            Err(error) => return Err(jni_error(env, "failed to open Ghidra decompiler", error)),
        };
        Ok(value.j()?)
    })
}

pub(crate) fn close_decompiler(runtime: &GhidraRuntime, decompiler: JavaHandle) -> Result<()> {
    attach_bridge(runtime)?;
    runtime.with_attached(|env| -> Result<()> {
        let class = find_bridge_class(env)?;
        match env.call_static_method(
            class,
            jni_str!("closeDecompiler"),
            jni_sig!("(J)V"),
            &[JValue::Long(decompiler)],
        ) {
            Ok(_) => Ok(()),
            Err(error) => Err(jni_error(env, "failed to close Ghidra decompiler", error)),
        }
    })
}

pub(crate) fn decompile_function(
    runtime: &GhidraRuntime,
    decompiler: JavaHandle,
    function_address: &str,
    options: DecompileOptions,
) -> Result<String> {
    attach_bridge(runtime)?;
    let timeout = java_int(options.timeout_seconds, "decompile timeout")?;
    let monitor_timeout_seconds = java_monitor_timeout_seconds(&options.monitor)?;
    runtime.with_attached(|env| -> Result<String> {
        let function_address = env.new_string(function_address)?;
        let class = find_bridge_class(env)?;
        let value = match env.call_static_method(
            class,
            jni_str!("decompileFunction"),
            jni_sig!("(JLjava/lang/String;II)Ljava/lang/String;"),
            &[
                JValue::Long(decompiler),
                JValue::Object(&JObject::from(function_address)),
                JValue::Int(timeout),
                JValue::Int(monitor_timeout_seconds),
            ],
        ) {
            Ok(value) => value,
            Err(error) => return Err(jni_error(env, "failed to decompile Ghidra function", error)),
        };
        java_string(env, value.l()?)
    })
}

pub(crate) fn decompiler_metadata(
    runtime: &GhidraRuntime,
    decompiler: JavaHandle,
) -> Result<String> {
    attach_bridge(runtime)?;
    runtime.with_attached(|env| -> Result<String> {
        let class = find_bridge_class(env)?;
        let value = match env.call_static_method(
            class,
            jni_str!("decompilerMetadata"),
            jni_sig!("(J)Ljava/lang/String;"),
            &[JValue::Long(decompiler)],
        ) {
            Ok(value) => value,
            Err(error) => {
                return Err(jni_error(
                    env,
                    "failed to extract Ghidra decompiler metadata",
                    error,
                ));
            }
        };
        java_string(env, value.l()?)
    })
}
