// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
// https://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
// modified, or distributed except according to those terms. Please review the Licences for the
// specific language governing permissions and limitations relating to use of the SAFE Network
// Software.

//! Build script for generating C header files from FFI modules.

fn main() {
    #[cfg(feature = "bindings")]
    bindings::main();
}

#[cfg(feature = "bindings")]
mod bindings {
    use jni::signature::{JavaType, Primitive};
    use safe_bindgen::{Bindgen, FilterMode, LangC, LangCSharp, LangJava};
    use safe_nd::XOR_NAME_LEN;
    use std::collections::HashMap;
    use std::env;
    use std::path::Path;
    use threshold_crypto::PK_SIZE;
    use unwrap::unwrap;

    const BSD_MIT_LICENSE: &str = "// Copyright 2019 MaidSafe.net limited.\n\
         //\n\
         // This SAFE Network Software is licensed to you under the MIT license\n\
         // <LICENSE-MIT or https://opensource.org/licenses/MIT> or the Modified\n\
         // BSD license <LICENSE-BSD or https://opensource.org/licenses/BSD-3-Clause>,\n\
         // at your option. This file may not be copied, modified, or distributed\n\
         // except according to those terms. Please review the Licences for the\n\
         // specific language governing permissions and limitations relating to use\n\
         // of the SAFE Network Software.";

    // As currently we have no easy way to pull code for external dependencies for bindgen parsing,
    // we use this workaround to cater for structures defined in the `ffi_utils` package. At
    // present, it's only `FfiResult`.
    const FFI_UTILS_CODE: &str =
        "#[repr(C)] pub struct FfiResult { error_code: i32, description: *const c_char }";

    pub fn main() {
        if env::var("CARGO_FEATURE_BINDINGS").is_err() {
            return;
        }

        println!("Generating C bindings");
        gen_bindings_c();
        println!("Generating C# bindings");
        gen_bindings_csharp();
        println!("Generating Java bindings");
        gen_bindings_java();
    }

    fn gen_bindings_c() {
        let target_dir = Path::new("../bindings/c/safe_app");
        let mut outputs = HashMap::new();

        let mut bindgen = unwrap!(Bindgen::new());
        let mut lang = LangC::new();

        lang.set_lib_name("ffi_utils");
        bindgen.source_code("ffi_utils/src/lib.rs", FFI_UTILS_CODE);
        unwrap!(bindgen.compile(&mut lang, &mut outputs, false));

        lang.set_lib_name("safe_core");
        bindgen.source_file("../safe_core/src/lib.rs");
        unwrap!(bindgen.compile(&mut lang, &mut outputs, false));

        lang.add_custom_code("typedef void* App;\n");
        lang.set_lib_name(unwrap!(env::var("CARGO_PKG_NAME")));
        bindgen.source_file("../safe_app/src/lib.rs");
        unwrap!(bindgen.compile(&mut lang, &mut outputs, true));

        add_license_headers(&mut outputs);
        unwrap!(bindgen.write_outputs(target_dir, &outputs));
    }

