//! ETF-related Erlang BIFs.

use crate::atom::{Atom, AtomTable};
use crate::etf::decode::{DecodeOptions, decode_term, decode_term_with_options};
use crate::etf::encode::{
    EncodeOptions, IoSegment, encode_term, encode_term_iovec, encode_term_with_options,
};
use crate::native::{
    BifRegistryImpl, Capability, NativeFn, NativeRegistrationError, ProcessContext,
};
use crate::term::Term;
use crate::term::binary::Binary;
use crate::term::boxed::{Cons, Tuple};

const ETF_BIFS: &[(&str, u8, Capability, NativeFn)] = &[
    ("term_to_binary", 1, Capability::Pure, bif_term_to_binary),
    ("term_to_binary", 2, Capability::Pure, bif_term_to_binary_2),
    ("term_to_iovec", 1, Capability::Pure, bif_term_to_iovec),
    ("term_to_iovec", 2, Capability::Pure, bif_term_to_iovec_2),
    ("binary_to_term", 1, Capability::Pure, bif_binary_to_term),
    ("binary_to_term", 2, Capability::Pure, bif_binary_to_term_2),
];

pub fn register_etf_bifs(
    registry: &BifRegistryImpl,
    atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
    let erlang = atom_table.intern("erlang");
    for &(function_name, arity, capability, native_function) in ETF_BIFS {
        let function = atom_table.intern(function_name);
        registry.register(erlang, function, arity, native_function, capability)?;
    }
    Ok(())
}

pub fn bif_term_to_binary(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [term] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table().ok_or_else(badarg)?;
    let bytes = encode_term(*term, atom_table).map_err(|_| badarg())?;
    context.alloc_binary(&bytes)
}

pub fn bif_term_to_binary_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [term, options] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table().ok_or_else(badarg)?;
    let options = parse_encode_options(*options, atom_table)?;
    let bytes = encode_term_with_options(*term, atom_table, options).map_err(|_| badarg())?;
    context.alloc_binary(&bytes)
}

pub fn bif_term_to_iovec(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [term] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table().ok_or_else(badarg)?;
    let segments = encode_term_iovec(*term, atom_table).map_err(|_| badarg())?;
    segments_to_iolist(segments, context)
}

pub fn bif_term_to_iovec_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [term, options] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table().ok_or_else(badarg)?;
    let options = parse_encode_options(*options, atom_table)?;
    let segments = encode_term_iovec(*term, atom_table).map_err(|_| badarg())?;
    if options.compression_level.is_none() || options.compression_level == Some(0) {
        return segments_to_iolist(segments, context);
    }

    let has_references = segments
        .iter()
        .any(|segment| matches!(segment, IoSegment::Reference(_)));
    if has_references {
        segments_to_iolist(segments, context)
    } else {
        let bytes = encode_term_with_options(*term, atom_table, options).map_err(|_| badarg())?;
        let binary = context.alloc_binary(&bytes)?;
        context.alloc_list(&[binary])
    }
}

pub fn bif_binary_to_term(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [binary] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
    let bytes = Binary::new(*binary).ok_or_else(badarg)?.as_bytes();
    decode_term(bytes, context, atom_table.as_ref()).map_err(|_| badarg())
}

pub fn bif_binary_to_term_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
    let [binary, options_term] = args else {
        return Err(badarg());
    };
    let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
    let options = parse_decode_options(*options_term, atom_table.as_ref())?;
    let bytes = Binary::new(*binary).ok_or_else(badarg)?.as_bytes();
    let decoded = decode_term_with_options(bytes, context, atom_table.as_ref(), options)
        .map_err(|_| badarg())?;
    if options.return_used {
        let used = i64::try_from(decoded.used).map_err(|_| badarg())?;
        let used_term = Term::try_small_int(used).ok_or_else(badarg)?;
        context.alloc_tuple(&[decoded.term, used_term])
    } else {
        Ok(decoded.term)
    }
}

