use super::construction::{BinaryBuilder, bs_put_binary, bs_put_integer, finalize_builder};
use super::matching::{Endian, MatchContext, SegmentFlags, decode_integer};
use super::*;
use crate::atom::Atom;
use crate::loader::{Instruction, Literal};
use crate::module::{Module, ModuleOrigin};
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::{Float, ProcBin, SubBinary};
use crate::term::shared_binary::{SharedBinary, write_proc_bin};
use crate::term::sub_binary::SUB_BINARY_WORDS;
use std::collections::HashMap;

fn module(code: Vec<Instruction>) -> Module {
    let label_index = code
        .iter()
        .enumerate()
        .filter_map(|(ip, instruction)| match instruction {
            Instruction::Label { label } => Some((*label, ip)),
            _ => None,
        })
        .collect();
    Module {
        name: Atom::OK,
        generation: 0,
        origin: ModuleOrigin::Preloaded,
        exports: HashMap::new(),
        label_index,
        code,
        literals: Vec::new(),
        constant_pool: Default::default(),
        resolved_imports: Vec::new(),
        lambdas: Vec::new(),
        string_table: Vec::new(),
        function_table: Vec::new(),
        line_table: Vec::new(),
        line_info: Vec::new(),
    }
}

fn binary_term(process: &mut Process, bytes: &[u8]) -> Term {
    let words = 2 + packed_word_count(bytes.len());
    let ptr = process.heap_mut().alloc(words).expect("test heap fits");
    let heap = heap_slice(ptr, words);
    write_binary(heap, bytes).expect("test binary fits")
}

fn proc_bin_term(process: &mut Process, shared: &SharedBinary) -> Term {
    let ptr = process.heap_mut().alloc(3).expect("test heap fits");
    let heap = heap_slice(ptr, 3);
    write_proc_bin(heap, shared).expect("test proc bin fits")
}

fn start_context(process: &mut Process, source_bytes: &[u8]) -> (Module, Term) {
    let module = module(vec![Instruction::Label { label: 9 }]);
    let source = binary_term(process, source_bytes);
    process.set_x_reg(0, source);
    binary_op(
        process,
        &module,
        BinaryOp::BsStartMatch3,
        &[Operand::Label(9), Operand::X(0), Operand::X(1)],
    )
    .expect("start match");
    (module, process.x_reg(1))
}

#[test]
fn interpreter_binary_builder_init_tracks_empty_position_and_capacity() {
    let mut process = Process::new(1, 8);
    let module = module(Vec::new());

    assert_eq!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsInitWritable,
            &[Operand::Unsigned(10), Operand::X(0)],
        ),
        Ok(InstructionOutcome::Continue)
    );
    let builder = BinaryBuilder::new(process.x_reg(0)).expect("builder context");
    assert_eq!(builder.write_position_bits(), 0);
    assert!(builder.capacity_bytes() >= 10);
}

#[test]
fn interpreter_binary_builder_init_collects_and_grows_when_heap_is_full() {
    let mut process = Process::new(1, 2);
    let module = module(Vec::new());

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsInitWritable,
        &[Operand::Unsigned(10), Operand::X(0)],
    )
    .expect("builder init grows the heap instead of failing");
    let builder = BinaryBuilder::new(process.x_reg(0)).expect("builder term in x0");
    assert_eq!(builder.write_position_bits(), 0);
    assert!(builder.capacity_bytes() >= 10);
}

#[test]
fn interpreter_binary_builder_appends_integer_and_binary_segments() {
    let mut process = Process::new(1, 32);
    let module = module(Vec::new());
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsInitWritable,
        &[Operand::Unsigned(3), Operand::X(0)],
    )
    .expect("builder init");
    let builder = process.x_reg(0);

    bs_put_integer(
        &mut process,
        &module,
        builder,
        &Operand::Integer(65),
        &Operand::Unsigned(8),
        &Operand::Unsigned(1),
        &Operand::Atom(None),
    )
    .expect("put integer");
    let source = binary_term(&mut process, &[66, 67]);
    process.set_x_reg(1, source);
    bs_put_binary(&mut process, &module, builder, &Operand::X(1)).expect("put binary");

    let builder_state = BinaryBuilder::new(builder).expect("builder context");
    assert_eq!(builder_state.write_position_bits(), 24);
    let result = finalize_builder(&mut process, builder).expect("final binary");
    assert_eq!(
        Binary::new(result).expect("binary").as_bytes(),
        &[65, 66, 67]
    );
}