    fn gen_bindings_java() {
        let target_dir = Path::new("../bindings/java/safe_app");

        let mut type_map = HashMap::new();
        type_map.insert(
            "XorNameArray",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "SignSecretKey",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "SignPublicKey",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "SymSecretKey",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "SymNonce",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "AsymPublicKey",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "AsymSecretKey",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert(
            "AsymNonce",
            JavaType::Array(Box::new(JavaType::Primitive(Primitive::Byte))),
        );
        type_map.insert("CipherOptHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("EncryptPubKeyHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("EncryptSecKeyHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("MDataEntriesHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert(
            "MDataEntryActionsHandle",
            JavaType::Primitive(Primitive::Long),
        );
        type_map.insert(
            "MDataPermissionsHandle",
            JavaType::Primitive(Primitive::Long),
        );
        type_map.insert(
            "SelfEncryptorReaderHandle",
            JavaType::Primitive(Primitive::Long),
        );
        type_map.insert(
            "SelfEncryptorWriterHandle",
            JavaType::Primitive(Primitive::Long),
        );
        type_map.insert("SEReaderHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("SEWriterHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("SignPubKeyHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("SignSecKeyHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("FileContextHandle", JavaType::Primitive(Primitive::Long));
        type_map.insert("App", JavaType::Primitive(Primitive::Long));
        type_map.insert("Authenticator", JavaType::Primitive(Primitive::Long));

        let mut bindgen = unwrap!(Bindgen::new());
        let mut lang = LangJava::new(type_map);

        lang.filter("app_registered");
        lang.filter("app_unregistered");

        lang.set_namespace("net.maidsafe.safe_app");
        lang.set_model_namespace("net.maidsafe.safe_app");

        let mut outputs = HashMap::new();

        bindgen.source_file("../safe_core/src/lib.rs");
        lang.set_lib_name("safe_core");
        unwrap!(bindgen.compile(&mut lang, &mut outputs, false));

        bindgen.source_code("ffi_utils/src/lib.rs", FFI_UTILS_CODE);
        lang.set_lib_name("ffi_utils");
        unwrap!(bindgen.compile(&mut lang, &mut outputs, false));

        bindgen.source_file("src/lib.rs");
        lang.set_lib_name(unwrap!(env::var("CARGO_PKG_NAME")));
        unwrap!(bindgen.compile(&mut lang, &mut outputs, true));

        add_license_headers(&mut outputs);
        unwrap!(bindgen.write_outputs(target_dir, &outputs));
    }

    fn gen_bindings_csharp() {
        let target_dir = Path::new("../bindings/csharp/safe_app");
        let test_idents = ["test_create_app", "test_create_app_with_access"];

        let mut bindgen = unwrap!(Bindgen::new());
        let mut lang = LangCSharp::new();

        lang.set_lib_name(unwrap!(env::var("CARGO_PKG_NAME")));

        lang.set_interface_section(
            "SafeApp.Utilities/IAppBindings.cs",
            "SafeApp.Utilities",
            "IAppBindings",
        );
        lang.set_functions_section(
            "SafeApp.AppBindings/AppBindings.cs",
            "SafeApp.AppBindings",
            "AppBindings",
        );
        lang.set_consts_section(
            "SafeApp.Utilities/AppConstants.cs",
            "SafeApp.Utilities",
            "AppConstants",
        );
        lang.set_types_section("SafeApp.Utilities/AppTypes.cs", "SafeApp.Utilities");
        lang.set_utils_section(
            "SafeApp.Utilities/BindingUtils.cs",
            "SafeApp.Utilities",
            "BindingUtils",
        );

        lang.add_const("ulong", "ASYM_PUBLIC_KEY_LEN", PK_SIZE);
        lang.add_const("ulong", "XOR_NAME_LEN", XOR_NAME_LEN);
        lang.add_opaque_type("App");

        lang.reset_filter(FilterMode::Blacklist);
        for &ident in &test_idents {
            lang.filter(ident);
        }

        bindgen.source_file("../safe_core/src/lib.rs");
        bindgen.compile_or_panic(&mut lang, &mut HashMap::new(), false);

        let mut outputs = HashMap::new();
        bindgen.source_file("src/lib.rs");
        bindgen.compile_or_panic(&mut lang, &mut outputs, true);
        apply_patches(&mut outputs);
        bindgen.write_outputs_or_panic(target_dir, &outputs);

        // Testing utilities.
        lang.set_interface_section(
            "SafeApp.MockAuthBindings/IMockAuthBindings.cs",
            "SafeApp.MockAuthBindings",
            "IMockAuthBindings",
        );
        lang.set_functions_section(
            "SafeApp.MockAuthBindings/MockAuthBindings.cs",
            "SafeApp.MockAuthBindings",
            "MockAuthBindings",
        );

        lang.set_consts_enabled(false);
        lang.set_types_enabled(false);
        lang.set_utils_enabled(false);
        lang.add_opaque_type("App");

        lang.reset_filter(FilterMode::Whitelist);
        for &ident in &test_idents {
            lang.filter(ident);
        }

        outputs.clear();
        bindgen.compile_or_panic(&mut lang, &mut outputs, true);
        apply_patches_testing(&mut outputs);
        add_license_headers(&mut outputs);
        bindgen.write_outputs_or_panic(target_dir, &outputs);

        // Hand-written code.
        unwrap!(ffi_utils::bindgen_utils::copy_files(
            "resources",
            target_dir,
            ".cs",
        ));
    }

    fn add_license_headers(outputs: &mut HashMap<String, String>) {
        for content in outputs.values_mut() {
            add_license_header(content);
        }
    }

    fn add_license_header(content: &mut String) {
        *content = format!("{}\n{}", BSD_MIT_LICENSE, content);
    }

    fn apply_patches(outputs: &mut HashMap<String, String>) {
        {
            let content = fetch_mut(outputs, "SafeApp.AppBindings/AppBindings.cs");
            insert_using_utilities(content);
            insert_using_obj_c_runtime(content);
            insert_guard(content);
            insert_resharper_disable_inconsistent_naming(content);
        }

        insert_internals_visible_to(fetch_mut(outputs, "SafeApp.Utilities/AppTypes.cs"));

        for content in outputs.values_mut() {
            fix_names(content);
        }
    }

    fn apply_patches_testing(outputs: &mut HashMap<String, String>) {
        insert_using_utilities(fetch_mut(
            outputs,
            "SafeApp.MockAuthBindings/MockAuthBindings.cs",
        ));

        insert_using_utilities(fetch_mut(
            outputs,
            "SafeApp.MockAuthBindings/IMockAuthBindings.cs",
        ));

        for content in outputs.values_mut() {
            fix_names(content);
        }
    }

    fn insert_guard(content: &mut String) {
        content.insert_str(0, "#if !NETSTANDARD1_2 || __DESKTOP__\n");
        content.push_str("#endif\n");
    }

    fn insert_using_utilities(content: &mut String) {
        content.insert_str(0, "using SafeApp.Utilities;\n");
    }

    fn insert_using_obj_c_runtime(content: &mut String) {
        content.insert_str(0, "#if __IOS__\nusing ObjCRuntime;\n#endif\n");
    }

    fn insert_internals_visible_to(content: &mut String) {
        content.insert_str(0, "using System.Runtime.CompilerServices;\n");

        // HACK: The [assembly: ...] annotation must go after all using declaration.
        *content = content.replace(
            "namespace SafeApp.Utilities",
            "[assembly: InternalsVisibleTo(\"SafeApp.AppBindings\")]\n\
             [assembly: InternalsVisibleTo(\"SafeApp.MockAuthBindings\")]\n\n\
             namespace SafeApp.Utilities",
        );
    }

    fn insert_resharper_disable_inconsistent_naming(content: &mut String) {
        content.insert_str(0, "// ReSharper disable InconsistentNaming\n");
    }

    fn fix_names(content: &mut String) {
        *content = content.replace("Idata", "IData").replace("Mdata", "MData");
    }

    fn fetch_mut<'a>(outputs: &'a mut HashMap<String, String>, key: &str) -> &'a mut String {
        unwrap!(outputs.get_mut(key), "key {:?} not found in outputs", key)
    }
}