fn segments_to_iolist(
    segments: Vec<IoSegment>,
    context: &mut ProcessContext,
) -> Result<Term, Term> {
    // Each owned-segment allocation may collect; keep every accumulated
    // term (including pre-existing references) rooted until the final list
    // is built.
    context.with_rooted(&[], |context, roots| {
        for segment in segments {
            let term = match segment {
                IoSegment::Owned(bytes) => context.alloc_binary(&bytes)?,
                IoSegment::Reference(binary_term) => binary_term,
            };
            context.rooted_push(roots, term)?;
        }
        let terms = (0..context.rooted_len(roots))
            .map(|index| context.rooted(roots, index))
            .collect::<Result<Vec<_>, _>>()?;
        context.alloc_list(&terms)
    })
}

fn parse_encode_options(options: Term, atom_table: &AtomTable) -> Result<EncodeOptions, Term> {
    let mut parsed = EncodeOptions::default();
    let mut current = options;
    while !current.is_nil() {
        let cons = Cons::new(current).ok_or_else(badarg)?;
        let option = cons.head();
        if let Some(atom) = option.as_atom() {
            match atom_table.resolve(atom) {
                Some("compressed") => parsed.compression_level = Some(6),
                _ => return Err(badarg()),
            }
        } else if let Some(tuple) = Tuple::new(option) {
            if tuple.arity() != 2 {
                return Err(badarg());
            }
            let key = tuple.get(0).ok_or_else(badarg)?;
            let value = tuple.get(1).ok_or_else(badarg)?;
            let key_atom = key.as_atom().ok_or_else(badarg)?;
            match atom_table.resolve(key_atom) {
                Some("compressed") => {
                    let level = value.as_small_int().ok_or_else(badarg)?;
                    if !(0..=9).contains(&level) {
                        return Err(badarg());
                    }
                    parsed.compression_level = Some(u32::try_from(level).map_err(|_| badarg())?);
                }
                Some("minor_version") => {
                    if value.as_small_int() != Some(2) {
                        return Err(badarg());
                    }
                }
                _ => return Err(badarg()),
            }
        } else {
            return Err(badarg());
        }
        current = cons.tail();
    }
    Ok(parsed)
}

fn parse_decode_options(options: Term, atom_table: &AtomTable) -> Result<DecodeOptions, Term> {
    let mut parsed = DecodeOptions::default();
    let mut current = options;
    while !current.is_nil() {
        let cons = Cons::new(current).ok_or_else(badarg)?;
        let option_atom = cons.head().as_atom().ok_or_else(badarg)?;
        match atom_table.resolve(option_atom) {
            Some("safe") => parsed.safe = true,
            Some("used") => parsed.return_used = true,
            _ => return Err(badarg()),
        }
        current = cons.tail();
    }
    Ok(parsed)
}