#[test]
fn interpreter_binary_builder_promotes_large_result_to_proc_bin() {
    let mut process = Process::new(1, 64);
    let module = module(Vec::new());
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsInitWritable,
        &[Operand::Unsigned(100), Operand::X(0)],
    )
    .expect("builder init");
    let builder = process.x_reg(0);

    for index in 0..100_i64 {
        bs_put_integer(
            &mut process,
            &module,
            builder,
            &Operand::Integer(index),
            &Operand::Unsigned(8),
            &Operand::Unsigned(1),
            &Operand::Atom(None),
        )
        .expect("put integer");
    }

    let result = finalize_builder(&mut process, builder).expect("final binary");
    let expected: Vec<u8> = (0..100).collect();
    assert_eq!(
        BinaryRef::new(result).expect("binary ref").as_bytes(),
        expected.as_slice()
    );
    assert!(ProcBin::new(result).is_some());
}

#[test]
fn interpreter_binary_builder_rejects_writes_past_capacity() {
    let mut process = Process::new(1, 16);
    let module = module(Vec::new());
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsInitWritable,
        &[Operand::Unsigned(1), Operand::X(0)],
    )
    .expect("builder init");
    let builder = process.x_reg(0);

    assert_eq!(
        bs_put_integer(
            &mut process,
            &module,
            builder,
            &Operand::Integer(0x4142),
            &Operand::Unsigned(16),
            &Operand::Unsigned(1),
            &Operand::Atom(None),
        ),
        Err(ExecError::Badarg)
    );
    assert_eq!(
        BinaryBuilder::new(builder)
            .expect("builder context")
            .write_position_bits(),
        0
    );
}

#[test]
fn interpreter_binary_match_extracts_fields_and_tail() {
    let mut process = Process::new(1, 64);
    let module = module(vec![Instruction::Label { label: 9 }]);
    let source = binary_term(&mut process, &[65, 66, 67, 68]);
    process.set_x_reg(0, source);

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsStartMatch3,
        &[Operand::Label(9), Operand::X(0), Operand::X(1)],
    )
    .expect("start match");
    assert_eq!(
        MatchContext::new(process.x_reg(1))
            .expect("match context")
            .position_bits(),
        0
    );
    assert_eq!(
        MatchContext::new(process.x_reg(1))
            .expect("match context")
            .total_bits(),
        32
    );

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetInteger2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(8),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("get first integer");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetInteger2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(8),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(3),
        ],
    )
    .expect("get second integer");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetBinary2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(16),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(4),
        ],
    )
    .expect("get rest binary");

    assert_eq!(process.x_reg(2).as_small_int(), Some(65));
    assert_eq!(process.x_reg(3).as_small_int(), Some(66));
    assert_eq!(
        Binary::new(process.x_reg(4)).expect("rest").as_bytes(),
        &[67, 68]
    );
    assert_eq!(
        Binary::new(process.x_reg(0)).expect("source").as_bytes(),
        &[65, 66, 67, 68]
    );
    assert_eq!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsTestTail2,
            &[Operand::Label(9), Operand::X(1), Operand::Unsigned(0)]
        ),
        Ok(InstructionOutcome::Continue)
    );
}

#[test]
fn interpreter_binary_match_extracts_sub_binary_from_proc_bin_without_copying() {
    let mut process = Process::new(1, 64);
    let module = module(vec![Instruction::Label { label: 9 }]);
    let bytes: Vec<u8> = (0_u8..=255).cycle().take(1024 * 1024).collect();
    let shared = SharedBinary::new(bytes);
    let source = proc_bin_term(&mut process, &shared);
    process.set_x_reg(0, source);
    assert_eq!(shared.ref_count(), 2);

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsStartMatch3,
        &[Operand::Label(9), Operand::X(0), Operand::X(1)],
    )
    .expect("start match");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsSkipBits2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(10 * u8::BITS as u64),
            Operand::Unsigned(1),
            Operand::Atom(None),
        ],
    )
    .expect("skip offset");
    let before_available = process.heap().available();
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetBinary2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(10 * u8::BITS as u64),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("get proc bin slice");

    assert_eq!(
        before_available - process.heap().available(),
        SUB_BINARY_WORDS
    );
    let sub_binary = SubBinary::new(process.x_reg(2)).expect("sub binary result");
    assert_eq!(sub_binary.parent(), source);
    assert_eq!(
        BinaryRef::new(process.x_reg(2))
            .expect("binary ref")
            .as_bytes(),
        &shared.as_bytes()[10..20]
    );
    assert_eq!(shared.ref_count(), 2);
}

