use nexus_lang::{Location, Token, TokenKind, Tokens, parser::cursor::Cursor};
use pretty_assertions::assert_eq;

/// Generates token sequence for `let x = 42;`.
fn create_token_sequence() -> Tokens {
    vec![
        Token::new(TokenKind::Let, "let", Location::new(0, 1, 1)),
        Token::new(
            TokenKind::Identifier("x".to_owned()),
            "x",
            Location::new(0, 1, 5),
        ),
        Token::new(TokenKind::Is, "=", Location::new(0, 1, 7)),
        Token::new(TokenKind::Number(42.0), "42", Location::new(0, 1, 9)),
        Token::new(TokenKind::Semicolon, ";", Location::new(0, 1, 11)),
    ]
}

#[test]
fn new() {
    let tokens = create_token_sequence();
    let cursor = Cursor::new(tokens);

    assert_eq!(cursor.value(), create_token_sequence().first());
}

#[test]
fn value() {
    let tokens = create_token_sequence();
    let mut cursor = Cursor::new(tokens);

    assert_eq!(cursor.value(), create_token_sequence().iter().nth(0));
    cursor.advance();
    assert_eq!(cursor.value(), create_token_sequence().iter().nth(1));
}

#[test]
fn matches() {
    let tokens = vec![
        Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
        Token::new(TokenKind::Minus, "-", Location::new(0, 1, 5)),
    ];
    let mut cursor = Cursor::new(tokens);

    assert_eq!(cursor.matches(&[TokenKind::Minus]), false);
    assert_eq!(cursor.matches(&[TokenKind::Plus]), true);

    cursor.advance();

    assert_eq!(cursor.matches(&[TokenKind::Dot, TokenKind::For]), false);
    assert_eq!(cursor.matches(&[TokenKind::Minus]), true);
}

#[test]
fn advance() {
    let tokens = create_token_sequence();
    let mut cursor = Cursor::new(tokens);

    for i in 1..10 {
        cursor.advance();
        assert_eq!(cursor.value(), create_token_sequence().iter().nth(i));
    }

    assert_eq!(cursor.value(), None);
}

#[test]
fn advance_if_matches() {
    let tokens = vec![
        Token::new(TokenKind::Plus, "+", Location::new(0, 1, 1)),
        Token::new(TokenKind::Minus, "-", Location::new(0, 1, 3)),
        Token::new(TokenKind::Star, "*", Location::new(0, 1, 5)),
    ];
    let mut cursor = Cursor::new(tokens);

    assert_eq!(cursor.advance_if_matches(&[TokenKind::Comma]), false);
    assert_eq!(cursor.advance_if_matches(&[TokenKind::Minus]), false);
    assert_eq!(cursor.advance_if_matches(&[TokenKind::Plus]), true);
    assert_eq!(
        cursor.advance_if_matches(&[TokenKind::Dot, TokenKind::For]),
        false
    );
    assert_eq!(cursor.advance_if_matches(&[TokenKind::Minus]), true);
    assert_eq!(cursor.advance_if_matches(&[TokenKind::Dot]), false);
    assert_eq!(cursor.advance_if_matches(&[TokenKind::Star]), true);
}

#[test]
fn is_eos_empty() {
    let tokens = Tokens::new();
    let cursor = Cursor::new(tokens);

    assert_eq!(cursor.is_eos(), true);
}

#[test]
fn consume() {
    let tokens = vec![
        Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)),
        Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
        Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)),
        Token::new(TokenKind::Semicolon, ";", Location::new(0, 1, 7)),
    ];
    let mut cursor = Cursor::new(tokens);

    // Should fail to consume wrong token kind
    assert_eq!(cursor.consume(TokenKind::Plus), false);
    assert_eq!(cursor.consume(TokenKind::Minus), false);

    // Should successfully consume correct token kind
    assert_eq!(cursor.consume(TokenKind::Number(1.0)), true);
    assert_eq!(cursor.consume(TokenKind::Plus), true);
    assert_eq!(cursor.consume(TokenKind::Number(2.0)), true);
    assert_eq!(cursor.consume(TokenKind::Semicolon), true);

    // Should fail when at end of stream
    assert_eq!(cursor.consume(TokenKind::Plus), false);
}

#[test]
fn is_eos() {
    let tokens = create_token_sequence();
    let num_tokens = tokens.len();
    let mut cursor = Cursor::new(tokens);

    for _ in 0..num_tokens {
        assert_eq!(cursor.is_eos(), false);
        cursor.advance();
    }

    assert_eq!(cursor.is_eos(), true);
}
