use std::{
    env,
    fs::File,
    io::{BufRead, BufReader, Write},
    path::Path,
};

use quote::ToTokens;
use syn::{parse_quote, visit::Visit, visit_mut::VisitMut};

struct FilterSwigAttrs;

impl VisitMut for FilterSwigAttrs {
    fn visit_attribute_mut(&mut self, i: &mut syn::Attribute) {
        if i.path
            .clone()
            .into_token_stream()
            .to_string()
            .starts_with("swig_")
        {
            *i = parse_quote! { #[doc = "swig_ replace"] };
        }
    }
}

mod file_cache {
    include!("src/file_cache.rs");
}

mod jni_find_cache {
    include!("src/java_jni/find_cache.rs");
}

fn 
() {
    let out_dir = env::var("OUT_DIR").unwrap();

    for include_path in &[
        Path::new("src/java_jni/jni-include.rs"),
        Path::new("src/cpp/cpp-include.rs"),
    ] {
        let src_cnt_tail = std::fs::read_to_string(include_path)
            .unwrap_or_else(|err| panic!("Error during read {}: {}", include_path.display(), err));
        let mut src_cnt = r#"
        macro_rules! foreign_typemap {
            ($($tree:tt)*) => {};
        }
"#
        .to_string();

        src_cnt.push_str(&src_cnt_tail);

        let mut file = syn::parse_file(&src_cnt)
            .unwrap_or_else(|err| panic!("Error during parse {}: {}", include_path.display(), err));

        let mut filter_swig_attrs = FilterSwigAttrs;
        filter_swig_attrs.visit_file_mut(&mut file);

        let mut jni_cache_macro_cache = jni_find_cache::JniCacheMacroCalls::default();
        let mut visitor = jni_find_cache::JniCacheMacroCallsVisitor {
            inner: &mut jni_cache_macro_cache,
            errors: vec![],
        };
        visitor.visit_file(&file);
        if !visitor.errors.is_empty() {
            panic!("jni cache macros visiting failed: {}", visitor.errors[0]);
        }
        let mut jni_global_vars = jni_cache_macro_cache.global_vars();
        file.items.append(&mut jni_global_vars);

        let out_path = Path::new(&out_dir).join(include_path.file_name().expect("No file name"));
        let mut cache =
            file_cache::FileWriteCache::new(&out_path, &mut file_cache::NoNeedFsOpsRegistration);
        let write_err_msg = format!("Error during write to file {}", out_path.display());
        write!(&mut cache, "{}", file.into_token_stream().to_string()).expect(&write_err_msg);
        cache.update_file_if_necessary().expect(&write_err_msg);
        println!("cargo:rerun-if-changed={}", include_path.display());
    }
    println!("cargo:rerun-if-changed=tests/test_includes_syntax.rs");

    let exp_tests_list_path = Path::new("tests").join("expectations").join("tests.list");
    let expectation_tests = File::open(&exp_tests_list_path)
        .unwrap_or_else(|err| panic!("Can not open {}: {}", exp_tests_list_path.display(), err));
    let expectation_tests = BufReader::new(&expectation_tests);
    let exp_code_path = Path::new(&out_dir).join("test_expectations.rs");
    let mut exp_code =
        file_cache::FileWriteCache::new(&exp_code_path, &mut file_cache::NoNeedFsOpsRegistration);
    for name in expectation_tests.lines() {
        let name = name.unwrap_or_else(|err| {
            panic!("Can not read {}: {}", exp_tests_list_path.display(), err)
        });
        write!(
            &mut exp_code,
            r##"
#[test]
fn test_expectation_{test_name}() {{
   let _ = env_logger::try_init();

   let test_case = Path::new("tests").join("expectations").join("{test_name}.rs");
   let base_name = test_case.file_stem().expect("name without extenstion");
   let test_name = base_name.to_string_lossy();

   let mut test_something = false;
   for lang in &[ForeignLang::Cpp, ForeignLang::Java] {{
       if check_expectation(&test_name, &test_case, *lang) {{
           test_something = true;
       }}
   }}
   assert!(test_something, "empty test");
}}
"##,
            test_name = name,
        )
        .unwrap();
    }
    exp_code
        .update_file_if_necessary()
        .unwrap_or_else(|err| panic!("Can not write to {}: {}", exp_code_path.display(), err));
    println!("cargo:rerun-if-changed={}", exp_tests_list_path.display());
}

main