#![cfg(test)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::vec_init_then_push)]
//use test_case::test_case;
use std::{fmt::Write, rc::Rc};

use pretty_assertions::assert_eq;

use super::{new::schala_parser, tokenize, ParseResult, Parser};
use crate::{ast::*, tokenizing::Location};

fn rc(s: &str) -> Rc<String> {
    Rc::new(s.to_owned())
}

fn bx<T>(item: T) -> Box<T> {
    Box::new(item)
}

fn make_parser(input: &str) -> Parser {
    let tokens: Vec<crate::tokenizing::Token> = tokenize(input);
    let mut parser = super::Parser::new();
    parser.add_new_tokens(tokens);
    parser
}

fn parse(input: &str) -> ParseResult<AST> {
    let mut parser = make_parser(input);
    parser.parse()
}

fn stmt(kind: StatementKind) -> Statement {
    Statement { location: Location::default(), id: ItemId::default(), kind }
}

fn exst(kind: ExpressionKind) -> Statement {
    Statement {
        location: Location::default(),
        id: ItemId::default(),
        kind: StatementKind::Expression(expr(kind)),
    }
}

fn decl(declaration: Declaration) -> Statement {
    Statement {
        location: Location::default(),
        id: ItemId::default(),
        kind: StatementKind::Declaration(declaration),
    }
}

fn fn_decl(sig: Signature, stmts: Block) -> Statement {
    Statement {
        kind: StatementKind::Declaration(Declaration::FuncDecl(sig, stmts)),
        location: Default::default(),
        id: Default::default(),
    }
}

fn expr_anno(kind: ExpressionKind, anno: TypeIdentifier) -> Expression {
    Expression { id: ItemId::default(), kind, type_anno: Some(anno) }
}

fn expr(kind: ExpressionKind) -> Expression {
    Expression { id: ItemId::default(), kind, type_anno: None }
}

fn binop(sigil: &str, lhs: Expression, rhs: Expression) -> Expression {
    Expression {
        id: Default::default(),
        type_anno: None,
        kind: ExpressionKind::BinExp(BinOp::from_sigil(sigil), Box::new(lhs), Box::new(rhs)),
    }
}

fn prefixop(sigil: &str, exp: Expression) -> Expression {
    Expression {
        id: Default::default(),
        type_anno: None,
        kind: ExpressionKind::PrefixExp(PrefixOp::from_sigil(sigil), Box::new(exp)),
    }
}

macro_rules! qn {
  ( $( $component:ident),* ) => {
    {
        let mut components = vec![];
        $(
        components.push(rc(stringify!($component)));
        )*
        QualifiedName { components, id: Default::default() }
    }
  };
}

fn ty_simple(name: &str) -> TypeIdentifier {
    TypeIdentifier::Singleton(TypeSingletonName { name: rc(name), params: vec![] })
}

macro_rules! assert_ast {
    ($input:expr, $statements:expr) => {
        let ast = parse($input).unwrap();
        let expected = AST { id: Default::default(), statements: $statements.into() };
        println!("Expected: {}", expected);
        println!("Actual: {}", ast);
        assert_eq!(ast, expected);
    };
}

macro_rules! assert_ast2 {
    ($input:expr, $statements:expr) => {
        let ast = schala_parser::program($input);
        let expected = AST { id: Default::default(), statements: $statements.into() };
        if ast.is_err() {
            println!("Parse error: {}", ast.unwrap_err());
            panic!();
        }
        assert_eq!(ast.unwrap(), expected);
    };
}

macro_rules! assert_fail {
    ($input:expr, $failure:expr) => {
        let err = parse($input).unwrap_err();
        assert_eq!(err.msg, $failure);
    };
}

macro_rules! assert_fail2 {
    ($input:expr, $failure:expr) => {
        let err = schala_parser::program($input).unwrap_err();
        assert_eq!(err.to_string(), $failure);
    };
}

macro_rules! assert_expr2 {
    ($input:expr, $correct:expr) => {
        let expr = schala_parser::expression($input);
        if expr.is_err() {
            println!("Expression parse error: {}", expr.unwrap_err());
            panic!();
        }
        assert_eq!(expr.unwrap(), $correct);
    };
}

