use nexus_lang::{Location, scanner::cursor::Cursor};
use pretty_assertions::assert_eq;

#[test]
fn new() {
    let empty = String::new();
    let line = "Test123".to_string();

    let c1 = Cursor::new(&empty);
    let c2 = Cursor::new(&line);

    assert_eq!(c1.value(), None);
    assert_eq!(c2.value(), Some('T'));
}

#[test]
fn at_end() {
    let empty = String::new();
    let line = "abc".to_string();

    let c1 = Cursor::new(&empty);
    let mut c2 = Cursor::new(&line);

    assert!(c1.at_end());
    assert!(!c2.at_end());

    c2.advance();
    c2.advance();
    c2.advance();

    assert!(c2.at_end());
}

#[test]
fn advance() {
    let line = "abcdefg".to_string();

    let mut c = Cursor::new(&line);

    for i in "abcdefg".chars() {
        assert_eq!(c.value().unwrap(), i);
        c.advance();
    }

    assert!(c.value().is_none());
    assert!(c.at_end());
}

#[test]
fn advance_by() {
    let line = "ab_cd_ɘƒ_gh".to_string();

    let mut c = Cursor::new(&line);

    assert_eq!(c.value(), Some('a'));
    c.advance_by(0);
    assert_eq!(c.value(), Some('a'));
    c.advance_by(3);
    assert_eq!(c.value(), Some('c'));
    c.advance_by(3);
    assert_eq!(c.value(), Some('ɘ'));
    c.advance_by(3);
    assert_eq!(c.value(), Some('g'));

    c.advance_by(3);

    assert!(c.value().is_none());
    assert!(c.at_end());

    let mut c = Cursor::new(&line);

    // Past the end in one go:
    c.advance_by(100);

    assert!(c.value().is_none());
    assert!(c.at_end());

    // Empty string.
    let empty = String::new();
    let mut c = Cursor::new(&empty);
    c.advance_by(5);
    assert!(c.at_end());
}

#[test]
fn advance_while() {
    let line = "abcd0123".to_string();

    let mut c = Cursor::new(&line);

    assert_eq!(c.value(), Some('a'));
    c.advance_while(char::is_numeric); // No advancement.
    assert_eq!(c.value(), Some('a'));
    c.advance_while(char::is_alphabetic);
    assert_eq!(c.value(), Some('0'));
    c.advance_while(char::is_numeric);
    assert_eq!(c.value(), None);

    c.advance();
    assert!(c.value().is_none());
    assert!(c.at_end());

    // Test advance_while that breaks on predicate failure.
    let mixed = "abc123xyz".to_string();
    let mut c = Cursor::new(&mixed);
    c.advance_while(char::is_alphabetic);
    assert_eq!(c.value(), Some('1'));

    // Consume remaining characters.
    c.advance_while(char::is_alphanumeric);
    assert!(c.at_end());
    assert_eq!(c.value(), None);
}

#[test]
fn peek() {
    let line = "abcdefg".to_string();

    let mut c = Cursor::new(&line);

    for i in "bcdefg".chars() {
        assert_eq!(c.peek().unwrap(), i);
        c.advance();
    }

    assert!(!c.at_end());

    c.advance();

    assert!(c.at_end());
}

#[test]
fn peek_nth() {
    let line = "abcdefg".to_string();

    let c = Cursor::new(&line);

    for (i, x) in line.char_indices() {
        assert_eq!(c.peek_nth(i), Some(x));
    }

    // Test with n=0 (should return current value).
    assert_eq!(c.peek_nth(0), Some('a'));

    // Test with n=1 (should return next value).
    assert_eq!(c.peek_nth(1), Some('b'));

    // Test with n > 1 (should use the loop).
    assert_eq!(c.peek_nth(2), Some('c'));

    // Test beyond string length.
    assert_eq!(c.peek_nth(10), None);

    // Test peek_nth on empty string.
    let empty = String::new();
    let c = Cursor::new(&empty);
    assert_eq!(c.peek_nth(0), None);
    assert_eq!(c.peek_nth(1), None);
}

#[test]
fn peek_while() {
    let line = "abc def".to_string();

    let mut c = Cursor::new(&line);

    let is_word_char = |c: char| -> bool { c.is_alphanumeric() || c == '_' };

    assert!(!c.at_end());
    assert_eq!(c.value(), Some('a'));

    assert_eq!(c.peek_while(is_word_char), Some("abc".to_string()));

    assert!(!c.at_end());
    assert_eq!(c.value(), Some('a'));

    c.advance();
    c.advance();
    c.advance();

    assert!(!c.at_end());
    assert_eq!(c.value(), Some(' '));

    c.advance();

    assert!(!c.at_end());
    assert_eq!(c.value(), Some('d'));

    assert_eq!(c.peek_while(is_word_char), Some("def".to_string()));

    assert!(!c.at_end());
    assert_eq!(c.value(), Some('d'));
}

#[test]
fn parse_word() {
    let test = |word: &str| {
        let cursor = Cursor::new(word);
        assert_eq!(
            cursor
                .peek_while(|c| { c.is_alphanumeric() || c == '_' })
                .unwrap(),
            word.to_string()
        );
    };

    test("x");
    test("ah");
    test("word");
    test("CamelCase");
    test("snake_case");
    test("ALLUPPER");
    test("ŮñĭçøƋɇ");
    test("trailing_numbers012");
    test("numbers1n8etw33n");
    test("veeeeeeeerylooooooongwooooooord");
}