fn badarg() -> Term {
    Term::atom(Atom::BADARG)
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use crate::atom::{Atom, AtomTable};
    use crate::etf::tags;
    use crate::native::{BifRegistryImpl, Capability, ProcessContext};
    use crate::process::Process;
    use crate::term::Term;
    use crate::term::binary::{Binary, packed_word_count, write_binary};
    use crate::term::binary_ref::BinaryRef;
    use crate::term::boxed::Cons;
    use crate::term::boxed::{Tuple, write_cons, write_tuple};

    use super::{
        bif_binary_to_term, bif_binary_to_term_2, bif_term_to_binary, bif_term_to_binary_2,
        bif_term_to_iovec, bif_term_to_iovec_2, parse_decode_options, parse_encode_options,
        register_etf_bifs,
    };
    use crate::native::stdlib_stubs::type_conversion_bifs::bif_iolist_to_binary;

    fn badarg() -> Term {
        Term::atom(Atom::BADARG)
    }

    fn ctx_with_atoms<'process>(
        process: &'process mut Process,
        atom_table: Arc<AtomTable>,
    ) -> ProcessContext<'process> {
        let mut context = ProcessContext::new();
        context.set_atom_table(Some(atom_table));
        context.attach_process(process, 0);
        context
    }

    #[test]
    fn register_etf_bifs_registers_term_to_binary_and_binary_to_term_arities() {
        let atom_table = AtomTable::new();
        let registry = BifRegistryImpl::new();

        register_etf_bifs(&registry, &atom_table).expect("ETF BIF registration");

        let erlang = atom_table.intern("erlang");
        for (name, arity) in [
            ("term_to_binary", 1),
            ("term_to_binary", 2),
            ("term_to_iovec", 1),
            ("term_to_iovec", 2),
            ("binary_to_term", 1),
            ("binary_to_term", 2),
        ] {
            let function = atom_table.intern(name);
            let entry = registry
                .lookup(erlang, function, arity)
                .expect("ETF BIF should be registered");
            assert_eq!(entry.capability, Capability::Pure);
        }
    }

    #[test]
    fn term_to_binary_returns_etf_binary_for_tuple() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let mut process = Process::new(1, 128);
        let mut tuple_heap = [0_u64; 3];
        let tuple = write_tuple(
            &mut tuple_heap,
            &[Term::atom(Atom::OK), Term::small_int(42)],
        )
        .expect("tuple");
        let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));

        let result = bif_term_to_binary(&[tuple], &mut context).expect("term_to_binary result");
        let binary = Binary::new(result).expect("result should be binary");
        assert_eq!(
            binary.as_bytes(),
            &[
                tags::VERSION,
                tags::SMALL_TUPLE_EXT,
                2,
                tags::SMALL_ATOM_UTF8_EXT,
                2,
                b'o',
                b'k',
                tags::SMALL_INTEGER_EXT,
                42,
            ]
        );
    }

    fn list_elements(list: Term) -> Vec<Term> {
        let mut elements = Vec::new();
        let mut current = list;
        while !current.is_nil() {
            let cons = Cons::new(current).expect("proper list");
            elements.push(cons.head());
            current = cons.tail();
        }
        elements
    }

    fn inline_binary(bytes: &[u8]) -> (Vec<u64>, Term) {
        let mut heap = vec![0_u64; 2 + packed_word_count(bytes.len())];
        let term = write_binary(&mut heap, bytes).expect("binary fits");
        (heap, term)
    }

    #[test]
    fn term_to_iovec_returns_iolist_with_large_binary_reference() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let data_atom = atom_table.intern("data");
        let bytes = vec![3_u8; 64];
        let (_binary_heap, large_binary) = inline_binary(&bytes);
        let mut tuple_heap = [0_u64; 3];
        let tuple =
            write_tuple(&mut tuple_heap, &[Term::atom(data_atom), large_binary]).expect("tuple");
        let mut process = Process::new(1, 256);
        let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));

        let iolist = bif_term_to_iovec(&[tuple], &mut context).expect("term_to_iovec");
        let elements = list_elements(iolist);

        assert!(elements.len() >= 2);
        assert!(elements.iter().all(|term| BinaryRef::new(*term).is_some()));
        assert!(elements.contains(&large_binary));
    }

    #[test]
    fn term_to_iovec_iolist_to_binary_matches_term_to_binary() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let data_atom = atom_table.intern("data");
        let bytes = vec![4_u8; 64];
        let (_binary_heap, large_binary) = inline_binary(&bytes);
        let mut tuple_heap = [0_u64; 3];
        let tuple =
            write_tuple(&mut tuple_heap, &[Term::atom(data_atom), large_binary]).expect("tuple");
        let mut process = Process::new(1, 512);

        let iolist = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_iovec(&[tuple], &mut context).expect("term_to_iovec")
        };
        let flattened = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_iolist_to_binary(&[iolist], &mut context).expect("iolist_to_binary")
        };
        let term_binary = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary(&[tuple], &mut context).expect("term_to_binary")
        };

        assert_eq!(
            BinaryRef::new(flattened).expect("flattened").as_bytes(),
            BinaryRef::new(term_binary).expect("term binary").as_bytes()
        );
    }

    #[test]
    fn term_to_iovec_2_reuses_encode_options_and_preserves_references() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let compressed_atom = atom_table.intern("compressed");
        let data_atom = atom_table.intern("data");
        let bytes = vec![5_u8; 64];
        let (_binary_heap, large_binary) = inline_binary(&bytes);
        let mut tuple_heap = [0_u64; 3];
        let tuple =
            write_tuple(&mut tuple_heap, &[Term::atom(data_atom), large_binary]).expect("tuple");
        let mut process = Process::new(1, 512);
        let options = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_list(&[Term::atom(compressed_atom)])
                .expect("options")
        };

        let iolist = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_iovec_2(&[tuple, options], &mut context).expect("term_to_iovec/2")
        };
        let elements = list_elements(iolist);

        assert!(elements.contains(&large_binary));
    }

    #[test]
    fn term_to_iovec_2_rejects_malformed_encode_options() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let compressed_atom = atom_table.intern("compressed");
        let mut process = Process::new(1, 128);
        let out_of_range_option = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            let tuple = context
                .alloc_tuple(&[Term::atom(compressed_atom), Term::small_int(10)])
                .expect("tuple");
            context.alloc_list(&[tuple]).expect("options")
        };
        let improper_options = {
            let mut cell = [0_u64; 2];
            write_cons(&mut cell, Term::atom(compressed_atom), Term::small_int(0))
                .expect("improper options")
        };

        for options in [Term::small_int(0), out_of_range_option, improper_options] {
            let result = {
                let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
                bif_term_to_iovec_2(&[Term::small_int(42), options], &mut context)
            };
            assert_eq!(result, Err(badarg()));
        }
    }

    #[test]
    fn term_to_binary_2_compresses_large_terms_and_round_trips() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let compressed_atom = atom_table.intern("compressed");
        let mut process = Process::new(1, 2048);
        let mut cells = vec![[0_u64; 2]; 512];
        let mut list = Term::NIL;
        for cell in cells.iter_mut().rev() {
            list = write_cons(cell, Term::small_int(7), list).expect("cons");
        }
        let option = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_list(&[Term::atom(compressed_atom)])
                .expect("option list")
        };
        let uncompressed = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary(&[list], &mut context).expect("uncompressed")
        };
        let compressed = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary_2(&[list, option], &mut context).expect("compressed")
        };
        let uncompressed_bytes = BinaryRef::new(uncompressed)
            .expect("uncompressed binary")
            .as_bytes();
        let compressed_bytes = BinaryRef::new(compressed)
            .expect("compressed binary")
            .as_bytes();
        assert!(compressed_bytes.len() < uncompressed_bytes.len());
        assert_eq!(compressed_bytes[0], tags::VERSION);
        assert_eq!(compressed_bytes[1], tags::COMPRESSED_EXT);

        let decoded = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_binary_to_term(&[compressed], &mut context).expect("decode")
        };
        assert_eq!(decoded, list);
    }

    #[test]
    fn term_to_binary_2_compressed_zero_is_uncompressed() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let compressed_atom = atom_table.intern("compressed");
        let mut process = Process::new(1, 128);
        let option = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            let tuple = context
                .alloc_tuple(&[Term::atom(compressed_atom), Term::small_int(0)])
                .expect("tuple");
            context.alloc_list(&[tuple]).expect("options")
        };
        let uncompressed = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary(&[Term::small_int(42)], &mut context).expect("uncompressed")
        };
        let encoded = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary_2(&[Term::small_int(42), option], &mut context).expect("encoded")
        };
        assert_eq!(
            Binary::new(encoded).expect("encoded").as_bytes(),
            Binary::new(uncompressed).expect("uncompressed").as_bytes()
        );
    }

    #[test]
    fn term_to_binary_2_accepts_minor_version_two_noop() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let minor_version_atom = atom_table.intern("minor_version");
        let mut process = Process::new(1, 128);
        let option = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            let tuple = context
                .alloc_tuple(&[Term::atom(minor_version_atom), Term::small_int(2)])
                .expect("tuple");
            context.alloc_list(&[tuple]).expect("options")
        };
        let uncompressed = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary(&[Term::small_int(42)], &mut context).expect("uncompressed")
        };
        let encoded = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary_2(&[Term::small_int(42), option], &mut context).expect("encoded")
        };
        assert_eq!(
            Binary::new(encoded).expect("encoded").as_bytes(),
            Binary::new(uncompressed).expect("uncompressed").as_bytes()
        );
    }

    #[test]
    fn term_to_binary_2_rejects_malformed_encode_options() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let compressed_atom = atom_table.intern("compressed");
        let mut process = Process::new(1, 128);
        let out_of_range_option = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            let tuple = context
                .alloc_tuple(&[Term::atom(compressed_atom), Term::small_int(10)])
                .expect("tuple");
            context.alloc_list(&[tuple]).expect("options")
        };
        let improper_options = {
            let mut cell = [0_u64; 2];
            write_cons(&mut cell, Term::atom(compressed_atom), Term::small_int(0))
                .expect("improper options")
        };

        for options in [Term::small_int(0), out_of_range_option, improper_options] {
            let result = {
                let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
                bif_term_to_binary_2(&[Term::small_int(42), options], &mut context)
            };
            assert_eq!(result, Err(badarg()));
        }
    }

    #[test]
    fn parse_decode_options_rejects_non_atoms_and_improper_lists() {
        let atom_table = AtomTable::with_common_atoms();
        let safe_atom = atom_table.intern("safe");
        let mut cell = [0_u64; 2];
        let improper_options = write_cons(&mut cell, Term::atom(safe_atom), Term::small_int(0))
            .expect("improper options");

        assert_eq!(
            parse_decode_options(Term::small_int(0), &atom_table),
            Err(badarg())
        );
        assert_eq!(
            parse_decode_options(improper_options, &atom_table),
            Err(badarg())
        );
        assert_eq!(
            parse_decode_options(Term::NIL, &atom_table).expect("empty options"),
            Default::default()
        );
        assert_eq!(
            parse_encode_options(Term::NIL, &atom_table).expect("empty options"),
            Default::default()
        );
    }

    #[test]
    fn binary_to_term_2_safe_known_atom_succeeds_and_novel_atom_fails() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let safe_atom = atom_table.intern("safe");
        let hello = atom_table.intern("hello");
        let mut process = Process::new(1, 128);
        let options = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_list(&[Term::atom(safe_atom)])
                .expect("options")
        };
        let hello_binary = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_term_to_binary(&[Term::atom(hello)], &mut context).expect("hello binary")
        };
        let decoded = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_binary_to_term_2(&[hello_binary, options], &mut context).expect("decode safe")
        };
        assert_eq!(decoded, Term::atom(hello));

        let novel_binary = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_binary(&[
                    tags::VERSION,
                    tags::SMALL_ATOM_UTF8_EXT,
                    5,
                    b'n',
                    b'o',
                    b'v',
                    b'e',
                    b'l',
                ])
                .expect("novel binary")
        };
        let result = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_binary_to_term_2(&[novel_binary, options], &mut context)
        };
        assert_eq!(result, Err(badarg()));
    }

    #[test]
    fn binary_to_term_2_used_returns_term_and_bytes_used() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let used_atom = atom_table.intern("used");
        let mut process = Process::new(1, 128);
        let options = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_list(&[Term::atom(used_atom)])
                .expect("options")
        };
        let binary = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_binary(&[tags::VERSION, tags::SMALL_INTEGER_EXT, 42, 99])
                .expect("binary")
        };
        let result = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_binary_to_term_2(&[binary, options], &mut context).expect("used result")
        };
        let tuple = Tuple::new(result).expect("used tuple");
        assert_eq!(tuple.get(0), Some(Term::small_int(42)));
        assert_eq!(tuple.get(1), Some(Term::small_int(3)));
    }

    #[test]
    fn binary_to_term_1_rejects_trailing_bytes() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let mut process = Process::new(1, 128);
        let binary = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            context
                .alloc_binary(&[tags::VERSION, tags::SMALL_INTEGER_EXT, 42, 99])
                .expect("binary")
        };
        let result = {
            let mut context = ctx_with_atoms(&mut process, Arc::clone(&atom_table));
            bif_binary_to_term(&[binary], &mut context)
        };
        assert_eq!(result, Err(badarg()));
    }

    #[test]
    fn term_to_binary_returns_badarg_without_atom_table() {
        let mut process = Process::new(1, 64);
        let mut context = ProcessContext::new();
        context.attach_process(&mut process, 0);

        assert_eq!(
            bif_term_to_binary(&[Term::atom(Atom::OK)], &mut context),
            Err(badarg())
        );
    }

    #[test]
    fn term_to_binary_returns_badarg_for_wrong_arity() {
        let atom_table = Arc::new(AtomTable::with_common_atoms());
        let mut process = Process::new(1, 64);
        let mut context = ctx_with_atoms(&mut process, atom_table);

        assert_eq!(bif_term_to_binary(&[], &mut context), Err(badarg()));
    }
}