macro_rules! assert_fail_expr2 {
    ($input:expr, $failure:expr) => {
        let _err = schala_parser::expression($input).unwrap_err();
        //TODO make real tests for failures
        //assert_eq!(err.to_string(), $failure);
    };
}
#[test]
fn basic_literals() {
    use ExpressionKind::*;

    assert_expr2!(".2", expr(FloatLiteral(0.2)));
    assert_expr2!("8.1", expr(FloatLiteral(8.1)));
    assert_expr2!("0b010", expr(NatLiteral(2)));
    assert_expr2!("0b0_1_0", expr(NatLiteral(2)));
    assert_expr2!("0xff", expr(NatLiteral(255)));
    assert_expr2!("0x032f", expr(NatLiteral(815)));
    assert_expr2!("0xf_f_", expr(NatLiteral(255)));
    assert_expr2!("false", expr(BoolLiteral(false)));
    assert_expr2!("true", expr(BoolLiteral(true)));
    assert_expr2!(r#""hello""#, expr(StringLiteral(rc("hello"))));
}

#[test]
fn list_literals() {
    use ExpressionKind::*;

    assert_expr2!("[]", expr(ListLiteral(vec![])));
    assert_expr2!("[1,2]", expr(ListLiteral(vec![expr(NatLiteral(1)), expr(NatLiteral(2)),])));
    assert_fail_expr2!("[1,,2]", "some failure");
}

#[test]
fn binexps() {
    use ExpressionKind::*;
    use StatementKind::Expression;

    assert_expr2!("0xf_f_+1", binop("+", expr(NatLiteral(255)), expr(NatLiteral(1))));
    assert_ast2!(
        "3; 4; 4.3",
        vec![
            stmt(Expression(expr(NatLiteral(3)))),
            stmt(Expression(expr(NatLiteral(4)))),
            stmt(Expression(expr(FloatLiteral(4.3)))),
        ]
    );

    assert_expr2!(
        "1 + 2 * 3",
        binop("+", expr(NatLiteral(1)), binop("*", expr(NatLiteral(2)), expr(NatLiteral(3))))
    );
    assert_expr2!(
        "1 * 2 + 3",
        binop("+", binop("*", expr(NatLiteral(1)), expr(NatLiteral(2))), expr(NatLiteral(3)))
    );
    assert_expr2!("1 && 2", binop("&&", expr(NatLiteral(1)), expr(NatLiteral(2))));
    assert_expr2!(
        "1 + 2 * 3 + 4",
        binop(
            "+",
            binop("+", expr(NatLiteral(1)), binop("*", expr(NatLiteral(2)), expr(NatLiteral(3)))),
            expr(NatLiteral(4))
        )
    );
    assert_expr2!(
        "(1 + 2) * 3",
        binop("*", binop("+", expr(NatLiteral(1)), expr(NatLiteral(2))), expr(NatLiteral(3)))
    );
    assert_expr2!(".1 + .2", binop("+", expr(FloatLiteral(0.1)), expr(FloatLiteral(0.2))));
    assert_expr2!("1 / 2.", binop("/", expr(NatLiteral(1)), expr(FloatLiteral(2.))));
}

#[test]
fn prefix_exps() {
    use ExpressionKind::*;

    assert_expr2!("-3", prefixop("-", expr(NatLiteral(3))));
    assert_expr2!("-0.2", prefixop("-", expr(FloatLiteral(0.2))));
    assert_expr2!("!3", prefixop("!", expr(NatLiteral(3))));
    assert_expr2!("!t", prefixop("!", expr(Value(qn!(t)))));
    assert_expr2!("a <- -b", binop("<-", expr(Value(qn!(a))), prefixop("-", expr(Value(qn!(b))))));
    assert_expr2!("a <--b", binop("<--", expr(Value(qn!(a))), expr(Value(qn!(b)))));
}

#[test]
fn operators() {
    use ExpressionKind::*;

    assert_expr2!("a <- 1", binop("<-", expr(Value(qn!(a))), expr(NatLiteral(1))));
    assert_expr2!("a || 1", binop("||", expr(Value(qn!(a))), expr(NatLiteral(1))));
    assert_expr2!("a <> 1", binop("<>", expr(Value(qn!(a))), expr(NatLiteral(1))));
}

#[test]
fn accessors() {
    use ExpressionKind::*;

    assert_expr2!("a.b", expr(Access { name: rc("b"), expr: bx(expr(Value(qn!(a)))) }));
    assert_expr2!(
        "a.b.c",
        expr(Access {
            name: rc("c"),
            expr: bx(expr(Access { name: rc("b"), expr: bx(expr(Value(qn!(a)))) }))
        })
    );
    assert_expr2!(
        "a.b.c(3)",
        expr(Call {
            f: bx(expr(Access {
                name: rc("c"),
                expr: bx(expr(Access { name: rc("b"), expr: bx(expr(Value(qn!(a)))) }))
            })),
            arguments: vec![InvocationArgument::Positional(expr(NatLiteral(3)))],
        })
    );
    assert_expr2!(
        "a.b().c",
        expr(Access {
            name: rc("c"),
            expr: bx(expr(Call {
                f: bx(expr(Access { name: rc("b"), expr: bx(expr(Value(qn!(a)))) })),
                arguments: vec![]
            }))
        })
    );
}

#[test]
fn tuples() {
    use ExpressionKind::*;

    assert_expr2!("()", expr(TupleLiteral(vec![])));
    assert_expr2!(
        r#"("hella", 34)"#,
        expr(TupleLiteral(vec![expr(StringLiteral(rc("hella"))), expr(NatLiteral(34))]))
    );
    assert_expr2!(
        r#"(1+2, "slough")"#,
        expr(TupleLiteral(vec![
            binop("+", expr(NatLiteral(1)), expr(NatLiteral(2))),
            expr(StringLiteral(rc("slough"))),
        ]))
    );
}

#[test]
fn identifiers() {
    use ExpressionKind::*;

    assert_expr2!("a", expr(Value(qn!(a))));
    assert_expr2!("some_value", expr(Value(qn!(some_value))));
    assert_expr2!("alpha::beta::gamma", expr(Value(qn!(alpha, beta, gamma))));
    assert_expr2!("a + b", binop("+", expr(Value(qn!(a))), expr(Value(qn!(b)))));
    assert_expr2!("None", expr(Value(qn!(None))));
    assert_expr2!(
        "thing::item::call()",
        expr(Call { f: bx(expr(Value(qn!(thing, item, call)))), arguments: vec![] })
    );
}

#[test]
fn named_struct() {
    use ExpressionKind::*;
    assert_expr2!(
        "Pandas {  a: x + y }",
        expr(NamedStruct {
            name: qn!(Pandas),
            fields: vec![(rc("a"), binop("+", expr(Value(qn!(x))), expr(Value(qn!(y)))))]
        })
    );
    assert_expr2!(
        "Trousers {  a:1, b:800 }",
        expr(NamedStruct {
            name: qn!(Trousers),
            fields: vec![(rc("a"), expr(NatLiteral(1))), (rc("b"), expr(NatLiteral(800)))]
        })
    );
}

#[test]
fn index() {
    use ExpressionKind::*;
    assert_expr2!(
        "armok[b,c]",
        expr(Index {
            indexee: bx(expr(Value(qn!(armok)))),
            indexers: vec![expr(Value(qn!(b))), expr(Value(qn!(c)))]
        })
    );
    assert_expr2!(
        "a[b,c][1]",
        expr(Index {
            indexee: bx(expr(Index {
                indexee: bx(expr(Value(qn!(a)))),
                indexers: vec![expr(Value(qn!(b))), expr(Value(qn!(c)))]
            })),
            indexers: vec![expr(NatLiteral(1))]
        })
    );
    assert_expr2!(
        "perspicacity()[a]",
        expr(Index {
            indexee: bx(expr(Call { f: bx(expr(Value(qn!(perspicacity)))), arguments: vec![] })),
            indexers: vec![expr(Value(qn!(a)))]
        })
    );

    let a = expr(Call { f: bx(expr(Value(qn!(a)))), arguments: vec![] });
    let b = expr(Index { indexee: bx(a), indexers: vec![expr(Value(qn!(b)))] });
    let c = expr(Call { f: bx(b), arguments: vec![] });
    let d = expr(Index { indexee: bx(c), indexers: vec![expr(Value(qn!(d)))] });
    assert_expr2!("a()[b]()[d]", d);

    assert_fail_expr2!("a[]", "Empty index expressions are not allowed");
}

#[test]
fn while_expression() {
    use ExpressionKind::*;

    assert_expr2!("while { }", expr(WhileExpression { condition: None, body: Block::default() }));
    assert_expr2!(
        "while a == b { }",
        expr(WhileExpression {
            condition: Some(bx(binop("==", expr(Value(qn!(a))), expr(Value(qn!(b)))))),
            body: Block::default()
        })
    );
}

#[test]
fn for_expression() {
    use ExpressionKind::*;

    assert_expr2!(
        "for { a <- garodzny::maybeValue } return 1",
        expr(ForExpression {
            enumerators: vec![Enumerator { id: rc("a"), generator: expr(Value(qn!(garodzny, maybeValue))) }],
            body: bx(ForBody::MonadicReturn(expr(NatLiteral(1))))
        })
    );

    assert_expr2!(
        "for n <- someRange { f(n) ; }",
        expr(ForExpression {
            enumerators: vec![Enumerator { id: rc("n"), generator: expr(Value(qn!(someRange))) }],
            body: bx(ForBody::StatementBlock(
                vec![stmt(StatementKind::Expression(expr(Call {
                    f: bx(expr(Value(qn!(f)))),
                    arguments: vec![InvocationArgument::Positional(expr(Value(qn!(n))))],
                }))),]
                .into()
            )),
        })
    );
}

#[test]
fn lambda_expressions() {
    use ExpressionKind::*;

    assert_expr2!(
        r#"\(x) { x + 1}"#,
        expr(Lambda {
            params: vec![FormalParam { name: rc!(x), anno: None, default: None }],
            type_anno: None,
            body:
                vec![stmt(StatementKind::Expression(binop("+", expr(Value(qn!(x))), expr(NatLiteral(1))))),]
                    .into()
        })
    );

    assert_expr2!(
        r#"\  (x: Int, y) { a;b;c;}"#,
        expr(Lambda {
            params: vec![
                FormalParam { name: rc!(x), anno: Some(ty_simple("Int")), default: None },
                FormalParam { name: rc!(y), anno: None, default: None },
            ],
            type_anno: None,
            body: vec![
                stmt(StatementKind::Expression(expr(Value(qn!(a))))),
                stmt(StatementKind::Expression(expr(Value(qn!(b))))),
                stmt(StatementKind::Expression(expr(Value(qn!(c))))),
            ]
            .into()
        })
    );

    assert_expr2!(
        r#"\(x){y}(1)"#,
        expr(Call {
            f: bx(expr(Lambda {
                params: vec![FormalParam { name: rc!(x), anno: None, default: None },],
                type_anno: None,
                body: vec![stmt(StatementKind::Expression(expr(Value(qn!(y))))),].into()
            })),
            arguments: vec![InvocationArgument::Positional(expr(NatLiteral(1)))],
        })
    );

    assert_expr2!(
        r#"\(x: Int): String { "q" }"#,
        expr(Lambda {
            params: vec![FormalParam { name: rc!(x), anno: Some(ty_simple("Int")), default: None },],
            type_anno: Some(TypeIdentifier::Singleton(TypeSingletonName {
                name: rc("String"),
                params: vec![]
            })),
            body: vec![stmt(StatementKind::Expression(expr(StringLiteral(rc("q"))))),].into()
        })
    );
}

#[test]
fn single_param_lambda() {
    use ExpressionKind::*;

    assert_expr2!(
        r#"\x { x + 10 }"#,
        expr(Lambda {
            params: vec![FormalParam { name: rc!(x), anno: None, default: None },],
            type_anno: None,
            body: vec![stmt(StatementKind::Expression(binop(
                "+",
                expr(Value(qn!(x))),
                expr(NatLiteral(10))
            )))]
            .into()
        })
    );

    assert_expr2!(
        r#"\x: Int { x + 10 }"#,
        expr(Lambda {
            params: vec![FormalParam { name: rc!(x), anno: Some(ty_simple("Int")), default: None },],
            type_anno: None,
            body: vec![stmt(StatementKind::Expression(binop(
                "+",
                expr(Value(qn!(x))),
                expr(NatLiteral(10))
            )))]
            .into()
        })
    );
}

#[test]
fn complex_lambdas() {
    use ExpressionKind::*;

    //TODO support this without the semicolon after the lambda
    assert_ast2! {
    r#"fn wahoo() { let a = 10; \(x) { x + a }; }
    wahoo()(3) "#,
    vec![
        fn_decl(Signature { name: rc("wahoo"), operator: false, type_anno: None, params: vec![] },
        vec![
            decl(Declaration::Binding {
            name: rc("a"),
            constant: true,
            type_anno: None,
            expr: expr(NatLiteral(10))
        }),
        stmt(StatementKind::Expression(expr(Lambda {
            params: vec![
                FormalParam { name: rc("x"), default: None, anno: None }
            ],
            type_anno: None,
            body: vec![
                stmt(StatementKind::Expression(binop("+", expr(Value(qn!(x))), expr(Value(qn!(a)))))),
            ].into()
        }))),
        ].into()),
        stmt(StatementKind::Expression(expr(Call {
            f: bx(expr(Call {
                f: bx(expr(Value(qn!(wahoo)))),
                arguments: vec![] })),
            arguments: vec![
            InvocationArgument::Positional(expr(NatLiteral(3)))
            ]
        })))
    ]
    };
}

#[test]
fn reserved_words() {
    assert_fail!("module::item::call()", "Expected an identifier, got Colon");
}

#[test]
fn type_annotations() {
    use ExpressionKind::*;
    use TypeIdentifier::*;

    assert_ast2!(
        "let a = b : Int",
        vec![decl(Declaration::Binding {
            name: rc("a"),
            constant: true,
            type_anno: None,
            expr: expr_anno(Value(qn!(b)), Singleton(TypeSingletonName { name: rc("Int"), params: vec![] })),
        })]
    );

    assert_expr2!(
        "a: Int",
        expr_anno(Value(qn!(a)), Singleton(TypeSingletonName { name: rc("Int"), params: vec![] }))
    );
    assert_expr2!(
        "a: Option<Int>",
        expr_anno(
            Value(qn!(a)),
            Singleton(TypeSingletonName {
                name: rc("Option"),
                params: vec![Singleton(TypeSingletonName { name: rc("Int"), params: vec![] })]
            })
        )
    );
    assert_expr2!(
        "a: KoreanBBQSpecifier<Kimchi, Option<Bulgogi> >",
        expr_anno(
            Value(qn!(a)),
            Singleton(TypeSingletonName {
                name: rc("KoreanBBQSpecifier"),
                params: vec![
                    Singleton(TypeSingletonName { name: rc("Kimchi"), params: vec![] }),
                    Singleton(TypeSingletonName {
                        name: rc("Option"),
                        params: vec![Singleton(TypeSingletonName { name: rc("Bulgogi"), params: vec![] })]
                    })
                ]
            })
        )
    );
    assert_expr2!(
        "a: (Int, Yolo<a>)",
        expr_anno(
            Value(qn!(a)),
            Tuple(vec![
                Singleton(TypeSingletonName { name: rc("Int"), params: vec![] }),
                Singleton(TypeSingletonName {
                    name: rc("Yolo"),
                    params: vec![Singleton(TypeSingletonName { name: rc("a"), params: vec![] })]
                }),
            ]),
        )
    );
}

#[test]
fn type_declarations() {
    use Declaration::TypeDecl;
    assert_ast2! {
        "type Alpha = Alpha", vec![
            decl(TypeDecl {
                name: TypeSingletonName { name: rc("Alpha"), params: vec![] },
                mutable: false,
                body: TypeBody::Variants(vec![
                    Variant {
                        id: Default::default(),
                        name: rc("Alpha"),
                        kind: VariantKind::UnitStruct
                    }
                ])
            })
        ]
    };

    assert_ast2!(
        "type mut Kuah = Kuah",
        decl(TypeDecl {
            name: TypeSingletonName { name: rc("Kuah"), params: vec![] },
            mutable: true,
            body: TypeBody::Variants(vec![Variant {
                id: Default::default(),
                name: rc("Kuah"),
                kind: VariantKind::UnitStruct
            }])
        })
    );

    assert_ast2! {
        "type Alpha = Alpha { a: Int, b: Int }",
        vec![decl(TypeDecl {
            name: TypeSingletonName { name: rc("Alpha"), params: vec![] },
            mutable: false,
            body: TypeBody::Variants(vec![
                Variant {
                    id: Default::default(),
                    name: rc("Alpha"),
                    kind: VariantKind::Record(vec![
                        (rc("a"), ty_simple("Int")),
                        (rc("b"), ty_simple("Int"))
                    ])
                }
            ])
        })]
    };

    assert_ast2! {
        "type Alpha = { a: Int, b: Int }",
        vec![decl(TypeDecl {
            name: TypeSingletonName { name: rc("Alpha"), params: vec![] },
            mutable: false,
            body: TypeBody::ImmediateRecord(Default::default(), vec![
                        (rc("a"), ty_simple("Int")),
                        (rc("b"), ty_simple("Int"))
                    ])

        })]
    };

    assert_ast2!(
        "type Option<T> = None | Some(T)",
        vec![decl(TypeDecl {
            name: TypeSingletonName {
                name: rc("Option"),
                params: vec![TypeIdentifier::Singleton(TypeSingletonName { name: rc("T"), params: vec![] })]
            },
            mutable: false,
            body: TypeBody::Variants(vec![
                Variant { id: Default::default(), name: rc("None"), kind: VariantKind::UnitStruct },
                Variant {
                    id: Default::default(),
                    name: rc("Some"),
                    kind: VariantKind::TupleStruct(vec![TypeIdentifier::Singleton(TypeSingletonName {
                        name: rc("T"),
                        params: vec![]
                    })])
                },
            ])
        })]
    );

    assert_ast2!(
        "type alias Alpha = Beta",
        decl(Declaration::TypeAlias { alias: rc("Alpha"), original: rc("Beta") })
    );

    assert_ast2!("type Complex<T, U> = Unit | Record { field: AnotherType<Bool>, field2: (Nat, Int), field3: T } | Tuple(Int, (String, T))",
    decl(TypeDecl {
        name: TypeSingletonName { name: rc("Complex"), params: vec![
            TypeIdentifier::Singleton(TypeSingletonName { name: rc("T"), params: vec![] }),
            TypeIdentifier::Singleton(TypeSingletonName { name: rc("U"), params: vec![] }),
        ] },
        mutable: false,
        body: TypeBody::Variants(vec![
            Variant { id: Default::default(), name: rc("Unit"), kind: VariantKind::UnitStruct },
            Variant { id: Default::default(), name: rc("Record"), kind: VariantKind::Record(
                vec![
                    (rc("field"), TypeIdentifier::Singleton(TypeSingletonName { name: rc("AnotherType"), params: vec![TypeIdentifier::Singleton(TypeSingletonName { name: rc("Bool"), params: vec![] })] })),
                    (rc("field2"), TypeIdentifier::Tuple(vec![
                        TypeIdentifier::Singleton(TypeSingletonName { name: rc("Nat"), params: vec![] }),
                        TypeIdentifier::Singleton(TypeSingletonName { name: rc("Int"), params: vec![] }),
                    ]
                    )),
                    (rc("field3"), TypeIdentifier::Singleton(TypeSingletonName { name: rc("T"), params: vec![] })),
                ]
            )},
            Variant { id: Default::default(), name: rc("Tuple"), kind: VariantKind::TupleStruct(
                vec![
                    TypeIdentifier::Singleton(TypeSingletonName { name: rc("Int"), params: vec![] }),
                    TypeIdentifier::Tuple(vec![
                        TypeIdentifier::Singleton(TypeSingletonName { name: rc("String"), params: vec![] }),
                        TypeIdentifier::Singleton(TypeSingletonName { name: rc("T"), params: vec![] }),
                    ])
                ]
            )},
        ]),
    }));
}

#[test]
fn declarations() {
    use ExpressionKind::*;

    assert_ast2!(
        "let q_q = Yolo::Swaggins",
        vec![decl(Declaration::Binding {
            name: rc("q_q"),
            constant: true,
            type_anno: None,
            expr: expr(Value(qn!(Yolo, Swaggins)))
        })]
    );
}

#[test]
fn bindings() {
    use ExpressionKind::*;

    assert_ast2!(
        "let mut a = 10",
        vec![decl(Declaration::Binding {
            name: rc("a"),
            constant: false,
            type_anno: None,
            expr: expr(NatLiteral(10)),
        })]
    );

    assert_ast2!(
        "let a = 2 + a",
        vec![stmt(StatementKind::Declaration(Declaration::Binding {
            name: rc("a"),
            constant: true,
            type_anno: None,
            expr: binop("+", expr(NatLiteral(2)), expr(Value(qn!(a)))),
        }))]
    );

    assert_ast2!(
        "let a: Nat = 2",
        vec![stmt(StatementKind::Declaration(Declaration::Binding {
            name: rc("a"),
            constant: true,
            type_anno: Some(TypeIdentifier::Singleton(TypeSingletonName { name: rc("Nat"), params: vec![] })),
            expr: expr(NatLiteral(2)),
        }))]
    );
}

#[test]
fn functions() {
    use ExpressionKind::*;
    assert_ast2!(
        "fn oi()",
        vec![stmt(StatementKind::Declaration(Declaration::FuncSig(Signature {
            name: rc("oi"),
            operator: false,
            params: vec![],
            type_anno: None
        })))]
    );

    assert_ast2!(
        "oi()",
        vec![stmt(StatementKind::Expression(expr(Call { f: bx(expr(Value(qn!(oi)))), arguments: vec![] })))]
    );

    assert_expr2!(
        "oi(a, 2+2)",
        expr(Call {
            f: bx(expr(Value(qn!(oi)))),
            arguments: vec![
                InvocationArgument::Positional(expr(Value(qn!(a)))),
                InvocationArgument::Positional(binop("+", expr(NatLiteral(2)), expr(NatLiteral(2)))),
            ]
        })
    );
    assert_fail!("a(b,,c)", "Expected a literal expression, got Comma");

    assert_ast2!(
        "fn a(b, c: Int): Int",
        vec![stmt(StatementKind::Declaration(Declaration::FuncSig(Signature {
            name: rc("a"),
            operator: false,
            params: vec![
                FormalParam { name: rc("b"), default: None, anno: None },
                FormalParam {
                    name: rc("c"),
                    default: None,
                    anno: Some(TypeIdentifier::Singleton(TypeSingletonName {
                        name: rc("Int"),
                        params: vec![]
                    })),
                },
            ],
            type_anno: Some(TypeIdentifier::Singleton(TypeSingletonName { name: rc("Int"), params: vec![] })),
        })))]
    );
}

#[test]
fn max_function_params() {
    let mut buf = "fn longfunc(".to_string();
    for n in 0..256 {
        write!(buf, "a{}, ", n).unwrap();
    }
    write!(buf, ") {{ return 20 }}").unwrap();
    //assert_fail2!(&buf, "A function cannot have more than 255 arguments");
    //TODO better errors again
    assert_fail2!(&buf, "error at 1:1439: expected ['a' ..= 'z' | 'A' ..= 'Z' | '_']");
}

#[test]
fn functions_with_different_whitespace() {
    use ExpressionKind::*;

    let a = "fn a(x) { x() }";
    let b = "fn a(x) {\n x() }";
    let c = r#"

    fn a(x) {


        x()


            }

    "#;

    for item in [a, b, c].iter() {
        assert_ast2!(
            item,
            vec![fn_decl(
                Signature {
                    name: rc("a"),
                    operator: false,
                    type_anno: None,
                    params: vec![FormalParam { name: rc("x"), default: None, anno: None }]
                },
                vec![stmt(StatementKind::Expression(expr(Call {
                    f: bx(expr(Value(qn!(x)))),
                    arguments: vec![],
                })))]
                .into()
            )]
        );
    }
}

#[test]
fn functions_with_default_args() {
    use ExpressionKind::*;

    assert_ast2!(
        "fn func(x: Int, y: Int = 4) { }",
        vec![fn_decl(
            Signature {
                name: rc("func"),
                operator: false,
                type_anno: None,
                params: vec![
                    FormalParam { name: rc("x"), anno: Some(ty_simple("Int")), default: None },
                    FormalParam {
                        name: rc("y"),
                        anno: Some(ty_simple("Int")),
                        default: Some(expr(NatLiteral(4)))
                    },
                ],
            },
            vec![].into()
        )]
    );
}

#[test]
fn interface() {
    let glue = TypeIdentifier::Singleton(TypeSingletonName { name: rc("Glue"), params: vec![] });
    assert_ast2!(
        "interface Unglueable { fn unglue(a: Glue); fn mar(): Glue }",
        vec![decl(Declaration::Interface {
            name: rc("Unglueable"),
            signatures: vec![
                Signature {
                    name: rc("unglue"),
                    operator: false,
                    params: vec![FormalParam { name: rc("a"), default: None, anno: Some(glue.clone()) },],
                    type_anno: None,
                },
                Signature { name: rc("mar"), operator: false, params: vec![], type_anno: Some(glue) },
            ],
        })]
    );
}

#[test]
fn impls() {
    use Declaration::{FuncDecl, Impl};

    let block = vec![
        FuncDecl(Signature { name: rc("yolo"), operator: false, params: vec![], type_anno: None },
        vec![].into()),
        FuncDecl(Signature { name: rc("swagg"), operator: false, params: vec![], type_anno: None },
            vec![].into()
        )
    ];

    assert_ast2!(
        "impl Heh { fn yolo() { }; fn swagg() { } }",
        vec![decl(Impl {
            type_name: ty_simple("Heh"),
            interface_name: None,
            block: block.clone(),
        })]
    );

    //TODO `"impl Heh<X> { fn yolo() { }; fn swagg() { }; }"` ought to work
    assert_ast2!(
        "impl Heh<X> { fn yolo() { }; fn swagg() { } }",
        vec![decl(Impl {
            type_name: TypeIdentifier::Singleton(TypeSingletonName {
                name: rc("Heh"),
                params: vec![ty_simple("X")]
            }),
            interface_name: None,
            block: block.clone(),
        })]
    );

    assert_ast2!(
        "impl Heh for Saraz { fn yolo() {}; fn swagg() {} }",
        vec![decl(Impl {
            type_name: ty_simple("Saraz"),
            interface_name: Some(TypeSingletonName { name: rc("Heh"), params: vec![] }),
            block: block.clone(),
        })]
    );

    assert_ast2!(
        "impl Heh<T> for (Int, Codepoint) {}",
        vec![decl(Impl {
            type_name: TypeIdentifier::Tuple(vec![ty_simple("Int"), ty_simple("Codepoint")]),
            interface_name: Some(TypeSingletonName { name: rc("Heh"), params: vec![ty_simple("T")] }),
            block: vec![]
        })]
    );
}

#[test]
fn annotations() {
    use ExpressionKind::*;

    let func = decl(Declaration::FuncDecl(
        Signature { name: rc("some_function"), operator: false, params: vec![], type_anno: None },
        vec![].into(),
    ));

    assert_ast! {
        r#"
        @test_annotation
        fn some_function() {

        }"#,
        vec![decl(Declaration::Annotation {
            name: rc("test_annotation"),
            arguments: vec![],
            inner: bx(func.clone()),
        }),
        ]
    };

    assert_ast! {
        r#"
        @test_annotation(some,value)
        @another_annotation
        fn some_function() {

        }"#,
        vec![decl(Declaration::Annotation {
            name: rc("test_annotation"),
            arguments: vec![expr(Value(qn!(some))), expr(Value(qn!(value)))],
            inner: bx(decl(Declaration::Annotation {
                name: rc("another_annotation"), arguments: vec![], inner: bx(func)
            }))
        }),
        ]
    };
}

#[test]
fn modules() {
    assert_ast! {
        r#"
    module ephraim {
        let mut a = 10
        fn nah() { 33 }
    }
    "#,
    vec![stmt(StatementKind::Declaration(Declaration::Module {
        name: rc("ephraim"),
        items: vec![
            decl(Declaration::Binding {
                name: rc("a"), constant: false, type_anno: None,
                expr: expr(ExpressionKind::NatLiteral(10))
            }),
            fn_decl(Signature { name: rc("nah"), operator: false, params: vec![], type_anno: None },
            vec![stmt(StatementKind::Expression(expr(ExpressionKind::NatLiteral(33))))].into()),
        ].into()
    }))]
    };
}

#[test]
fn imports() {
    assert_ast! {
        "import harbinger::draughts::Norgleheim",
        vec![stmt(StatementKind::Import(ImportSpecifier {
            id: ItemId::default(),
            path_components: vec![rc("harbinger"), rc("draughts"), rc("Norgleheim")],
            imported_names: ImportedNames::LastOfPath
        }))]
    };

    assert_ast! {
        "import harbinger::draughts::{Norgleheim, Xraksenlaigar}",
        vec![stmt(StatementKind::Import(ImportSpecifier {
            id: ItemId::default(),
            path_components: vec![rc("harbinger"), rc("draughts")],
            imported_names: ImportedNames::List(vec![
                rc("Norgleheim"), rc("Xraksenlaigar")])
        }))]
    };

    assert_ast! {
        "import bespouri::{}",
        vec![stmt(StatementKind::Import(ImportSpecifier {
            id: Default::default(),
            path_components: vec![rc("bespouri")],
            imported_names: ImportedNames::List(vec![]),
        }))]
    };

    assert_ast! {
        "import bespouri::*",
        vec![stmt(StatementKind::Import(ImportSpecifier {
            id: Default::default(),
            path_components: vec![rc("bespouri")],
            imported_names: ImportedNames::All,
        }))]
    };
}

#[test]
fn if_exprs() {
    use ExpressionKind::*;
    assert_expr2!(
        "if a() then { tuah(); }",
        expr(IfExpression {
            discriminator: Some(bx(expr(Call { f: bx(expr(Value(qn!(a)))), arguments: vec![] }))),
            body: bx(IfExpressionBody::SimpleConditional {
                then_case: vec![exst(Call { f: bx(expr(Value(qn!(tuah)))), arguments: vec![] })].into(),
                else_case: None,
            })
        })
    );

    assert_expr2!(
        "if a then b else c",
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(a))))),
            body: bx(IfExpressionBody::SimpleConditional {
                then_case: vec![exst(Value(qn!(b)))].into(),
                else_case: Some(vec![exst(Value(qn!(c)))].into()),
            })
        })
    );

    assert_expr2!(
        r#"
  if true then {
    let a = 10
    b
  } else {
    c
  }"#,
        expr(IfExpression {
            discriminator: Some(bx(expr(BoolLiteral(true)))),
            body: bx(IfExpressionBody::SimpleConditional {
                then_case: vec![
                    decl(Declaration::Binding {
                        name: rc("a"),
                        constant: true,
                        type_anno: None,
                        expr: expr(NatLiteral(10))
                    }),
                    exst(Value(qn!(b))),
                ]
                .into(),
                else_case: Some(vec![exst(Value(qn!(c))),].into())
            })
        })
    );
}