#[test]
fn interpreter_binary_match_failures_branch_without_advancing() {
    let mut process = Process::new(1, 64);
    let mut module = module(vec![Instruction::Label { label: 9 }]);
    let literals = vec![Literal::String(b"he".to_vec())];
    module.constant_pool =
        crate::constant_pool::materialise_literals(&literals, None).expect("literal pool");
    module.literals = literals;
    let source = binary_term(&mut process, b"hello");
    process.set_x_reg(0, source);

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsStartMatch3,
        &[Operand::Label(9), Operand::X(0), Operand::X(1)],
    )
    .expect("start match");
    assert_eq!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsMatchString,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(16),
                Operand::Literal(0)
            ]
        ),
        Ok(InstructionOutcome::Continue)
    );
    assert_eq!(
        MatchContext::new(process.x_reg(1))
            .expect("match context")
            .position_bits(),
        16
    );

    let failed = binary_op(
        &mut process,
        &module,
        BinaryOp::BsMatchString,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(16),
            Operand::Literal(0),
        ],
    )
    .expect("failed match branches");
    assert_eq!(
        failed,
        InstructionOutcome::Jump(CodePosition {
            module: Atom::OK,
            instruction_pointer: 0
        })
    );
    assert_eq!(
        MatchContext::new(process.x_reg(1))
            .expect("match context")
            .position_bits(),
        16
    );
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsTestTail2,
            &[Operand::Label(9), Operand::X(1), Operand::Unsigned(0)]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsGetInteger2,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(64),
                Operand::Unsigned(1),
                Operand::Atom(None),
                Operand::X(2)
            ]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
}

fn match_one_integer(source_bytes: &[u8], size_bits: u64, flags: Operand) -> Term {
    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, source_bytes);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetInteger2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(size_bits),
            Operand::Unsigned(1),
            flags,
            Operand::X(2),
        ],
    )
    .expect("get integer");
    process.x_reg(2)
}

#[test]
fn bs_get_integer_signed_high_bit_byte_is_negative() {
    assert_eq!(
        match_one_integer(&[0xFF], 8, Operand::Unsigned(0x04)).as_small_int(),
        Some(-1)
    );
}

#[test]
fn bs_get_integer_unsigned_high_bit_byte_stays_positive() {
    assert_eq!(
        match_one_integer(&[0xFF], 8, Operand::Atom(None)).as_small_int(),
        Some(255)
    );
}

#[test]
fn bs_get_integer_signed_multibit_sign_extends_to_full_width() {
    assert_eq!(
        match_one_integer(&[0x80, 0x00], 16, Operand::Unsigned(0x04)).as_small_int(),
        Some(-32768)
    );
    assert_eq!(
        match_one_integer(&[0x80, 0x00], 16, Operand::Atom(None)).as_small_int(),
        Some(32768)
    );
}

#[test]
fn bs_get_integer_signed_respects_endianness() {
    assert_eq!(
        match_one_integer(&[0x80, 0x01], 16, Operand::Unsigned(0x04)).as_small_int(),
        Some(-32767)
    );
    assert_eq!(
        match_one_integer(&[0x80, 0x01], 16, Operand::Unsigned(0x06)).as_small_int(),
        Some(384)
    );
}

#[test]
fn decode_integer_sign_extends_sub_byte_width() {
    let signed = SegmentFlags {
        endian: Endian::Big,
        signed: true,
    };
    assert_eq!(decode_integer(&[0xF8], signed), Ok(-8));
}

