use snapbox::prelude::*;
use snapbox::str;

use crate::ascii::dec_uint;
use crate::ascii::digit0;
use crate::combinator::dispatch;
use crate::combinator::empty;
use crate::combinator::fail;
use crate::combinator::seq;
use crate::combinator::unordered_seq;
use crate::prelude::*;
use crate::stream::Partial;
use crate::token::any;

#[test]
fn dispatch_basics() {
    fn escape_seq_char<'i>(input: &mut &'i str) -> TestResult<&'i str, char> {
        dispatch! {any;
            'b' => empty.value('\u{8}'),
            'f' => empty.value('\u{c}'),
            'n' => empty.value('\n'),
            'r' => empty.value('\r'),
            't' => empty.value('\t'),
            '\\' => empty.value('\\'),
            '"' => empty.value('"'),
            _ => fail::<_, char, _>,
        }
        .parse_next(input)
    }
    assert_parse!(
        escape_seq_char.parse_peek("b123"),
        str![[r#"
Ok(
    (
        "123",
        '\u{8}',
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        escape_seq_char.parse_peek("error"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "rror",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        escape_seq_char.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_struct_basics() {
    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
            }
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point {
            x: 123,
            y: 4,
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_struct_default_init() {
    #[derive(Debug, PartialEq, Default)]
    struct Point {
        x: u32,
        y: u32,
        z: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                ..Default::default()
            }
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point {
            x: 123,
            y: 4,
            z: 0,
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_struct_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty,
            }
        }
        .parse_next(input)
    }
}

#[test]
fn seq_struct_no_trailing_comma() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint
            }
        }
        .parse_next(input)
    }
}

#[test]
fn seq_struct_no_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty
            }
        }
        .parse_next(input)
    }
}