#[test]
fn pattern_matching() {
    use ExpressionKind::*;

    for item in ["if x is Some(a) then { 4 } else { 9 }", "if x is Some(a) then 4 else 9"] {
        assert_expr2!(
            item,
            expr(IfExpression {
                discriminator: Some(bx(expr(Value(qn!(x))))),
                body: bx(IfExpressionBody::SimplePatternMatch {
                    pattern: Pattern::TupleStruct(qn!(Some), vec![Pattern::VarOrName(qn!(a))]),
                    then_case: vec![exst(NatLiteral(4))].into(),
                    else_case: Some(vec![exst(NatLiteral(9))].into()),
                })
            })
        );
    }

    assert_expr2!(
        "if x is Something { a, b: x } then { 4 } else { 9 }",
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(x))))),
            body: bx(IfExpressionBody::SimplePatternMatch {
                pattern: Pattern::Record(
                    qn!(Something),
                    vec![(rc("a"), Pattern::VarOrName(qn!(a))), (rc("b"), Pattern::VarOrName(qn!(x)))]
                ),
                then_case: vec![exst(NatLiteral(4))].into(),
                else_case: Some(vec![exst(NatLiteral(9))].into()),
            })
        })
    );

    assert_expr2!(
        "if x is -1 then 1 else 2",
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(x))))),
            body: bx(IfExpressionBody::SimplePatternMatch {
                pattern: Pattern::Literal(PatternLiteral::NumPattern { neg: true, num: NatLiteral(1) }),
                then_case: vec![exst(NatLiteral(1))].into(),
                else_case: Some(vec![exst(NatLiteral(2))].into()),
            })
        })
    );

    assert_expr2!(
        "if x is true then 1 else 2",
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(x))))),
            body: bx(IfExpressionBody::SimplePatternMatch {
                pattern: Pattern::Literal(PatternLiteral::BoolPattern(true)),
                then_case: vec![exst(NatLiteral(1))].into(),
                else_case: Some(vec![exst(NatLiteral(2))].into()),
            })
        })
    );

    assert_expr2!(
        "if x { is 1 then 5; else 20 }",
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(x))))),
            body: bx(IfExpressionBody::CondList(vec![
                ConditionArm {
                    condition: Condition::Pattern(Pattern::Literal(PatternLiteral::NumPattern {
                        neg: false,
                        num: NatLiteral(1)
                    })),
                    guard: None,
                    body: vec![exst(NatLiteral(5))].into(),
                },
                ConditionArm {
                    condition: Condition::Else,
                    guard: None,
                    body: vec![exst(NatLiteral(20))].into(),
                },
            ]))
        })
    );

    assert_expr2!(
        r#"if x is "gnosticism" then 1 else 2"#,
        expr(IfExpression {
            discriminator: Some(bx(expr(Value(qn!(x))))),
            body: bx(IfExpressionBody::SimplePatternMatch {
                pattern: Pattern::Literal(PatternLiteral::StringPattern(rc("gnosticism"))),
                then_case: vec![exst(NatLiteral(1))].into(),
                else_case: Some(vec![exst(NatLiteral(2))].into()),
            })
        })
    );

    assert_expr2! {
        r#"
if (45, "panda", false, 2.2) {
    is (49, "pablo", _,  28.4) then "no"
    is (_, "panda", _, -2.2) then "yes"
    is _ then "maybe"
}"#,
      expr(
        IfExpression {
          discriminator: Some(bx(expr(TupleLiteral(vec![
            expr(NatLiteral(45)), expr(StringLiteral(rc("panda"))), expr(BoolLiteral(false)), expr(FloatLiteral(2.2))
          ])))),
          body: bx(IfExpressionBody::CondList(vec![
            ConditionArm {
              condition: Condition::Pattern(Pattern::TuplePattern(
                vec![
                    Pattern::Literal(PatternLiteral::NumPattern { neg: false, num: NatLiteral(49) }),
                    Pattern::Literal(PatternLiteral::StringPattern(rc("pablo"))),
                    Pattern::Ignored,
                    Pattern::Literal(PatternLiteral::NumPattern { neg: false, num: FloatLiteral(28.4) }),
                ]
              )),
              guard: None,
              body: vec![stmt(StatementKind::Expression(expr(StringLiteral(rc("no")))))].into(),
            },
            ConditionArm {
              condition: Condition::Pattern(Pattern::TuplePattern(
                vec![
                    Pattern::Ignored,
                    Pattern::Literal(PatternLiteral::StringPattern(rc!(panda))),
                    Pattern::Ignored,
                    Pattern::Literal(PatternLiteral::NumPattern { neg: true, num: FloatLiteral(2.2) }),
                ]
              )),
              guard: None,
              body: vec![stmt(StatementKind::Expression(expr(StringLiteral(rc("yes")))))].into(),
            },
            ConditionArm {
              condition: Condition::Pattern(Pattern::Ignored),
              guard: None,
              body: vec![exst(StringLiteral(rc("maybe")))].into(),
            },
          ]))
        }
      )
    };
}

