use bindgen::callbacks::TypeKind;
use bindgen::{
    builder, AliasVariation, Builder, CodegenConfig, EnumVariation,
    FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle,
    RegexSet, RustTarget, DEFAULT_ANON_FIELDS_PREFIX, RUST_TARGET_STRINGS,
};
use clap::{CommandFactory, Parser};
use std::fs::File;
use std::io::{self, Error, ErrorKind};
use std::path::PathBuf;
use std::process::exit;

fn rust_target_help() -> String {
    format!(
        "Version of the Rust compiler to target. Valid options are: {:?}. Defaults to {:?}.",
        RUST_TARGET_STRINGS,
        String::from(RustTarget::default())
    )
}

fn parse_codegen_config(what_to_generate: &str) -> io::Result<CodegenConfig> {
    let mut config = CodegenConfig::empty();
    for what in what_to_generate.split(',') {
        match what {
            "functions" => config.insert(CodegenConfig::FUNCTIONS),
            "types" => config.insert(CodegenConfig::TYPES),
            "vars" => config.insert(CodegenConfig::VARS),
            "methods" => config.insert(CodegenConfig::METHODS),
            "constructors" => config.insert(CodegenConfig::CONSTRUCTORS),
            "destructors" => config.insert(CodegenConfig::DESTRUCTORS),
            otherwise => {
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("Unknown generate item: {}", otherwise),
                ));
            }
        }
    }

    Ok(config)
}

