#![cfg(feature = "std")]

use deku::{noseek::NoSeek, prelude::*};
use hexlit::hex;
use rstest::*;

#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)]
pub struct Test {
    // how many following bytes to skip
    skip_u8: u8,
    #[deku(seek_from_current = "*skip_u8")]
    byte: u8,
    #[deku(seek_from_current = "0")]
    byte_after: u8,
    #[deku(seek_rewind)]
    byte_after_rewind: u8,
    #[deku(seek_from_start = "2")]
    byte_again: u8,
    #[deku(seek_from_end = "-1")]
    another: u8,
}

#[rstest(input, expected,
    case(&hex!("01002030"), Test{ skip_u8: 1, byte: 0x20, byte_after: 0x30, byte_after_rewind: 0x01, byte_again: 0x20, another: 0x30 }),
    case(&hex!("0200002030"), Test{ skip_u8: 2, byte: 0x20, byte_after: 0x30, byte_after_rewind: 0x02, byte_again: 0x00, another: 0x30 }),
    case(&hex!("00ffaa"), Test{ skip_u8: 0, byte: 0xff, byte_after: 0xaa, byte_after_rewind: 0x00, byte_again: 0xaa, another: 0xaa }),
)]
#[cfg(feature = "alloc")]
fn test_seek(input: &[u8], expected: Test) {
    let input = input.to_vec();
    let mut cursor = std::io::Cursor::new(input.clone());
    let (_, ret_read) = Test::from_reader((&mut cursor, 0)).unwrap();

    assert_eq!(ret_read, expected);

    let bytes = ret_read.to_bytes().unwrap();
    assert_eq!(bytes, input);
}

#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)]
#[deku(seek_from_current = "skip", ctx = "skip: usize")]
pub struct SeekCtxBefore {
    byte: u8,
}

#[rstest(input, ctx, expected,
    case(&hex!("0003"), 1, SeekCtxBefore{ byte: 0x03 }),
    case(&hex!("000004"), 2, SeekCtxBefore{ byte: 0x04 }),
)]
#[cfg(feature = "std")]
fn test_seek_ctx_before(input: &[u8], ctx: usize, expected: SeekCtxBefore) {
    use std::io::Cursor;
    let input = input.to_vec();

    let mut cursor = std::io::Cursor::new(input.clone());
    let mut reader = Reader::new(&mut cursor);
    let ret_read = SeekCtxBefore::from_reader_with_ctx(&mut reader, ctx).unwrap();

    assert_eq!(ret_read, expected);

    let mut buf = vec![];
    let mut cursor = Cursor::new(&mut buf);
    let mut writer = Writer::new(&mut cursor);
    ret_read.to_writer(&mut writer, ctx).unwrap();
    assert_eq!(buf, input);
}

#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)]
#[deku(seek_from_start = "1")]
pub struct SeekCtxBeforeStart {
    byte: u8,
}

#[rstest(input, expected,
    case(&hex!("0003"), SeekCtxBeforeStart{ byte: 0x03 }),
    case(&hex!("00ff"), SeekCtxBeforeStart{ byte: 0xff }),
)]
#[cfg(feature = "std")]
fn test_seek_ctx_start(input: &[u8], expected: SeekCtxBeforeStart) {
    use std::io::Cursor;
    let input = input.to_vec();

    let mut cursor = std::io::Cursor::new(input.clone());
    let mut reader = Reader::new(&mut cursor);
    let ret_read = SeekCtxBeforeStart::from_reader_with_ctx(&mut reader, ()).unwrap();

    assert_eq!(ret_read, expected);

    let mut buf = vec![];
    let mut cursor = Cursor::new(&mut buf);
    let mut writer = Writer::new(&mut cursor);
    ret_read.to_writer(&mut writer, ()).unwrap();
    assert_eq!(buf, input);
}

#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)]
#[deku(seek_from_end = "-2")]
pub struct SeekCtxBeforeEnd {
    byte: u8,
}

#[rstest(input, expected,
    case(&hex!("000300"), SeekCtxBeforeEnd{ byte: 0x03 }),
    case(&hex!("00ff00"), SeekCtxBeforeEnd{ byte: 0xff }),
)]
#[cfg(feature = "std")]
fn test_seek_ctx_end(input: &[u8], expected: SeekCtxBeforeEnd) {
    use std::io::Cursor;
    let input = input.to_vec();

    let mut cursor = std::io::Cursor::new(input.clone());
    let mut reader = Reader::new(&mut cursor);
    let ret_read = SeekCtxBeforeEnd::from_reader_with_ctx(&mut reader, ()).unwrap();

    assert_eq!(ret_read, expected);

    let mut buf = vec![0, 0, 0];
    let mut cursor = Cursor::new(&mut buf);
    let mut writer = Writer::new(&mut cursor);
    ret_read.to_writer(&mut writer, ()).unwrap();
    assert_eq!(buf, input);
}

#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)]
#[deku(seek_from_start = "0")]
pub struct SeekCtxNoSeek {
    byte: u8,
}

#[rstest(input, expected,
    case(&hex!("03"), SeekCtxNoSeek { byte: 0x03 }),
    case(&hex!("ff"), SeekCtxNoSeek { byte: 0xff }),
)]
#[cfg(feature = "std")]
fn test_seek_ctx_no_seek(input: &[u8], expected: SeekCtxNoSeek) {
    use std::io::Cursor;
    let input = input.to_vec();

    let mut cursor = std::io::Cursor::new(input.clone());
    let mut reader = Reader::new(&mut cursor);
    let ret_read = SeekCtxNoSeek::from_reader_with_ctx(&mut reader, ()).unwrap();

    assert_eq!(ret_read, expected);

    let mut buf = vec![];
    let mut cursor = Cursor::new(&mut buf);
    let mut writer = Writer::new(&mut cursor);
    ret_read.to_writer(&mut writer, ()).unwrap();
    assert_eq!(buf, input);

    let mut buf = vec![];
    let mut cursor = NoSeek::new(&mut buf);
    let mut writer = Writer::new(&mut cursor);
    ret_read.to_writer(&mut writer, ()).unwrap();
    assert_eq!(buf, input);
}