#[test]
fn flow_control() {
    use ExpressionKind::*;

    // This is an incorrect program, but shoudl parse correctly.
    let source = r#"
    fn test() {
        let a = 10;
        break;
        continue;
        return;
        return 10;
    }"#;

    assert_ast!(
        source,
        vec![fn_decl(
            Signature { name: rc("test"), operator: false, type_anno: None, params: vec![] },
            vec![
                decl(Declaration::Binding {
                    name: rc("a"),
                    constant: true,
                    type_anno: None,
                    expr: expr(NatLiteral(10))
                }),
                stmt(StatementKind::Flow(FlowControl::Break)),
                stmt(StatementKind::Flow(FlowControl::Continue)),
                stmt(StatementKind::Flow(FlowControl::Return(None))),
                stmt(StatementKind::Flow(FlowControl::Return(Some(expr(NatLiteral(10)))))),
            ]
            .into()
        )]
    );
}

#[test]
fn blocks() {
    use ExpressionKind::*;

    let cases = ["{ a }", "{ a; }", "{a}", "{ a\n }", "{ a\n\n }", "{   a;\n\n; }"];

    for case in cases.iter() {
        let block = schala_parser::block(case);
        assert_eq!(block.unwrap(), vec![exst(Value(qn!(a)))].into());
    }
}