#[derive(Parser, Debug)]
#[clap(
    about = "Generates Rust bindings from C/C++ headers.",
    override_usage = "bindgen [FLAGS] [OPTIONS] [HEADER] -- [CLANG_ARGS]...",
    trailing_var_arg = true
)]
struct BindgenCommand {
    /// C or C++ header file.
    header: Option<String>,
    /// Path to write depfile to.
    #[arg(long)]
    depfile: Option<String>,
    /// The default style of code used to generate enums.
    #[arg(long, value_name = "VARIANT")]
    default_enum_style: Option<EnumVariation>,
    /// Mark any enum whose name matches REGEX as a set of bitfield flags.
    #[arg(long, value_name = "REGEX")]
    bitfield_enum: Vec<String>,
    /// Mark any enum whose name matches REGEX as a newtype.
    #[arg(long, value_name = "REGEX")]
    newtype_enum: Vec<String>,
    /// Mark any enum whose name matches REGEX as a global newtype.
    #[arg(long, value_name = "REGEX")]
    newtype_global_enum: Vec<String>,
    /// Mark any enum whose name matches REGEX as a Rust enum.
    #[arg(long, value_name = "REGEX")]
    rustified_enum: Vec<String>,
    /// Mark any enum whose name matches REGEX as a series of constants.
    #[arg(long, value_name = "REGEX")]
    constified_enum: Vec<String>,
    /// Mark any enum whose name matches REGEX as a module of constants.
    #[arg(long, value_name = "REGEX")]
    constified_enum_module: Vec<String>,
    /// The default signed/unsigned type for C macro constants.
    #[arg(long, value_name = "VARIANT")]
    default_macro_constant_type: Option<MacroTypeVariation>,
    /// The default style of code used to generate typedefs.
    #[arg(long, value_name = "VARIANT")]
    default_alias_style: Option<AliasVariation>,
    /// Mark any typedef alias whose name matches REGEX to use normal type aliasing.
    #[arg(long, value_name = "REGEX")]
    normal_alias: Vec<String>,
    /// Mark any typedef alias whose name matches REGEX to have a new type generated for it.
    #[arg(long, value_name = "REGEX")]
    new_type_alias: Vec<String>,
    /// Mark any typedef alias whose name matches REGEX to have a new type with Deref and DerefMut to the inner type.
    #[arg(long, value_name = "REGEX")]
    new_type_alias_deref: Vec<String>,
    /// The default style of code used to generate unions with non-Copy members. Note that ManuallyDrop was first stabilized in Rust 1.20.0.
    #[arg(long, value_name = "STYLE")]
    default_non_copy_union_style: Option<NonCopyUnionStyle>,
    /// Mark any union whose name matches REGEX and who has a non-Copy member to use a bindgen-generated wrapper for fields.
    #[arg(long, value_name = "REGEX")]
    bindgen_wrapper_union: Vec<String>,
    /// Mark any union whose name matches REGEX and who has a non-Copy member to use ManuallyDrop (stabilized in Rust 1.20.0) for fields.
    #[arg(long, value_name = "REGEX")]
    manually_drop_union: Vec<String>,
    /// Mark TYPE as hidden.
    #[arg(long, value_name = "TYPE")]
    blocklist_type: Vec<String>,
    /// Mark FUNCTION as hidden.
    #[arg(long, value_name = "FUNCTION")]
    blocklist_function: Vec<String>,
    /// Mark ITEM as hidden.
    #[arg(long, value_name = "ITEM")]
    blocklist_item: Vec<String>,
    /// Mark FILE as hidden.
    #[arg(long, value_name = "FILE")]
    blocklist_file: Vec<String>,
    /// Avoid generating layout tests for any type.
    #[arg(long)]
    no_layout_tests: bool,
    /// Avoid deriving Copy on any type.
    #[arg(long)]
    no_derive_copy: bool,
    /// Avoid deriving Debug on any type.
    #[arg(long)]
    no_derive_debug: bool,
    /// Avoid deriving Default on any type.
    #[arg(long, hide = true)]
    no_derive_default: bool,
    /// Create a Debug implementation if it cannot be derived automatically.
    #[arg(long)]
    impl_debug: bool,
    /// Create a PartialEq implementation if it cannot be derived automatically.
    #[arg(long)]
    impl_partialeq: bool,
    /// Derive Default on any type.
    #[arg(long)]
    with_derive_default: bool,
    /// Derive Hash on any type.
    #[arg(long)]
    with_derive_hash: bool,
    /// Derive PartialEq on any type.
    #[arg(long)]
    with_derive_partialeq: bool,
    /// Derive PartialOrd on any type.
    #[arg(long)]
    with_derive_partialord: bool,
    /// Derive Eq on any type.
    #[arg(long)]
    with_derive_eq: bool,
    /// Derive Ord on any type.
    #[arg(long)]
    with_derive_ord: bool,
    /// Avoid including doc comments in the output, see: <https://github.com/rust-lang/rust-bindgen/issues/426>
    #[arg(long)]
    no_doc_comments: bool,
    /// Disable allowlisting types recursively. This will cause bindgen to emit Rust code that won't compile! See the `bindgen::Builder::allowlist_recursively` method's documentation for details.
    #[arg(long)]
    no_recursive_allowlist: bool,
    /// Use extern crate instead of use for objc.
    #[arg(long)]
    objc_extern_crate: bool,
    /// Generate block signatures instead of void pointers.
    #[arg(long)]
    generate_block: bool,
    /// Generate string constants as `&CStr` instead of `&[u8]`.
    #[arg(long)]
    generate_cstr: bool,
    /// Use extern crate instead of use for block.
    #[arg(long)]
    block_extern_crate: bool,
    /// Do not trust the libclang-provided mangling
    #[arg(long)]
    distrust_clang_mangling: bool,
    /// Output bindings for builtin definitions, e.g. __builtin_va_list.
    #[arg(long)]
    builtins: bool,
    /// Use the given prefix before raw types instead of ::std::os::raw.
    #[arg(long, value_name = "PREFIX")]
    ctypes_prefix: Option<String>,
    /// Use the given prefix for anonymous fields.
    #[arg(long, default_value = DEFAULT_ANON_FIELDS_PREFIX, value_name = "PREFIX")]
    anon_fields_prefix: String,
    /// Time the different bindgen phases and print to stderr
    #[arg(long)]
    time_phases: bool,
    /// Output the Clang AST for debugging purposes.
    #[arg(long)]
    emit_clang_ast: bool,
    /// Output our internal IR for debugging purposes.
    #[arg(long)]
    emit_ir: bool,
    /// Dump graphviz dot file.
    #[arg(long, value_name = "PATH")]
    emit_ir_graphviz: Option<String>,
    /// Enable support for C++ namespaces.
    #[arg(long)]
    enable_cxx_namespaces: bool,
    /// Disable namespacing via mangling, causing bindgen to generate names like `Baz` instead of `foo_bar_Baz` for an input name `foo::bar::Baz`.
    #[arg(long)]
    disable_name_namespacing: bool,
    /// Disable nested struct naming, causing bindgen to generate names like `bar` instead of `foo_bar` for a nested definition `struct foo { struct bar { } b; };`.
    #[arg(long)]
    disable_nested_struct_naming: bool,
    /// Disable support for native Rust unions.
    #[arg(long)]
    disable_untagged_union: bool,
    /// Suppress insertion of bindgen's version identifier into generated bindings.
    #[arg(long)]
    disable_header_comment: bool,
    /// Do not generate bindings for functions or methods. This is useful when you only care about struct layouts.
    #[arg(long)]
    ignore_functions: bool,
    /// Generate only given items, split by commas. Valid values are `functions`,`types`, `vars`, `methods`, `constructors` and `destructors`.
    #[arg(long, value_parser = parse_codegen_config)]
    generate: Option<CodegenConfig>,
    /// Do not generate bindings for methods.
    #[arg(long)]
    ignore_methods: bool,
    /// Do not automatically convert floats to f32/f64.
    #[arg(long)]
    no_convert_floats: bool,
    /// Do not prepend the enum name to constant or newtype variants.
    #[arg(long)]
    no_prepend_enum_name: bool,
    /// Do not try to detect default include paths
    #[arg(long)]
    no_include_path_detection: bool,
    /// Try to fit macro constants into types smaller than u32/i32
    #[arg(long)]
    fit_macro_constant_types: bool,
    /// Mark TYPE as opaque.
    #[arg(long, value_name = "TYPE")]
    opaque_type: Vec<String>,
    ///  Write Rust bindings to OUTPUT.
    #[arg(long, short, value_name = "OUTPUT")]
    output: Option<String>,
    /// Add a raw line of Rust code at the beginning of output.
    #[arg(long)]
    raw_line: Vec<String>,
    /// Add a raw line of Rust code to a given module.
    #[arg(long, number_of_values = 2, value_names = ["MODULE-NAME", "RAW-LINE"])]
    module_raw_line: Vec<String>,
    #[arg(long, help = rust_target_help())]
    rust_target: Option<RustTarget>,
    /// Use types from Rust core instead of std.
    #[arg(long)]
    use_core: bool,
    /// Conservatively generate inline namespaces to avoid name conflicts.
    #[arg(long)]
    conservative_inline_namespaces: bool,
    /// Allowlist all the free-standing functions matching REGEX. Other non-allowlisted functions will not be generated.
    #[arg(long, value_name = "REGEX")]
    allowlist_function: Vec<String>,
    /// Generate inline functions.
    #[arg(long)]
    generate_inline_functions: bool,
    /// Only generate types matching REGEX. Other non-allowlisted types will not be generated.
    #[arg(long, value_name = "REGEX")]
    allowlist_type: Vec<String>,
    /// Allowlist all the free-standing variables matching REGEX. Other non-allowlisted variables will not be generated.
    #[arg(long, value_name = "REGEX")]
    allowlist_var: Vec<String>,
    /// Allowlist all contents of PATH.
    #[arg(long, value_name = "PATH")]
    allowlist_file: Vec<String>,
    /// Print verbose error messages.
    #[arg(long)]
    verbose: bool,
    /// Preprocess and dump the input header files to disk. Useful when debugging bindgen, using C-Reduce, or when filing issues. The resulting file will be named something like `__bindgen.i` or `__bindgen.ii`.
    #[arg(long)]
    dump_preprocessed_input: bool,
    /// Do not record matching items in the regex sets. This disables reporting of unused items.
    #[arg(long)]
    no_record_matches: bool,
    /// Do not bind size_t as usize (useful on platforms where those types are incompatible).
    #[arg(long = "no-size_t-is-usize")]
    no_size_t_is_usize: bool,
    /// Do not format the generated bindings with rustfmt. This option is deprecated, please use
    /// `--formatter=none` instead.
    #[arg(long)]
    no_rustfmt_bindings: bool,
    /// Which tool should be used to format the bindings
    #[arg(
        long,
        value_name = "FORMATTER",
        conflicts_with = "no_rustfmt_bindings"
    )]
    formatter: Option<Formatter>,
    /// The absolute path to the rustfmt configuration file. The configuration file will be used for formatting the bindings. This parameter sets `formatter` to `rustfmt`.
    #[arg(long, value_name = "PATH", conflicts_with = "no_rustfmt_bindings")]
    rustfmt_configuration_file: Option<String>,
    /// Avoid deriving PartialEq for types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    no_partialeq: Vec<String>,
    /// Avoid deriving Copy and Clone for types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    no_copy: Vec<String>,
    /// Avoid deriving Debug for types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    no_debug: Vec<String>,
    /// Avoid deriving/implementing Default for types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    no_default: Vec<String>,
    /// Avoid deriving Hash for types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    no_hash: Vec<String>,
    /// Add `#[must_use]` annotation to types matching REGEX.
    #[arg(long, value_name = "REGEX")]
    must_use_type: Vec<String>,
    /// Enables detecting unexposed attributes in functions (slow). Used to generate `#[must_use]` annotations.
    #[arg(long)]
    enable_function_attribute_detection: bool,
    /// Use `*const [T; size]` instead of `*const T` for C arrays
    #[arg(long)]
    use_array_pointers_in_arguments: bool,
    /// The name to be used in a #[link(wasm_import_module = ...)] statement
    #[arg(long, value_name = "NAME")]
    wasm_import_module_name: Option<String>,
    /// Use dynamic loading mode with the given library name.
    #[arg(long, value_name = "NAME")]
    dynamic_loading: Option<String>,
    /// Require successful linkage to all functions in the library.
    #[arg(long)]
    dynamic_link_require_all: bool,
    /// Prefix the name of exported symbols.
    #[arg(long)]
    prefix_link_name: Option<String>,
    /// Makes generated bindings `pub` only for items if the items are publically accessible in C++.
    #[arg(long)]
    respect_cxx_access_specs: bool,
    /// Always translate enum integer types to native Rust integer types.
    #[arg(long)]
    translate_enum_integer_types: bool,
    /// Generate types with C style naming.
    #[arg(long)]
    c_naming: bool,
    /// Always output explicit padding fields.
    #[arg(long)]
    explicit_padding: bool,
    /// Enables generation of vtable functions.
    #[arg(long)]
    vtable_generation: bool,
    /// Enables sorting of code generation in a predefined manner.
    #[arg(long)]
    sort_semantically: bool,
    /// Deduplicates extern blocks.
    #[arg(long)]
    merge_extern_blocks: bool,
    /// Overrides the ABI of functions matching REGEX. The OVERRIDE value must be of the shape REGEX=ABI where ABI can be one of C, stdcall, efiapi, fastcall, thiscall, aapcs, win64 or C-unwind.
    #[arg(long, value_name = "OVERRIDE")]
    override_abi: Vec<String>,
    /// Wrap unsafe operations in unsafe blocks.
    #[arg(long)]
    wrap_unsafe_ops: bool,
    /// Derive custom traits on any kind of type. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
    #[arg(long, value_name = "CUSTOM")]
    with_derive_custom: Vec<String>,
    /// Derive custom traits on a `struct`. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
    #[arg(long, value_name = "CUSTOM")]
    with_derive_custom_struct: Vec<String>,
    /// Derive custom traits on an `enum. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
    #[arg(long, value_name = "CUSTOM")]
    with_derive_custom_enum: Vec<String>,
    /// Derive custom traits on a `union`. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
    #[arg(long, value_name = "CUSTOM")]
    with_derive_custom_union: Vec<String>,
    /// Generate wrappers for `static` and `static inline` functions.
    #[arg(long, requires = "experimental")]
    wrap_static_fns: bool,
    /// Sets the path for the source file that must be created due to the presence of `static` and
    /// `static inline` functions.
    #[arg(long, requires = "experimental", value_name = "PATH")]
    wrap_static_fns_path: Option<PathBuf>,
    /// Sets the suffix added to the extern wrapper functions generated for `static` and `static
    /// inline` functions.
    #[arg(long, requires = "experimental", value_name = "SUFFIX")]
    wrap_static_fns_suffix: Option<String>,
    /// Set the default visibility of fields, including bitfields and accessor methods for
    /// bitfields. This flag is ignored if the `--respect-cxx-access-specs` flag is used.
    #[arg(long, value_name = "VISIBILITY")]
    default_visibility: Option<FieldVisibilityKind>,
    /// Whether to emit diagnostics or not.
    #[arg(long, requires = "experimental")]
    emit_diagnostics: bool,
    /// Generates completions for the specified SHELL, sends them to `stdout` and exits.
    #[arg(long, value_name = "SHELL")]
    generate_shell_completions: Option<clap_complete::Shell>,
    /// Enables experimental features.
    #[arg(long)]
    experimental: bool,
    /// Prints the version, and exits
    #[arg(short = 'V', long)]
    version: bool,
    /// Arguments to be passed straight through to clang.
    clang_args: Vec<String>,
}