#[test]
fn interpreter_binary_start_match_non_binary_branches_to_fail() {
    let mut process = Process::new(1, 16);
    let module = module(vec![Instruction::Label { label: 9 }]);
    process.set_x_reg(0, Term::small_int(12));
    assert_eq!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsStartMatch3,
            &[Operand::Label(9), Operand::X(0), Operand::X(1)]
        ),
        Ok(InstructionOutcome::Jump(CodePosition {
            module: Atom::OK,
            instruction_pointer: 0
        }))
    );
}

#[test]
fn bs_skip_bits_and_test_unit_update_or_branch() {
    let mut process = Process::new(1, 64);
    let (module, ctx) = start_context(&mut process, &[1, 2, 3, 4]);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsSkipBits2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(16),
            Operand::Unsigned(1),
            Operand::Atom(None),
        ],
    )
    .expect("skip");
    assert_eq!(MatchContext::new(ctx).expect("context").position_bits(), 16);
    assert_eq!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsTestUnit,
            &[Operand::Label(9), Operand::X(1), Operand::Unsigned(8)]
        ),
        Ok(InstructionOutcome::Continue)
    );
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsSkipBits2,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(32),
                Operand::Unsigned(1),
                Operand::Atom(None)
            ]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
    assert_eq!(MatchContext::new(ctx).expect("context").position_bits(), 16);
}

#[test]
fn bs_test_unit_fails_for_non_divisible_remainder() {
    let mut process = Process::new(1, 64);
    let (module, ctx) = start_context(&mut process, &[1, 2, 3, 4]);
    MatchContext::new(ctx)
        .expect("context")
        .set_position_bits(7);
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsTestUnit,
            &[Operand::Label(9), Operand::X(1), Operand::Unsigned(8)]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
}

#[test]
fn bs_get_float_extracts_64_and_32_bit_values() {
    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, &2.75_f64.to_be_bytes());
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetFloat2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(64),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("float64");
    assert!((Float::new(process.x_reg(2)).expect("float").value() - 2.75).abs() < f64::EPSILON);

    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, &1.5_f32.to_be_bytes());
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetFloat2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(32),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("float32");
    assert_eq!(Float::new(process.x_reg(2)).expect("float").value(), 1.5);
}

#[test]
fn bs_get_tail_returns_remaining_or_empty_binary() {
    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, b"abcdefgh");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsSkipBits2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(32),
            Operand::Unsigned(1),
            Operand::Atom(None),
        ],
    )
    .expect("skip");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetTail,
        &[Operand::X(1), Operand::X(2), Operand::Unsigned(0)],
    )
    .expect("tail");
    assert_eq!(
        Binary::new(process.x_reg(2)).expect("tail").as_bytes(),
        b"efgh"
    );

    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetTail,
        &[Operand::X(1), Operand::X(3), Operand::Unsigned(0)],
    )
    .expect("empty tail");
    assert_eq!(Binary::new(process.x_reg(3)).expect("tail").as_bytes(), b"");
}

#[test]
fn utf_get_and_skip_decode_valid_sequences_and_branch_on_invalid() {
    let mut process = Process::new(1, 128);
    let (module, _) = start_context(&mut process, "A¢€𐍈".as_bytes());
    for (reg, expected) in [(2, 0x41), (3, 0xa2), (4, 0x20ac), (5, 0x10348)] {
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsGetUtf8,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(0),
                Operand::Atom(None),
                Operand::X(reg),
            ],
        )
        .expect("utf8");
        assert_eq!(process.x_reg(reg as u16).as_small_int(), Some(expected));
    }

    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, &[0xf0, 0x28, 0x8c, 0x28]);
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsSkipUtf8,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(0),
                Operand::Atom(None)
            ]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
}

