use binrw::{
    binread,
    io::{Cursor, Seek, SeekFrom},
    BinRead,
};

#[test]
fn enum_assert() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(assert(b == 1))]
    enum Test {
        A {
            a: u8,
            b: u8,
        },
        #[br(assert(a == -1))]
        B {
            a: i16,
            b: u8,
        },
    }

    assert_eq!(
        Test::read_le(&mut Cursor::new(b"\xff\xff\x01")).unwrap(),
        Test::B { a: -1, b: 1 }
    );
    Test::read_le(&mut Cursor::new(b"\xff\xff\0")).expect_err("accepted bad data");
    Test::read_le(&mut Cursor::new(b"\0\0\x01")).expect_err("accepted bad data");
}

#[test]
fn enum_assert_with_self() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(assert(self.verify()))]
    enum Test {
        A {
            a: u8,
            b: u8,
        },
        #[br(assert(self.verify_only_b()))]
        B {
            a: i16,
            b: u8,
        },
    }

    impl Test {
        fn verify(&self) -> bool {
            match self {
                Test::A { b, .. } => *b == 1,
                Test::B { a, b } => *a == -1 && *b == 1,
            }
        }

        fn verify_only_b(&self) -> bool {
            matches!(self, Test::B { .. })
        }
    }

    assert_eq!(
        Test::read_le(&mut Cursor::new(b"\xff\xff\x01")).unwrap(),
        Test::B { a: -1, b: 1 }
    );
    Test::read_le(&mut Cursor::new(b"\xff\xff\0")).expect_err("accepted bad data");
    Test::read_le(&mut Cursor::new(b"\0\0\x01")).expect_err("accepted bad data");
}

#[test]
fn enum_field_args() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(import(a: u8))]
    struct Foo(#[br(calc(a))] u8);

    #[derive(BinRead, Debug, PartialEq)]
    #[br(import(a: u8))]
    enum Test {
        A {
            #[br(args(a))]
            field: Foo,
        },
    }

    let result = Test::read_le_args(&mut Cursor::new(b""), (42,)).unwrap();
    assert_eq!(result, Test::A { field: Foo(42) });
}

#[test]
fn enum_non_copy_args() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(import(a: NonCopyArg))]
    enum Test {
        A {
            #[br(calc = a.0)]
            a: u8,
        },
        B {
            #[br(calc = a.0)]
            _b: u8,
        },
    }

    #[derive(Clone)]
    struct NonCopyArg(u8);

    let result = Test::read_le_args(&mut Cursor::new(b""), (NonCopyArg(1),)).unwrap();
    assert_eq!(result, Test::A { a: 1 });
}

#[test]
fn enum_calc_temp_field() {
    #[binread]
    #[derive(Debug, Eq, PartialEq)]
    enum Test {
        #[br(magic(0u8))]
        Zero {
            #[br(temp)]
            a: u8,
            #[br(calc(a))]
            b: u8,
        },
    }

    let result = Test::read_le(&mut Cursor::new(b"\0\x04")).unwrap();
    // This also indirectly checks that `temp` is actually working since
    // compilation would fail if it weren’t due to the missing `a` property
    assert_eq!(result, Test::Zero { b: 4 });
}

#[test]
fn enum_endianness() {
    #[derive(BinRead, Debug, Eq, PartialEq)]
    #[br(big)]
    enum Test {
        #[br(magic(1u16))]
        OneBig,
        #[br(little, magic(2u16))]
        TwoLittle {
            a: u16,
        },
        ThreeBig {
            a: u16,
            b: u16,
            c: u16,
        },
    }

    assert_eq!(
        Test::read(&mut Cursor::new(b"\0\x01")).unwrap(),
        Test::OneBig
    );
    let error = Test::read(&mut Cursor::new(b"\x01\0")).expect_err("accepted bad data");
    assert!(matches!(error, binrw::Error::EnumErrors { .. }));
    assert_eq!(
        Test::read(&mut Cursor::new(b"\x02\0\x03\0")).unwrap(),
        Test::TwoLittle { a: 3 }
    );
    let error = Test::read(&mut Cursor::new(b"\0\x02\x03\0")).expect_err("accepted bad data");
    assert!(matches!(error, binrw::Error::EnumErrors { .. }));
    assert_eq!(
        Test::read(&mut Cursor::new(b"\0\x03\x01\x00\x02\x00")).unwrap(),
        Test::ThreeBig {
            a: 3,
            b: 0x100,
            c: 0x200
        }
    );
}

#[test]
fn enum_magic() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(big, magic(0x1234u16))]
    enum Test {
        #[br(magic(0u8))]
        Zero { a: u16 },
        // Fail on the second field to actually test that a rewind happens
        // to the beginning of the enum data, not to before the enum magic
        #[br(magic(1u8))]
        One { a: u16 },
    }

    let result = Test::read(&mut Cursor::new(b"\x12\x34\x01\x02\x03")).unwrap();
    assert_eq!(result, Test::One { a: 515 });
}