#[test]
fn seq_enum_struct_variant() {
    #[derive(Debug, PartialEq, Eq)]
    enum Expr {
        Add { lhs: u32, rhs: u32 },
        Mul(u32, u32),
    }

    fn add<'i>(input: &mut &'i [u8]) -> TestResult<&'i [u8], Expr> {
        seq! {Expr::Add {
            lhs: dec_uint::<_, u32, _>,
            _: b" + ",
            rhs: dec_uint::<_, u32, _>,
        }}
        .parse_next(input)
    }

    fn mul<'i>(input: &mut &'i [u8]) -> TestResult<&'i [u8], Expr> {
        seq!(Expr::Mul(
             dec_uint::<_, u32, _>,
             _: b" * ",
             dec_uint::<_, u32, _>,
        ))
        .parse_next(input)
    }

    assert_parse!(
        add.parse_peek(&b"1 + 2"[..]),
        str![[r#"
Ok(
    (
        [],
        Add {
            lhs: 1,
            rhs: 2,
        },
    ),
)

"#]]
        .raw()
    );

    assert_parse!(
        mul.parse_peek(&b"3 * 4"[..]),
        str![[r#"
Ok(
    (
        [],
        Mul(
            3,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_struct_borrow() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        let mut dec_uint = digit0.parse_to();
        seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty
            }
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_struct_basics() {
    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
            )
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point(
            123,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_tuple_struct_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty,
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_struct_no_trailing_comma() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_struct_no_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_basics() {
    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
            )
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        (
            123,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn seq_tuple_trailing_comma_elided() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
                _: empty,
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_no_trailing_comma() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        seq! {
            (
                dec_uint,
                _: ',',
                dec_uint
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_no_trailing_comma_elided() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_no_parens() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        seq! (
            dec_uint,
            _: ',',
            dec_uint,
        )
        .parse_next(input)
    }
}

#[test]
fn seq_tuple_borrow() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        let mut dec_uint = digit0.parse_to();
        seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_struct_basics() {
    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
            }
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point {
            x: 123,
            y: 4,
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn unordered_seq_struct_default_init() {
    #[derive(Debug, PartialEq, Default)]
    struct Point {
        x: u32,
        y: u32,
        z: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                ..Default::default()
            }
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point {
            x: 123,
            y: 4,
            z: 0,
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn unordered_seq_struct_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty,
            }
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_struct_no_trailing_comma() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint
            }
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_struct_no_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty
            }
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_enum_struct_variant() {
    #[derive(Debug, PartialEq, Eq)]
    enum Expr {
        Add { lhs: u32, rhs: u32 },
        Mul(u32, u32),
    }

    fn add<'i>(input: &mut &'i [u8]) -> TestResult<&'i [u8], Expr> {
        unordered_seq! {Expr::Add {
            lhs: dec_uint::<_, u32, _>,
            _: b" + ",
            rhs: dec_uint::<_, u32, _>,
        }}
        .parse_next(input)
    }

    fn mul<'i>(input: &mut &'i [u8]) -> TestResult<&'i [u8], Expr> {
        unordered_seq!(Expr::Mul(
             dec_uint::<_, u32, _>,
             _: b" * ",
             dec_uint::<_, u32, _>,
        ))
        .parse_next(input)
    }

    assert_parse!(
        add.parse_peek(&b"1 + 2"[..]),
        str![[r#"
Ok(
    (
        [],
        Add {
            lhs: 1,
            rhs: 2,
        },
    ),
)

"#]]
        .raw()
    );

    assert_parse!(
        mul.parse_peek(&b"3 * 4"[..]),
        str![[r#"
Ok(
    (
        [],
        Mul(
            3,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn unordered_seq_struct_borrow() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point {
        x: u32,
        y: u32,
    }

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        let mut dec_uint = digit0.parse_to();
        unordered_seq! {
            Point {
                x: dec_uint,
                _: ',',
                y: dec_uint,
                _: empty
            }
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_struct_basics() {
    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
            )
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        Point(
            123,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn unordered_seq_tuple_struct_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty,
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_struct_no_trailing_comma() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_struct_no_trailing_comma_elided() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        unordered_seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_basics() {
    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        unordered_seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
            )
        }
        .parse_next(input)
    }
    assert_parse!(
        parser.parse_peek("123,4 remaining"),
        str![[r#"
Ok(
    (
        " remaining",
        (
            123,
            4,
        ),
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek("123, remaining"),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: " remaining",
        },
    ),
)

"#]]
        .raw()
    );
    assert_parse!(
        parser.parse_peek(""),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: "",
        },
    ),
)

"#]]
        .raw()
    );
}

#[test]
fn unordered_seq_tuple_trailing_comma_elided() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        unordered_seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
                _: empty,
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_no_trailing_comma() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        unordered_seq! {
            (
                dec_uint,
                _: ',',
                dec_uint
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_no_trailing_comma_elided() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        unordered_seq! {
            (
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_no_parens() {
    #![allow(dead_code)]

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, (u32, u32)> {
        unordered_seq! (
            dec_uint,
            _: ',',
            dec_uint,
        )
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_tuple_borrow() {
    #![allow(dead_code)]

    #[derive(Debug, PartialEq)]
    struct Point(u32, u32);

    fn parser<'i>(input: &mut &'i str) -> TestResult<&'i str, Point> {
        let mut dec_uint = digit0.parse_to();
        unordered_seq! {
            Point(
                dec_uint,
                _: ',',
                dec_uint,
                _: empty
            )
        }
        .parse_next(input)
    }
}

#[test]
fn unordered_seq_permutation_test() {
    #[allow(clippy::type_complexity)]
    fn perm<'i>(
        i: &mut Partial<&'i [u8]>,
    ) -> TestResult<Partial<&'i [u8]>, (&'i [u8], &'i [u8], &'i [u8])> {
        unordered_seq!(("abcd", "efg", "hi")).parse_next(i)
    }

    let a = &b"abcdefghijk"[..];
    assert_parse!(
        perm.parse_peek(Partial::new(a)),
        str![[r#"
Ok(
    (
        Partial {
            input: [
                106,
                107,
            ],
            partial: true,
        },
        (
            [
                97,
                98,
                99,
                100,
            ],
            [
                101,
                102,
                103,
            ],
            [
                104,
                105,
            ],
        ),
    ),
)

"#]]
        .raw()
    );
    let b = &b"efgabcdhijk"[..];
    assert_parse!(
        perm.parse_peek(Partial::new(b)),
        str![[r#"
Ok(
    (
        Partial {
            input: [
                106,
                107,
            ],
            partial: true,
        },
        (
            [
                97,
                98,
                99,
                100,
            ],
            [
                101,
                102,
                103,
            ],
            [
                104,
                105,
            ],
        ),
    ),
)

"#]]
        .raw()
    );
    let c = &b"hiefgabcdjk"[..];
    assert_parse!(
        perm.parse_peek(Partial::new(c)),
        str![[r#"
Ok(
    (
        Partial {
            input: [
                106,
                107,
            ],
            partial: true,
        },
        (
            [
                97,
                98,
                99,
                100,
            ],
            [
                101,
                102,
                103,
            ],
            [
                104,
                105,
            ],
        ),
    ),
)

"#]]
        .raw()
    );

    let d = &b"efgxyzabcdefghi"[..];
    assert_parse!(
        perm.parse_peek(Partial::new(d)),
        str![[r#"
Err(
    Backtrack(
        InputError {
            input: Partial {
                input: [
                    120,
                    121,
                    122,
                    97,
                    98,
                    99,
                    100,
                    101,
                    102,
                    103,
                    104,
                    105,
                ],
                partial: true,
            },
        },
    ),
)

"#]]
        .raw()
    );

    let e = &b"efgabc"[..];
    assert_parse!(
        perm.parse_peek(Partial::new(e)),
        str![[r#"
Err(
    Incomplete(
        Unknown,
    ),
)

"#]]
        .raw()
    );
}