#[test]
fn index_advance() {
    let mut cursor = Cursor::new("0123456789");

    assert_eq!(cursor.index(), 0);

    while !cursor.at_end() {
        assert_eq!(
            cursor.index(),
            cursor.value().unwrap().to_digit(10).unwrap() as usize
        );
        cursor.advance();
    }

    assert!(cursor.at_end());
    assert_eq!(cursor.index(), 10);

    cursor.advance();
    cursor.advance();

    assert!(cursor.at_end());
    assert_eq!(cursor.index(), 10);
}

#[test]
fn index_advance_by() {
    let mut cursor = Cursor::new("This is a test string");
    //                            0    5  7 9    14    EOL

    let test = |cursor: &Cursor, i: usize, c: char| {
        assert_eq!(cursor.index(), i);
        assert_eq!(cursor.value(), Some(c));
    };

    test(&cursor, 0, 'T');
    cursor.advance_by(5);
    test(&cursor, 5, 'i');
    cursor.advance_by(3);
    test(&cursor, 8, 'a');
    cursor.advance_by(2);
    test(&cursor, 10, 't');
    cursor.advance_by(5);
    test(&cursor, 15, 's');
    cursor.advance_by(6);

    assert!(cursor.at_end());
    assert_eq!(cursor.index(), 21);

    cursor.advance_by(10);

    assert!(cursor.at_end());
    assert_eq!(cursor.index(), 21);
}

#[test]
fn location() {
    let s = "abc123\nblah".to_string();
    let mut c = Cursor::new(&s);

    assert_eq!(c.location(), Location::new(0, 1, 1));
    c.advance();
    assert_eq!(c.location(), Location::new(1, 1, 2));
    c.advance_by(3);
    assert_eq!(c.location(), Location::new(4, 1, 5));
    c.advance_by(4);
    assert_eq!(c.location(), Location::new(8, 2, 2));
}

#[test]
fn consume() {
    let s = "abc".to_string();
    let mut c = Cursor::new(&s);

    assert_eq!(c.consume(), Some('a'));
    assert_eq!(c.consume(), Some('b'));
    assert_eq!(c.consume(), Some('c'));
    assert_eq!(c.consume(), None);
    assert!(c.at_end());

    // Test consume on empty string.
    let empty = String::new();
    let mut c = Cursor::new(&empty);
    assert_eq!(c.consume(), None);
    assert!(c.at_end());
}

#[test]
fn consume_if() {
    let s = "a1b2c".to_string();
    let mut c = Cursor::new(&s);

    // Test alphabetic predicate.
    assert_eq!(c.consume_if(char::is_alphabetic), Some('a'));
    assert_eq!(c.consume_if(char::is_alphabetic), None); // '1' is not alphabetic.
    assert_eq!(c.value(), Some('1')); // cursor didn't advance

    // Test numeric predicate.
    assert_eq!(c.consume_if(char::is_numeric), Some('1'));
    assert_eq!(c.consume_if(char::is_alphabetic), Some('b'));
    assert_eq!(c.consume_if(char::is_numeric), Some('2'));
    assert_eq!(c.consume_if(char::is_alphabetic), Some('c'));
    assert_eq!(c.consume_if(char::is_alphabetic), None); // at end

    // Test with empty string.
    let empty = String::new();
    let mut c = Cursor::new(&empty);
    assert_eq!(c.consume_if(char::is_alphabetic), None);
}

#[test]
fn consume_char() {
    let s = "abc".to_string();
    let mut c = Cursor::new(&s);

    assert!(c.consume_char('a'));
    assert!(!c.consume_char('x')); // 'b' != 'x'
    assert_eq!(c.value(), Some('b')); // cursor didn't advance
    assert!(c.consume_char('b'));
    assert!(c.consume_char('c'));
    assert!(!c.consume_char('d')); // at end
    assert!(c.at_end());

    // Test with empty string.
    let empty = String::new();
    let mut c = Cursor::new(&empty);
    assert!(!c.consume_char('a'));
}

#[test]
fn iterator_implementation() {
    let s = "abc".to_string();
    let c = Cursor::new(&s);

    let chars: Vec<char> = c.collect();
    assert_eq!(chars, vec!['a', 'b', 'c']);

    // Test iterator with empty string.
    let empty = String::new();
    let c = Cursor::new(&empty);
    let chars: Vec<char> = c.collect();
    assert_eq!(chars, Vec::<char>::new());
}

#[test]
fn iterator_with_for_loop() {
    let s = "hello".to_string();
    let c = Cursor::new(&s);

    let mut result = String::new();
    for ch in c {
        result.push(ch);
    }
    assert_eq!(result, "hello");
}

#[test]
fn iterator_partial_consumption() {
    let s = "abcdef".to_string();
    let mut c = Cursor::new(&s);

    // Consume first two characters manually.
    assert_eq!(c.consume(), Some('a'));
    assert_eq!(c.consume(), Some('b'));

    // Use iterator for the rest.
    let remaining: Vec<char> = c.collect();
    assert_eq!(remaining, vec!['c', 'd', 'e', 'f']);
}
