use compiler_builtins::int::{HInt, MinInt, i256, u256};

const LOHI_SPLIT: u128 = 0xaaaaaaaaaaaaaaaaffffffffffffffff;

/// Print a `u256` as hex since we can't add format implementations
fn hexu(v: u256) -> String {
    format!(
        "0x{:016x}{:016x}{:016x}{:016x}",
        v.0[3], v.0[2], v.0[1], v.0[0]
    )
}

#[test]
fn widen_u128() {
    assert_eq!(u128::MAX.widen(), u256([u64::MAX, u64::MAX, 0, 0]));
    assert_eq!(
        LOHI_SPLIT.widen(),
        u256([u64::MAX, 0xaaaaaaaaaaaaaaaa, 0, 0])
    );
}

#[test]
fn widen_i128() {
    assert_eq!((-1i128).widen(), u256::MAX.signed());
    assert_eq!(
        (LOHI_SPLIT as i128).widen(),
        i256([u64::MAX, 0xaaaaaaaaaaaaaaaa, u64::MAX, u64::MAX])
    );
    assert_eq!((-1i128).zero_widen().unsigned(), (u128::MAX).widen());
}

#[test]
fn widen_mul_u128() {
    let tests = [
        (u128::MAX / 2, 2_u128, u256([u64::MAX - 1, u64::MAX, 0, 0])),
        (u128::MAX, 2_u128, u256([u64::MAX - 1, u64::MAX, 1, 0])),
        (u128::MAX, u128::MAX, u256([1, 0, u64::MAX - 1, u64::MAX])),
        (u128::MIN, u128::MIN, u256::ZERO),
        (1234, 0, u256::ZERO),
        (0, 1234, u256::ZERO),
    ];

    let mut errors = Vec::new();
    for (i, (a, b, exp)) in tests.iter().copied().enumerate() {
        let res = a.widen_mul(b);
        let res_z = a.zero_widen_mul(b);
        assert_eq!(res, res_z);
        if res != exp {
            errors.push((i, a, b, exp, res));
        }
    }

    for (i, a, b, exp, res) in &errors {
        eprintln!(
            "FAILURE ({i}): {a:#034x} * {b:#034x} = {} got {}",
            hexu(*exp),
            hexu(*res)
        );
    }
    assert!(errors.is_empty());
}

#[test]
fn not_u128() {
    assert_eq!(!u256::ZERO, u256::MAX);
}

#[test]
fn shr_u128() {
    let only_low = [
        1,
        u16::MAX.into(),
        u32::MAX.into(),
        u64::MAX.into(),
        u128::MAX,
    ];

    let mut errors = Vec::new();

    for a in only_low {
        for perturb in 0..10 {
            let a = a.saturating_add(perturb);
            for shift in 0..128 {
                let res = a.widen() >> shift;
                let expected = (a >> shift).widen();
                if res != expected {
                    errors.push((a.widen(), shift, res, expected));
                }
            }
        }
    }

    let check = [
        (
            u256::MAX,
            1,
            u256([u64::MAX, u64::MAX, u64::MAX, u64::MAX >> 1]),
        ),
        (
            u256::MAX,
            5,
            u256([u64::MAX, u64::MAX, u64::MAX, u64::MAX >> 5]),
        ),
        (u256::MAX, 63, u256([u64::MAX, u64::MAX, u64::MAX, 1])),
        (u256::MAX, 64, u256([u64::MAX, u64::MAX, u64::MAX, 0])),
        (u256::MAX, 65, u256([u64::MAX, u64::MAX, u64::MAX >> 1, 0])),
        (u256::MAX, 127, u256([u64::MAX, u64::MAX, 1, 0])),
        (u256::MAX, 128, u256([u64::MAX, u64::MAX, 0, 0])),
        (u256::MAX, 129, u256([u64::MAX, u64::MAX >> 1, 0, 0])),
        (u256::MAX, 191, u256([u64::MAX, 1, 0, 0])),
        (u256::MAX, 192, u256([u64::MAX, 0, 0, 0])),
        (u256::MAX, 193, u256([u64::MAX >> 1, 0, 0, 0])),
        (u256::MAX, 191, u256([u64::MAX, 1, 0, 0])),
        (u256::MAX, 254, u256([0b11, 0, 0, 0])),
        (u256::MAX, 255, u256([1, 0, 0, 0])),
    ];

    for (input, shift, expected) in check {
        let res = input >> shift;
        if res != expected {
            errors.push((input, shift, res, expected));
        }
    }

    for (a, b, res, expected) in &errors {
        eprintln!(
            "FAILURE: {} >> {b} = {} got {}",
            hexu(*a),
            hexu(*expected),
            hexu(*res),
        );
    }
    assert!(errors.is_empty());
}