#[test]
fn utf16_and_utf32_decode_endianness_and_reject_invalid_codepoints() {
    let mut process = Process::new(1, 128);
    let (module, _) = start_context(&mut process, &[0x00, 0x41, 0xd8, 0x34, 0xdd, 0x1e]);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetUtf16,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(0),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("utf16 be");
    assert_eq!(process.x_reg(2).as_small_int(), Some(0x41));
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetUtf16,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(0),
            Operand::Atom(None),
            Operand::X(3),
        ],
    )
    .expect("utf16 surrogate");
    assert_eq!(process.x_reg(3).as_small_int(), Some(0x1d11e));

    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, &[0x41, 0x00]);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetUtf16,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(0),
            Operand::Unsigned(0x02),
            Operand::X(2),
        ],
    )
    .expect("utf16 le");
    assert_eq!(process.x_reg(2).as_small_int(), Some(0x41));

    let mut process = Process::new(1, 64);
    let (module, _) = start_context(
        &mut process,
        &[0x00, 0x10, 0xff, 0xff, 0x00, 0x11, 0x00, 0x00],
    );
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetUtf32,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(0),
            Operand::Atom(None),
            Operand::X(2),
        ],
    )
    .expect("utf32");
    assert_eq!(process.x_reg(2).as_small_int(), Some(0x10ffff));
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsSkipUtf32,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::Unsigned(0),
                Operand::Atom(None)
            ]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
}

#[test]
fn bs_get_and_set_position_support_re_reading() {
    let mut process = Process::new(1, 64);
    let (module, _) = start_context(&mut process, &[0xaa, 0xbb]);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetPosition,
        &[Operand::X(1), Operand::X(2), Operand::Unsigned(0)],
    )
    .expect("get pos");
    assert_eq!(process.x_reg(2).as_small_int(), Some(0));
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsSkipBits2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(8),
            Operand::Unsigned(1),
            Operand::Atom(None),
        ],
    )
    .expect("skip");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetPosition,
        &[Operand::X(1), Operand::X(3), Operand::Unsigned(0)],
    )
    .expect("get pos");
    assert_eq!(process.x_reg(3).as_small_int(), Some(8));
    process.set_x_reg(4, Term::small_int(0));
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsSetPosition,
        &[Operand::X(1), Operand::X(4)],
    )
    .expect("set pos");
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsGetInteger2,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::Unsigned(8),
            Operand::Unsigned(1),
            Operand::Atom(None),
            Operand::X(5),
        ],
    )
    .expect("get integer");
    assert_eq!(process.x_reg(5).as_small_int(), Some(0xaa));
}

#[test]
fn bs_match_runs_commands_and_rolls_back_position_on_failure() {
    let mut process = Process::new(1, 128);
    let (module, ctx) = start_context(&mut process, &[1, 2, 3, 4, 5]);
    binary_op(
        &mut process,
        &module,
        BinaryOp::BsMatch,
        &[
            Operand::Label(9),
            Operand::X(1),
            Operand::List(vec![
                Operand::List(vec![
                    Operand::Unsigned(2),
                    Operand::Unsigned(0),
                    Operand::Atom(None),
                    Operand::Unsigned(8),
                    Operand::Unsigned(1),
                    Operand::X(2),
                ]),
                Operand::List(vec![Operand::Unsigned(5), Operand::Unsigned(8)]),
                Operand::List(vec![
                    Operand::Unsigned(4),
                    Operand::Unsigned(0),
                    Operand::Atom(None),
                    Operand::Unsigned(16),
                    Operand::Unsigned(1),
                    Operand::X(3),
                ]),
            ]),
        ],
    )
    .expect("bs_match");
    assert_eq!(process.x_reg(2).as_small_int(), Some(1));
    assert_eq!(
        Binary::new(process.x_reg(3)).expect("binary").as_bytes(),
        &[3, 4]
    );
    assert_eq!(MatchContext::new(ctx).expect("context").position_bits(), 32);

    let before = MatchContext::new(ctx).expect("context").position_bits();
    assert!(matches!(
        binary_op(
            &mut process,
            &module,
            BinaryOp::BsMatch,
            &[
                Operand::Label(9),
                Operand::X(1),
                Operand::List(vec![
                    Operand::List(vec![Operand::Unsigned(5), Operand::Unsigned(8)]),
                    Operand::List(vec![
                        Operand::Unsigned(0),
                        Operand::Unsigned(64),
                        Operand::Unsigned(1),
                    ]),
                ])
            ]
        ),
        Ok(InstructionOutcome::Jump(_))
    ));
    assert_eq!(
        MatchContext::new(ctx).expect("context").position_bits(),
        before
    );
}
