#[macro_use]
extern crate pretty_assertions;

extern crate gluon_base as base;
extern crate gluon_parser as parser;

mod support;

use {
    base::{
        ast::{Expr, Pattern, PatternField, TypedIdent},
        mk_ast_arena,
        pos::{self, BytePos},
        types::Type,
    },
    parser::{Error, ParseErrors, Token, TokenizeError},
};

use crate::support::*;

test_parse_error! {
    empty_input,
    "",
    |_: base::ast::ArenaRef<'_, '_, String>| error(),
    {
        let error = Error::UnexpectedEof(vec![]);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

test_parse_error! {
    missing_match_expr,
    r#"
    match with
    | x -> x
    "#,
    |arena| case(arena,
            error(),
            vec![(Pattern::Ident(TypedIdent::new(intern("x"))), id("x"))],
        ),
    {

        let error = Error::UnexpectedToken(Token::With, vec![]);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

#[test]
fn wrong_indent_expression() {
    let _ = ::env_logger::try_init();

    let result = parse(
        r#"
let y =
    let x = 1
    x
   2
y
"#,
    );
    let span = pos::span(BytePos::from(0), BytePos::from(0));
    let errors = ParseErrors::from(vec![
        pos::spanned(span, Error::UnexpectedToken(Token::IntLiteral(2), vec![])),
        pos::spanned(span, Error::UnexpectedToken(Token::CloseBlock, vec![])),
    ]);

    assert_eq!(remove_expected(result.unwrap_err().1), errors);
}

test_parse_error! {
    unclosed_string,
    r#"
"abc
123"#,
    |_: base::ast::ArenaRef<'_, '_, String>| string("abc\n123"),
    {
        let error = Error::Token(parser::TokenizeError::UnterminatedStringLiteral);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

#[test]
fn tokenizer_error_is_returned() {
    let _ = ::env_logger::try_init();

    let result = parse(
        r#"
12345678901234567890 test
"#,
    );

    let error = Error::Token(TokenizeError::NonParseableInt);
    let span = pos::span(BytePos::from(0), BytePos::from(0));
    let errors = ParseErrors::from(vec![pos::spanned(span, error)]);

    assert_eq!(remove_expected(result.unwrap_err().1), errors);
}

#[test]
fn tokenizer_error_at_eof_is_returned() {
    let _ = ::env_logger::try_init();

    let result = parse(
        r#"
12345678901234567890
"#,
    );

    let error = Error::Token(TokenizeError::NonParseableInt);
    let span = pos::span(BytePos::from(0), BytePos::from(0));
    let errors = ParseErrors::from(vec![pos::spanned(span, error)]);

    assert_eq!(remove_expected(result.unwrap_err().1), errors);
}

#[test]
fn no_infinite_loop_from_default_block() {
    let _ = ::env_logger::try_init();

    let result = parse(
        r#"
let x = 1

    x,
    y = 1
}
"#,
    );
    assert!(result.is_err());
}

test_parse_error! {
    missing_pattern,
    r#"
    match 1 with
    | -> x
    "#,
    |arena| case(arena, int(1), vec![(Pattern::Error, id("x"))]),
    {
        let error = Error::UnexpectedToken(Token::RArrow, vec![]);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

test_parse_error! {
    incomplete_alternative,
    r#"
    match 1 with
    | //
    "#,
    |arena| case(arena, int(1), vec![(Pattern::Error, error())]),
    {

        let error = Error::UnexpectedToken(Token::CloseBlock, vec![]);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

test_parse_error! {
    incomplete_alternative_before_complete_alternative,
    r#"
    match 1 with
    | //
    | x -> x
    "#,
    |arena| case(arena,
            int(1),
            vec![
                (Pattern::Error, error()),
                (Pattern::Ident(TypedIdent::new(intern("x"))), id("x")),
            ],
        ),
    {
        let error = Error::UnexpectedToken(Token::Pipe, vec![]);
        let span = pos::span(BytePos::from(0), BytePos::from(0));
        ParseErrors::from(vec![pos::spanned(span, error)])
    }
}

test_parse_error! {
    incomplete_alternative_with_partial_pattern,
    r#"
    match 1 with
    | { x = }
    "#,
    |arena| case(arena,
            int(1),
            vec![(
                Pattern::Record {
                    typ: Type::hole(),
                    fields: arena.alloc_extend(vec![PatternField::Value {
                        name: no_loc(intern("x")),
                        value: Some(no_loc(Pattern::Error)),
                    }]),
                    implicit_import: None,
                },
                error(),
            )],
        ),
    vec![
        no_loc(Error::UnexpectedToken(Token::RBrace, vec![])),
        no_loc(Error::UnexpectedToken(Token::CloseBlock, vec![])),
    ],
}

test_parse_error! {
    incomplete_let_binding,
    r#"
    let test =
    1
    "#,
    |arena| let_(arena, "test", no_loc(Expr::Error(None)), int(1),),
    vec![no_loc(Error::UnexpectedToken(Token::CloseBlock, vec![]))]
}

test_parse_error! {
    incomplete_let_binding_2,
    r#"
    let test = io
    "#,
    |arena| let_(arena, "test", id("io"), no_loc(Expr::Error(None))),
    vec![no_loc(Error::UnexpectedToken(Token::CloseBlock, vec![]))],
}

test_parse_error! {
    incomplete_let_binding_3,
    r#"
    let test
    1
    "#,
    |arena| let_(arena, "test", no_loc(Expr::Error(None)), int(1),),
    vec![no_loc(Error::UnexpectedToken(Token::In, vec![]))],
}

#[test]
fn unterminated_char_literal() {
    let _ = ::env_logger::try_init();

    let expr = r#"
    'a
    "#;
    let result = parse(expr);
    assert!(result.is_err());
    let (_expr, err) = result.unwrap_err();

    let error = Error::Token(TokenizeError::UnterminatedCharLiteral);
    let span = pos::span(BytePos::from(6), BytePos::from(8));
    assert_eq!(err, ParseErrors::from(vec![pos::spanned(span, error)]));
}

#[test]
fn missing_close_paren() {
    let _ = ::env_logger::try_init();

    let expr = r#"
    let x =
        (1
    x
    "#;
    let result = parse(expr);
    assert!(result.is_err());
    let (_expr, err) = result.unwrap_err();

    let error = Error::UnexpectedEof([",", ")"].iter().map(|s| s.to_string()).collect());
    let span = pos::span(BytePos::from(30), BytePos::from(30));
    assert_eq!(err, ParseErrors::from(vec![pos::spanned(span, error)]));
}

#[test]
fn invalid_case() {
    let _ = env_logger::try_init();

    assert!(parse(r#"type X = { Test : Type } in ()"#).is_err());
    assert!(parse(r#"type x = { } in ()"#).is_err());
    assert!(parse(r#"type x = | Test in ()"#).is_err());
}

#[test]
fn only_identifiers_are_allowed_on_recursive_patterns() {
    let _ = env_logger::try_init();

    assert!(parse(r#"rec let { } = { } in 1"#).is_err());
    assert!(parse(r#"rec let () = { } in 1"#).is_err());
    assert!(parse(r#"rec let x @ { }  = { x } in 1"#).is_err());
}

#[test]
fn invalid_variant() {
    let _ = env_logger::try_init();

    assert!(parse(r#"type X = | r in ()"#).is_err());
}

test_parse_error! {
error_in_do_1,
        r#"
do x
1
"#,
    |arena| do_(arena, "x", no_loc(Expr::Error(None)), int(1)),
    vec![no_loc(Error::UnexpectedToken(Token::In, vec![]))],
}

#[test]
fn error_in_do_2() {
    let _ = ::env_logger::try_init();

    let result = parse(
        r#"
do
1
"#,
    );

    let e = clear_span(result.unwrap_err().0.expect("Expression"));
    mk_ast_arena!(arena);
    assert_eq!(
        *e.expr(),
        do_2(
            arena.borrow(),
            Some(no_loc(Pattern::Error)),
            no_loc(Expr::Error(None)),
            int(1),
        )
    );
}
