use std::fmt;
use std::collections::BTreeMap;
use std::default::Default;

use miette::Diagnostic;

use knuffel::{Decode, span::Span};
use knuffel::traits::DecodeChildren;


#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Arg1 {
    #[knuffel(argument)]
    name: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Arg1RawIdent {
    #[knuffel(argument)]
    r#type: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ArgDef {
    #[knuffel(argument, default)]
    name: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ArgDefValue {
    #[knuffel(argument, default="unnamed".into())]
    name: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ArgDefOptValue {
    #[knuffel(argument, default=Some("unnamed".into()))]
    name: Option<String>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct OptArg {
    #[knuffel(argument)]
    name: Option<String>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Extra {
    field: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct VarArg {
    #[knuffel(arguments)]
    params: Vec<u64>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq, Default)]
struct Prop1 {
    #[knuffel(property)]
    label: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq, Default)]
struct Prop1RawIdent {
    #[knuffel(property)]
    r#type: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct PropDef {
    #[knuffel(property, default)]
    label: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct PropDefValue {
    #[knuffel(property, default="unknown".into())]
    label: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct PropDefOptValue {
    #[knuffel(property, default=Some("unknown".into()))]
    label: Option<String>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct PropNamed {
    #[knuffel(property(name="x"))]
    label: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct OptProp {
    #[knuffel(property)]
    label: Option<String>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct VarProp {
    #[knuffel(properties)]
    scores: BTreeMap<String, u64>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Children {
    #[knuffel(children)]
    children: Vec<Arg1>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct FilteredChildren {
    #[knuffel(children(name="left"))]
    left: Vec<OptArg>,
    #[knuffel(children(name="right"))]
    right: Vec<OptArg>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
enum Variant {
    Arg1(Arg1),
    Prop1(Prop1),
    #[knuffel(skip)]
    #[allow(dead_code)]
    Var3(u32),
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Child {
    #[knuffel(child)]
    main: Prop1,
    #[knuffel(child)]
    extra: Option<Prop1>,
    #[knuffel(child)]
    flag: bool,
    #[knuffel(child)]
    унікод: Option<Prop1>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ChildDef {
    #[knuffel(child, default)]
    main: Prop1,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ChildDefValue {
    #[knuffel(child, default=Prop1 { label: String::from("prop1") })]
    main: Prop1,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Unwrap {
    #[knuffel(child, unwrap(argument))]
    label: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct UnwrapRawIdent {
    #[knuffel(child, unwrap(argument))]
    r#type: String,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct UnwrapFiltChildren {
    #[knuffel(children(name="labels"), unwrap(arguments))]
    labels: Vec<Vec<String>>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct UnwrapChildren {
    #[knuffel(children, unwrap(arguments))]
    labels: Vec<Vec<String>>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Parse {
    #[knuffel(child, unwrap(argument, str))]
    listen: std::net::SocketAddr,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct ParseOpt {
    #[knuffel(property, str)]
    listen: Option<std::net::SocketAddr>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct Bytes {
    #[knuffel(child, unwrap(argument, bytes))]
    data: Vec<u8>,
}

#[derive(knuffel_derive::Decode, Debug, PartialEq)]
struct OptBytes {
    #[knuffel(property, bytes)]
    data: Option<Vec<u8>>,
}

fn parse<T: Decode<Span>>(text: &str) -> T {
    let mut nodes: Vec<T> = knuffel::parse("<test>", text).unwrap();
    assert_eq!(nodes.len(), 1);
    nodes.remove(0)
}

fn parse_err<T: Decode<Span>+fmt::Debug>(text: &str) -> String {
    let err = knuffel::parse::<Vec<T>>("<test>", text).unwrap_err();
    err.related().unwrap()
        .map(|e| e.to_string()).collect::<Vec<_>>()
        .join("\n")
}

fn parse_doc<T: DecodeChildren<Span>>(text: &str) -> T {
    knuffel::parse("<test>", text).unwrap()
}

fn parse_doc_err<T: DecodeChildren<Span>+fmt::Debug>(text: &str) -> String {
    let err = knuffel::parse::<T>("<test>", text).unwrap_err();
    err.related().unwrap()
        .map(|e| e.to_string()).collect::<Vec<_>>()
        .join("\n")
}

#[test]
fn parse_arg1() {
    assert_eq!(parse::<Arg1>(r#"node "hello""#),
               Arg1 { name: "hello".into() } );
    assert_eq!(parse_err::<Arg1>(r#"node "hello" "world""#),
        "unexpected argument");
    assert_eq!(parse_err::<Arg1>(r#"(some)node "hello""#),
        "no type name expected for this node");
    assert_eq!(parse_err::<Arg1>(r#"node"#),
        "additional argument `name` is required");
}

#[test]
fn parse_arg1_raw_ident() {
    assert_eq!(parse::<Arg1RawIdent>(r#"node "hello""#),
               Arg1RawIdent { r#type: "hello".into() } );
    assert_eq!(parse_err::<Arg1RawIdent>(r#"node "hello" "world""#),
               "unexpected argument");
    assert_eq!(parse_err::<Arg1RawIdent>(r#"(some)node "hello""#),
               "no type name expected for this node");
    assert_eq!(parse_err::<Arg1RawIdent>(r#"node"#),
               "additional argument `type` is required");
}


#[test]
fn parse_arg_default() {
    assert_eq!(parse::<ArgDef>(r#"node "hello""#),
               ArgDef { name: "hello".into() } );
    assert_eq!(parse_err::<ArgDef>(r#"node "hello" "world""#),
        "unexpected argument");
    assert_eq!(parse::<ArgDef>(r#"node"#),
               ArgDef { name: "".into() } );
}

#[test]
fn parse_arg_def_value() {
    assert_eq!(parse::<ArgDefValue>(r#"node "hello""#),
               ArgDefValue { name: "hello".into() } );
    assert_eq!(parse_err::<ArgDefValue>(r#"node "hello" "world""#),
        "unexpected argument");
    assert_eq!(parse::<ArgDefValue>(r#"node"#),
               ArgDefValue { name: "unnamed".into() } );

    assert_eq!(parse::<ArgDefOptValue>(r#"node "hello""#),
               ArgDefOptValue { name: Some("hello".into()) } );
    assert_eq!(parse_err::<ArgDefValue>(r#"node "hello" "world""#),
        "unexpected argument");
    assert_eq!(parse::<ArgDefOptValue>(r#"node"#),
               ArgDefOptValue { name: Some("unnamed".into()) } );
    assert_eq!(parse::<ArgDefOptValue>(r#"node null"#),
               ArgDefOptValue { name: None } );
}

#[test]
fn parse_opt_arg() {
    assert_eq!(parse::<OptArg>(r#"node "hello""#),
               OptArg { name: Some("hello".into()) } );
    assert_eq!(parse::<OptArg>(r#"node"#),
               OptArg { name: None });
    assert_eq!(parse::<OptArg>(r#"node null"#),
               OptArg { name: None });
}

#[test]
fn parse_prop() {
    assert_eq!(parse::<Prop1>(r#"node label="hello""#),
               Prop1 { label: "hello".into() } );
    assert_eq!(parse_err::<Prop1>(r#"node label="hello" y="world""#),
        "unexpected property `y`");
    assert_eq!(parse_err::<Prop1>(r#"node"#),
        "property `label` is required");
}

#[test]
fn parse_prop_raw_ident() {
    assert_eq!(parse::<Prop1RawIdent>(r#"node type="hello""#),
               Prop1RawIdent { r#type: "hello".into() } );
    assert_eq!(parse_err::<Prop1RawIdent>(r#"node type="hello" y="world""#),
               "unexpected property `y`");
    assert_eq!(parse_err::<Prop1RawIdent>(r#"node"#),
               "property `type` is required");
}

#[test]
fn parse_prop_default() {
    assert_eq!(parse::<PropDef>(r#"node label="hello""#),
               PropDef { label: "hello".into() } );
    assert_eq!(parse::<PropDef>(r#"node"#),
               PropDef { label: "".into() });
}

#[test]
fn parse_prop_def_value() {
    assert_eq!(parse::<PropDefValue>(r#"node label="hello""#),
               PropDefValue { label: "hello".into() } );
    assert_eq!(parse::<PropDefValue>(r#"node"#),
               PropDefValue { label: "unknown".into() });

    assert_eq!(parse::<PropDefOptValue>(r#"node label="hello""#),
               PropDefOptValue { label: Some("hello".into()) } );
    assert_eq!(parse::<PropDefOptValue>(r#"node"#),
               PropDefOptValue { label: Some("unknown".into()) });
    assert_eq!(parse::<PropDefOptValue>(r#"node label=null"#),
               PropDefOptValue { label: None });
}

#[test]
fn parse_prop_named() {
    assert_eq!(parse::<PropNamed>(r#"node x="hello""#),
               PropNamed { label: "hello".into() } );
    assert_eq!(parse_err::<PropNamed>(r#"node label="hello" y="world""#),
        "unexpected property `label`");
    assert_eq!(parse_err::<PropNamed>(r#"node"#),
        "property `x` is required");
}

#[test]
fn parse_unwrap() {
    assert_eq!(parse::<Unwrap>(r#"node { label "hello"; }"#),
               Unwrap { label: "hello".into() } );
    assert_eq!(parse_err::<Unwrap>(r#"node label="hello""#),
        "unexpected property `label`");
    assert_eq!(parse_err::<Unwrap>(r#"node"#),
        "child node `label` is required");
    assert_eq!(parse_doc::<Unwrap>(r#"label "hello""#),
               Unwrap { label: "hello".into() } );
}

#[test]
fn parse_unwrap_raw_ident() {
    assert_eq!(parse::<UnwrapRawIdent>(r#"node { type "hello"; }"#),
               UnwrapRawIdent { r#type: "hello".into() } );
    assert_eq!(parse_err::<UnwrapRawIdent>(r#"node type="hello""#),
               "unexpected property `type`");
    assert_eq!(parse_err::<UnwrapRawIdent>(r#"node"#),
               "child node `type` is required");
    assert_eq!(parse_doc::<UnwrapRawIdent>(r#"type "hello""#),
               UnwrapRawIdent { r#type: "hello".into() } );
}

#[test]
fn parse_unwrap_filtered_children() {
    assert_eq!(parse::<UnwrapFiltChildren>(
       r#"node { labels "hello" "world"; labels "oh" "my"; }"#),
       UnwrapFiltChildren { labels: vec![
           vec!["hello".into(), "world".into()],
           vec!["oh".into(), "my".into()],
       ]},
    );
}

#[test]
fn parse_unwrap_children() {
    assert_eq!(parse::<UnwrapChildren>(
       r#"node { some "hello" "world"; other "oh" "my"; }"#),
       UnwrapChildren { labels: vec![
           vec!["hello".into(), "world".into()],
           vec!["oh".into(), "my".into()],
       ]},
    );
}

#[test]
fn parse_opt_prop() {
    assert_eq!(parse::<OptProp>(r#"node label="hello""#),
               OptProp { label: Some("hello".into()) } );
    assert_eq!(parse::<OptProp>(r#"node"#),
               OptProp { label: None } );
    assert_eq!(parse::<OptProp>(r#"node label=null"#),
               OptProp { label: None } );
}

#[test]
fn parse_var_arg() {
    assert_eq!(parse::<VarArg>(r#"sum 1 2 3"#),
               VarArg { params: vec![1, 2, 3] } );
    assert_eq!(parse::<VarArg>(r#"sum"#),
               VarArg { params: vec![] } );
}

#[test]
fn parse_var_prop() {
    let mut scores = BTreeMap::new();
    scores.insert("john".into(), 13);
    scores.insert("jack".into(), 7);
    assert_eq!(parse::<VarProp>(r#"scores john=13 jack=7"#),
               VarProp { scores } );
    assert_eq!(parse::<VarProp>(r#"scores"#),
               VarProp { scores: BTreeMap::new() } );
}

#[test]
fn parse_children() {
    assert_eq!(parse::<Children>(r#"parent { - "val1"; - "val2"; }"#),
               Children { children: vec! [
                   Arg1 { name: "val1".into() },
                   Arg1 { name: "val2".into() },
               ]} );
    assert_eq!(parse::<Children>(r#"parent"#),
               Children { children: Vec::new() } );

    assert_eq!(parse_doc::<Children>(r#"- "val1"; - "val2""#),
               Children { children: vec! [
                   Arg1 { name: "val1".into() },
                   Arg1 { name: "val2".into() },
               ]} );
    assert_eq!(parse_doc::<Children>(r#""#),
               Children { children: Vec::new() } );
}

#[test]
fn parse_filtered_children() {
    assert_eq!(parse_doc::<FilteredChildren>(
                   r#"left "v1"; right "v2"; left "v3""#),
               FilteredChildren {
                   left: vec![
                       OptArg { name: Some("v1".into()) },
                       OptArg { name: Some("v3".into()) },
                   ],
                   right: vec![
                       OptArg { name: Some("v2".into()) },
                   ]
               });
    assert_eq!(parse_doc::<FilteredChildren>(r#"right; left"#),
               FilteredChildren {
                   left: vec![
                       OptArg { name: None },
                   ],
                   right: vec![
                       OptArg { name: None },
                   ]
               });
    assert_eq!(parse_doc_err::<FilteredChildren>(r#"some"#),
               "unexpected node `some`");
}

#[test]
fn parse_child() {
    assert_eq!(parse::<Child>(r#"parent { main label="val1"; }"#),
               Child {
                   main: Prop1 { label: "val1".into() },
                   extra: None,
                   flag: false,
                   унікод: None,
               });
    assert_eq!(parse::<Child>(r#"parent {
                    main label="primary";
                    extra label="replica";
                 }"#),
               Child {
                   main: Prop1 { label: "primary".into() },
                   extra: Some(Prop1 { label: "replica".into() }),
                   flag: false,
                   унікод: None,
               });
    assert_eq!(parse_err::<Child>(r#"parent { something; }"#),
               "unexpected node `something`\n\
                child node `main` is required");
    assert_eq!(parse_err::<Child>(r#"parent"#),
               "child node `main` is required");

    assert_eq!(parse_doc::<Child>(r#"main label="val1""#),
               Child {
                   main: Prop1 { label: "val1".into() },
                   extra: None,
                   flag: false,
                   унікод: None,
               });
    assert_eq!(parse_doc::<Child>(r#"
                    main label="primary"
                    extra label="replica"
                    flag
                 "#),
               Child {
                   main: Prop1 { label: "primary".into() },
                   extra: Some(Prop1 { label: "replica".into() }),
                   flag: true,
                   унікод: None,
               });
    assert_eq!(parse_doc_err::<Child>(r#"something"#),
               "unexpected node `something`\n\
                child node `main` is required");
    assert_eq!(parse_doc_err::<Child>(r#""#),
               "child node `main` is required");

    assert_eq!(parse_doc::<Child>(r#"
                    main label="primary"
                    унікод label="працює"
                 "#),
               Child {
                   main: Prop1 { label: "primary".into() },
                   extra: None,
                   flag: false,
                   унікод: Some(Prop1 { label: "працює".into() }),
               });
}

#[test]
fn parse_child_def() {
    assert_eq!(parse::<ChildDef>(r#"parent { main label="val1"; }"#),
               ChildDef {
                   main: Prop1 { label: "val1".into() },
               });
    assert_eq!(parse::<ChildDef>(r#"parent"#),
               ChildDef {
                   main: Prop1 { label: "".into() },
               });
}

#[test]
fn parse_child_def_value() {
    assert_eq!(parse::<ChildDefValue>(r#"parent { main label="val1"; }"#),
               ChildDefValue {
                   main: Prop1 { label: "val1".into() },
               });
    assert_eq!(parse::<ChildDefValue>(r#"parent"#),
               ChildDefValue {
                   main: Prop1 { label: "prop1".into() },
               });
}

#[test]
fn parse_enum() {
    assert_eq!(parse::<Variant>(r#"arg1 "hello""#),
               Variant::Arg1(Arg1 { name: "hello".into() }));
    assert_eq!(parse::<Variant>(r#"prop1 label="hello""#),
               Variant::Prop1(Prop1 { label: "hello".into() }));
    assert_eq!(parse_err::<Variant>(r#"something"#),
        "expected one of `arg1`, `prop1`");
}

#[test]
fn parse_str() {
    assert_eq!(parse_doc::<Parse>(r#"listen "127.0.0.1:8080""#),
               Parse { listen: "127.0.0.1:8080".parse().unwrap() });
    assert!(parse_doc_err::<Parse>(r#"listen "2/3""#).contains("invalid"));

    assert_eq!(parse::<ParseOpt>(r#"server listen="127.0.0.1:8080""#),
               ParseOpt { listen: Some("127.0.0.1:8080".parse().unwrap()) });
    assert!(parse_err::<ParseOpt>(r#"server listen="2/3""#).contains("invalid"));
    assert_eq!(parse::<ParseOpt>(r#"server listen=null"#),
               ParseOpt { listen: None });
}

#[test]
fn parse_bytes() {
    assert_eq!(parse_doc::<Bytes>(r#"data (base64)"aGVsbG8=""#),
               Bytes { data: b"hello".to_vec() });
    assert_eq!(parse_doc::<Bytes>(r#"data "world""#),
               Bytes { data: b"world".to_vec() });
    assert_eq!(parse_doc_err::<Bytes>(r#"data (base64)"2/3""#),
        "Invalid padding");

    assert_eq!(parse::<OptBytes>(r#"node data=(base64)"aGVsbG8=""#),
               OptBytes { data: Some(b"hello".to_vec()) });
    assert_eq!(parse::<OptBytes>(r#"node data="world""#),
               OptBytes { data: Some(b"world".to_vec()) });
    assert_eq!(parse::<OptBytes>(r#"node data=null"#),
               OptBytes { data: None });
}

#[test]
fn parse_extra() {
    assert_eq!(parse::<Extra>(r#"data"#),
               Extra { field: "".into() });
    assert_eq!(parse_err::<Extra>(r#"data x=1"#),
        "unexpected property `x`");
}