#[test]
fn enum_magic_holey() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(big, magic(0x12u8))]
    enum Test {
        Wrong(#[br(magic(0x01u8))] u16),
        #[br(magic(0x12u8))]
        Right {
            a: u16,
        },
    }

    let result = Test::read(&mut Cursor::new(b"\x12\x12\x01\x02\x03")).unwrap();
    assert_eq!(result, Test::Right { a: 0x102 });
}

#[test]
fn enum_pre_assert() {
    #[derive(BinRead, Debug, PartialEq)]
    #[br(big, import(a: bool))]
    enum Test {
        #[br(pre_assert(a))]
        A(u16),
        B(u16),
    }

    assert_eq!(
        Test::read_args(&mut Cursor::new(b"\0\x01"), (true,)).unwrap(),
        Test::A(1)
    );
    assert_eq!(
        Test::read_args(&mut Cursor::new(b"\0\x01"), (false,)).unwrap(),
        Test::B(1)
    );
}

#[test]
fn enum_return_all_errors() {
    #[derive(BinRead, Debug)]
    #[br(big, return_all_errors)]
    enum Test {
        #[br(magic(0u16))]
        One { _a: u16 },
        #[br(magic(1u16))]
        Two { _a: u16 },
    }

    let error = Test::read(&mut Cursor::new("\0\x01")).expect_err("accepted bad data");

    match error {
        binrw::Error::EnumErrors {
            pos,
            variant_errors,
        } => {
            assert_eq!(pos, 0);
            assert_eq!(variant_errors.len(), 2);
            assert_eq!(variant_errors[0].0, "One");
            if let binrw::Error::BadMagic { pos, found } = &variant_errors[0].1 {
                assert_eq!(pos, &0);
                assert_eq!(&format!("{found:?}"), "1");
            } else {
                panic!("expected BadMagic; got {:?}", variant_errors[0].1);
            }
            assert_eq!(variant_errors[1].0, "Two");
            assert!(matches!(
                variant_errors[1].1.root_cause(),
                binrw::Error::Io(..)
            ));
        }
        _ => panic!("wrong error type"),
    }
}

#[test]
fn enum_rewind_on_assert() {
    #[allow(dead_code)]
    #[derive(BinRead, Debug)]
    #[br(assert(b == 1))]
    enum Test {
        A { a: u8, b: u8 },
        B { a: u16, b: u8 },
    }

    let mut data = Cursor::new(b"\0\0\0\0");
    let expected = data.seek(SeekFrom::Start(1)).unwrap();
    Test::read_le(&mut data).expect_err("accepted bad data");
    assert_eq!(expected, data.stream_position().unwrap());
}

#[test]
fn enum_rewind_on_eof() {
    #[derive(BinRead, Debug)]
    enum Test {
        A {
            _a: u8,
            // Fail on the second field to actually test that a rewind happens
            // to the beginning of the enum, not just the beginning of the field
            _b: u16,
        },
    }

    let mut data = Cursor::new(b"\0\0\0");
    let expected = data.seek(SeekFrom::Start(1)).unwrap();
    Test::read_le(&mut data).expect_err("accepted bad data");
    assert_eq!(expected, data.stream_position().unwrap());
}

#[test]
fn enum_rewind_on_variant_assert() {
    #[allow(dead_code)]
    #[derive(BinRead, Debug)]
    enum Test {
        #[br(assert(b == 1))]
        A { a: u8, b: u8 },
    }

    let mut data = Cursor::new(b"\0\0");
    let expected = data.seek(SeekFrom::Start(1)).unwrap();
    Test::read_le(&mut data).expect_err("accepted bad data");
    assert_eq!(expected, data.stream_position().unwrap());
}

#[test]
fn enum_return_unexpected_error() {
    #[derive(BinRead, Debug)]
    #[br(big, return_unexpected_error)]
    enum Test {
        #[br(magic(0u16))]
        One { _a: u16 },
        #[br(magic(1u16))]
        Two { _a: u16 },
    }

    let error = Test::read(&mut Cursor::new("\0\x01")).expect_err("accepted bad data");
    assert!(matches!(error, binrw::Error::NoVariantMatch { .. }));
}

#[test]
fn mixed_enum() {
    #[derive(BinRead, Debug, Eq, PartialEq)]
    #[br(big)]
    enum Test {
        #[br(magic(0u8))]
        Zero,
        #[br(magic(2u8))]
        Two { a: u16, b: u16 },
    }

    assert!(matches!(
        Test::read(&mut Cursor::new(b"\0")).unwrap(),
        Test::Zero
    ));
    let error = Test::read(&mut Cursor::new(b"\x01")).expect_err("accepted bad data");
    assert!(matches!(error, binrw::Error::EnumErrors { .. }));
    let result = Test::read(&mut Cursor::new(b"\x02\0\x03\0\x04")).unwrap();
    assert_eq!(result, Test::Two { a: 3, b: 4 });
}