/// Construct a new [`Builder`](./struct.Builder.html) from command line flags.
pub fn builder_from_flags<I>(
    args: I,
) -> Result<(Builder, Box<dyn io::Write>, bool), io::Error>
where
    I: Iterator<Item = String>,
{
    let command = BindgenCommand::parse_from(args);

    let BindgenCommand {
        header,
        depfile,
        default_enum_style,
        bitfield_enum,
        newtype_enum,
        newtype_global_enum,
        rustified_enum,
        constified_enum,
        constified_enum_module,
        default_macro_constant_type,
        default_alias_style,
        normal_alias,
        new_type_alias,
        new_type_alias_deref,
        default_non_copy_union_style,
        bindgen_wrapper_union,
        manually_drop_union,
        blocklist_type,
        blocklist_function,
        blocklist_item,
        blocklist_file,
        no_layout_tests,
        no_derive_copy,
        no_derive_debug,
        no_derive_default,
        impl_debug,
        impl_partialeq,
        with_derive_default,
        with_derive_hash,
        with_derive_partialeq,
        with_derive_partialord,
        with_derive_eq,
        with_derive_ord,
        no_doc_comments,
        no_recursive_allowlist,
        objc_extern_crate,
        generate_block,
        generate_cstr,
        block_extern_crate,
        distrust_clang_mangling,
        builtins,
        ctypes_prefix,
        anon_fields_prefix,
        time_phases,
        emit_clang_ast,
        emit_ir,
        emit_ir_graphviz,
        enable_cxx_namespaces,
        disable_name_namespacing,
        disable_nested_struct_naming,
        disable_untagged_union,
        disable_header_comment,
        ignore_functions,
        generate,
        ignore_methods,
        no_convert_floats,
        no_prepend_enum_name,
        no_include_path_detection,
        fit_macro_constant_types,
        opaque_type,
        output,
        raw_line,
        module_raw_line,
        rust_target,
        use_core,
        conservative_inline_namespaces,
        allowlist_function,
        generate_inline_functions,
        allowlist_type,
        allowlist_var,
        allowlist_file,
        verbose,
        dump_preprocessed_input,
        no_record_matches,
        no_size_t_is_usize,
        no_rustfmt_bindings,
        formatter,
        rustfmt_configuration_file,
        no_partialeq,
        no_copy,
        no_debug,
        no_default,
        no_hash,
        must_use_type,
        enable_function_attribute_detection,
        use_array_pointers_in_arguments,
        wasm_import_module_name,
        dynamic_loading,
        dynamic_link_require_all,
        prefix_link_name,
        respect_cxx_access_specs,
        translate_enum_integer_types,
        c_naming,
        explicit_padding,
        vtable_generation,
        sort_semantically,
        merge_extern_blocks,
        override_abi,
        wrap_unsafe_ops,
        with_derive_custom,
        with_derive_custom_struct,
        with_derive_custom_enum,
        with_derive_custom_union,
        wrap_static_fns,
        wrap_static_fns_path,
        wrap_static_fns_suffix,
        default_visibility,
        emit_diagnostics,
        generate_shell_completions,
        experimental: _,
        version,
        clang_args,
    } = command;

    if let Some(shell) = generate_shell_completions {
        clap_complete::generate(
            shell,
            &mut BindgenCommand::command(),
            "bindgen",
            &mut std::io::stdout(),
        );

        exit(0);
    }

    if version {
        println!(
            "bindgen {}",
            option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")
        );
        if verbose {
            println!("Clang: {}", bindgen::clang_version().full);
        }
        std::process::exit(0);
    }

    let mut builder = builder();

    if let Some(header) = header {
        builder = builder.header(header);
    } else {
        return Err(Error::new(ErrorKind::Other, "Header not found"));
    }

    if let Some(rust_target) = rust_target {
        builder = builder.rust_target(rust_target);
    }

    if let Some(variant) = default_enum_style {
        builder = builder.default_enum_style(variant);
    }

    for regex in bitfield_enum {
        builder = builder.bitfield_enum(regex);
    }

    for regex in newtype_enum {
        builder = builder.newtype_enum(regex);
    }

    for regex in newtype_global_enum {
        builder = builder.newtype_global_enum(regex);
    }

    for regex in rustified_enum {
        builder = builder.rustified_enum(regex);
    }

    for regex in constified_enum {
        builder = builder.constified_enum(regex);
    }

    for regex in constified_enum_module {
        builder = builder.constified_enum_module(regex);
    }

    if let Some(default_macro_constant_type) = default_macro_constant_type {
        builder =
            builder.default_macro_constant_type(default_macro_constant_type)
    }

    if let Some(variant) = default_alias_style {
        builder = builder.default_alias_style(variant);
    }

    for regex in normal_alias {
        builder = builder.type_alias(regex);
    }

    for regex in new_type_alias {
        builder = builder.new_type_alias(regex);
    }

    for regex in new_type_alias_deref {
        builder = builder.new_type_alias_deref(regex);
    }

    if let Some(variant) = default_non_copy_union_style {
        builder = builder.default_non_copy_union_style(variant);
    }

    for regex in bindgen_wrapper_union {
        builder = builder.bindgen_wrapper_union(regex);
    }

    for regex in manually_drop_union {
        builder = builder.manually_drop_union(regex);
    }

    for ty in blocklist_type {
        builder = builder.blocklist_type(ty);
    }

    for fun in blocklist_function {
        builder = builder.blocklist_function(fun);
    }

    for id in blocklist_item {
        builder = builder.blocklist_item(id);
    }

    for file in blocklist_file {
        builder = builder.blocklist_file(file);
    }

    if builtins {
        builder = builder.emit_builtins();
    }

    if no_layout_tests {
        builder = builder.layout_tests(false);
    }

    if no_derive_copy {
        builder = builder.derive_copy(false);
    }

    if no_derive_debug {
        builder = builder.derive_debug(false);
    }

    if impl_debug {
        builder = builder.impl_debug(true);
    }

    if impl_partialeq {
        builder = builder.impl_partialeq(true);
    }

    if with_derive_default {
        builder = builder.derive_default(true);
    }

    if with_derive_hash {
        builder = builder.derive_hash(true);
    }

    if with_derive_partialeq {
        builder = builder.derive_partialeq(true);
    }

    if with_derive_partialord {
        builder = builder.derive_partialord(true);
    }

    if with_derive_eq {
        builder = builder.derive_eq(true);
    }

    if with_derive_ord {
        builder = builder.derive_ord(true);
    }

    if no_derive_default {
        builder = builder.derive_default(false);
    }

    if no_prepend_enum_name {
        builder = builder.prepend_enum_name(false);
    }

    if no_include_path_detection {
        builder = builder.detect_include_paths(false);
    }

    if fit_macro_constant_types {
        builder = builder.fit_macro_constants(true);
    }

    if time_phases {
        builder = builder.time_phases(true);
    }

    if use_array_pointers_in_arguments {
        builder = builder.array_pointers_in_arguments(true);
    }

    if let Some(wasm_import_name) = wasm_import_module_name {
        builder = builder.wasm_import_module_name(wasm_import_name);
    }

    if let Some(prefix) = ctypes_prefix {
        builder = builder.ctypes_prefix(prefix);
    }

    builder = builder.anon_fields_prefix(anon_fields_prefix);

    if let Some(config) = generate {
        builder = builder.with_codegen_config(config);
    }

    if emit_clang_ast {
        builder = builder.emit_clang_ast();
    }

    if emit_ir {
        builder = builder.emit_ir();
    }

    if let Some(path) = emit_ir_graphviz {
        builder = builder.emit_ir_graphviz(path);
    }

    if enable_cxx_namespaces {
        builder = builder.enable_cxx_namespaces();
    }

    if enable_function_attribute_detection {
        builder = builder.enable_function_attribute_detection();
    }

    if disable_name_namespacing {
        builder = builder.disable_name_namespacing();
    }

    if disable_nested_struct_naming {
        builder = builder.disable_nested_struct_naming();
    }

    if disable_untagged_union {
        builder = builder.disable_untagged_union();
    }

    if disable_header_comment {
        builder = builder.disable_header_comment();
    }

    if ignore_functions {
        builder = builder.ignore_functions();
    }

    if ignore_methods {
        builder = builder.ignore_methods();
    }

    if no_convert_floats {
        builder = builder.no_convert_floats();
    }

    if no_doc_comments {
        builder = builder.generate_comments(false);
    }

    if no_recursive_allowlist {
        builder = builder.allowlist_recursively(false);
    }

    if objc_extern_crate {
        builder = builder.objc_extern_crate(true);
    }

    if generate_block {
        builder = builder.generate_block(true);
    }

    if generate_cstr {
        builder = builder.generate_cstr(true);
    }

    if block_extern_crate {
        builder = builder.block_extern_crate(true);
    }

    for ty in opaque_type {
        builder = builder.opaque_type(ty);
    }

    for line in raw_line {
        builder = builder.raw_line(line);
    }

    let mut values = module_raw_line.into_iter();
    while let Some(module) = values.next() {
        let line = values.next().unwrap();
        builder = builder.module_raw_line(module, line);
    }

    if use_core {
        builder = builder.use_core();
    }

    if distrust_clang_mangling {
        builder = builder.trust_clang_mangling(false);
    }

    if conservative_inline_namespaces {
        builder = builder.conservative_inline_namespaces();
    }

    if generate_inline_functions {
        builder = builder.generate_inline_functions(true);
    }

    for regex in allowlist_function {
        builder = builder.allowlist_function(regex);
    }

    for regex in allowlist_type {
        builder = builder.allowlist_type(regex);
    }

    for regex in allowlist_var {
        builder = builder.allowlist_var(regex);
    }

    for file in allowlist_file {
        builder = builder.allowlist_file(file);
    }

    for arg in clang_args {
        builder = builder.clang_arg(arg);
    }

    let output = if let Some(path) = &output {
        let file = File::create(path)?;
        if let Some(depfile) = depfile {
            builder = builder.depfile(path, depfile);
        }
        Box::new(io::BufWriter::new(file)) as Box<dyn io::Write>
    } else {
        if let Some(depfile) = depfile {
            builder = builder.depfile("-", depfile);
        }
        Box::new(io::BufWriter::new(io::stdout())) as Box<dyn io::Write>
    };

    if dump_preprocessed_input {
        builder.dump_preprocessed_input()?;
    }

    if no_record_matches {
        builder = builder.record_matches(false);
    }

    if no_size_t_is_usize {
        builder = builder.size_t_is_usize(false);
    }

    if no_rustfmt_bindings {
        builder = builder.formatter(Formatter::None);
    }

    if let Some(formatter) = formatter {
        builder = builder.formatter(formatter);
    }

    if let Some(path_str) = rustfmt_configuration_file {
        let path = PathBuf::from(path_str);

        if !path.is_absolute() {
            return Err(Error::new(
                ErrorKind::Other,
                "--rustfmt-configuration-file needs to be an absolute path!",
            ));
        }

        if path.to_str().is_none() {
            return Err(Error::new(
                ErrorKind::Other,
                "--rustfmt-configuration-file contains non-valid UTF8 characters.",
            ));
        }

        builder = builder.rustfmt_configuration_file(Some(path));
    }

    for regex in no_partialeq {
        builder = builder.no_partialeq(regex);
    }

    for regex in no_copy {
        builder = builder.no_copy(regex);
    }

    for regex in no_debug {
        builder = builder.no_debug(regex);
    }

    for regex in no_default {
        builder = builder.no_default(regex);
    }

    for regex in no_hash {
        builder = builder.no_hash(regex);
    }

    for regex in must_use_type {
        builder = builder.must_use_type(regex);
    }

    if let Some(dynamic_library_name) = dynamic_loading {
        builder = builder.dynamic_library_name(dynamic_library_name);
    }

    if dynamic_link_require_all {
        builder = builder.dynamic_link_require_all(true);
    }

    if let Some(prefix_link_name) = prefix_link_name {
        #[derive(Debug)]
        struct PrefixLinkNameCallback {
            prefix: String,
        }

        impl bindgen::callbacks::ParseCallbacks for PrefixLinkNameCallback {
            fn generated_link_name_override(
                &self,
                item_info: bindgen::callbacks::ItemInfo<'_>,
            ) -> Option<String> {
                let mut prefix = self.prefix.clone();
                prefix.push_str(item_info.name);
                Some(prefix)
            }
        }

        builder = builder.parse_callbacks(Box::new(PrefixLinkNameCallback {
            prefix: prefix_link_name,
        }))
    }

    if respect_cxx_access_specs {
        builder = builder.respect_cxx_access_specs(true);
    }

    if translate_enum_integer_types {
        builder = builder.translate_enum_integer_types(true);
    }

    if c_naming {
        builder = builder.c_naming(true);
    }

    if explicit_padding {
        builder = builder.explicit_padding(true);
    }

    if vtable_generation {
        builder = builder.vtable_generation(true);
    }

    if sort_semantically {
        builder = builder.sort_semantically(true);
    }

    if merge_extern_blocks {
        builder = builder.merge_extern_blocks(true);
    }

    for abi_override in override_abi {
        let (regex, abi_str) = abi_override
            .rsplit_once('=')
            .expect("Invalid ABI override: Missing `=`");
        let abi = abi_str
            .parse()
            .unwrap_or_else(|err| panic!("Invalid ABI override: {}", err));
        builder = builder.override_abi(abi, regex);
    }

    if wrap_unsafe_ops {
        builder = builder.wrap_unsafe_ops(true);
    }

    #[derive(Debug)]
    struct CustomDeriveCallback {
        derives: Vec<String>,
        kind: Option<TypeKind>,
        regex_set: bindgen::RegexSet,
    }

    impl bindgen::callbacks::ParseCallbacks for CustomDeriveCallback {
        fn cli_args(&self) -> Vec<String> {
            let mut args = vec![];

            let flag = match &self.kind {
                None => "--with-derive-custom",
                Some(TypeKind::Struct) => "--with-derive-custom-struct",
                Some(TypeKind::Enum) => "--with-derive-custom-enum",
                Some(TypeKind::Union) => "--with-derive-custom-union",
            };

            let derives = self.derives.join(",");

            for item in self.regex_set.get_items() {
                args.extend_from_slice(&[
                    flag.to_owned(),
                    format!("{}={}", item, derives),
                ]);
            }

            args
        }

        fn add_derives(
            &self,
            info: &bindgen::callbacks::DeriveInfo<'_>,
        ) -> Vec<String> {
            if self.kind.map(|kind| kind == info.kind).unwrap_or(true) &&
                self.regex_set.matches(info.name)
            {
                return self.derives.clone();
            }
            vec![]
        }
    }

    for (custom_derives, kind, name) in [
        (with_derive_custom, None, "--with-derive-custom"),
        (
            with_derive_custom_struct,
            Some(TypeKind::Struct),
            "--with-derive-custom-struct",
        ),
        (
            with_derive_custom_enum,
            Some(TypeKind::Enum),
            "--with-derive-custom-enum",
        ),
        (
            with_derive_custom_union,
            Some(TypeKind::Union),
            "--with-derive-custom-union",
        ),
    ] {
        let name = emit_diagnostics.then_some(name);
        for custom_derive in custom_derives {
            let (regex, derives) = custom_derive
                .rsplit_once('=')
                .expect("Invalid custom derive argument: Missing `=`");
            let derives = derives.split(',').map(|s| s.to_owned()).collect();

            let mut regex_set = RegexSet::new();
            regex_set.insert(regex);
            regex_set.build_with_diagnostics(false, name);

            builder = builder.parse_callbacks(Box::new(CustomDeriveCallback {
                derives,
                kind,
                regex_set,
            }));
        }
    }

    if wrap_static_fns {
        builder = builder.wrap_static_fns(true);
    }

    if let Some(path) = wrap_static_fns_path {
        builder = builder.wrap_static_fns_path(path);
    }

    if let Some(suffix) = wrap_static_fns_suffix {
        builder = builder.wrap_static_fns_suffix(suffix);
    }

    if let Some(visibility) = default_visibility {
        builder = builder.default_visibility(visibility);
    }

    if emit_diagnostics {
        builder = builder.emit_diagnostics();
    }

    Ok((builder, output, verbose))
}
