//! # Lexer
//!
//! Converts a [`Source`] (with  raw **ASCII G-Code lines**),
//! into usable [`Token`]s (represented as [`Lexer`]), which then can then be parsed.
//!
//! ## Reference
//! [Tomassetti](https://tomassetti.me/guide-parsing-algorithms-terminology/)

use crate::source::Source;

/// Prefix **ASCII** character for codes.
pub type Prefix = u8;
/// Suffix type which pairs with prefixes expecting **an integer** type.
pub type Int = usize;
/// Suffix type which pairs with prefixes expecting **a floating** type.
pub type Float = f64;
/// Type specifying **a code group**. Only for 'G' prefix codes.
pub type Group = u8;

/// Represents a numeric suffix for a [`Token`].
///
/// A G-Code field can contain an integer or a floating point.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Suffix {
    Int(Int),
    Float(Float),
}

impl Suffix {
    /// Tries to *optionally* return the value inside of the [`Suffix::Int`] variant.
    pub fn int(&self) -> Option<Int> {
        match self {
            Suffix::Int(i) => Some(*i),
            Suffix::Float(_) => None,
        }
    }

    /// Tries to *optionally* return the value inside of the [`Suffix::Float`] variant.
    pub fn float(&self) -> Option<Float> {
        match self {
            Suffix::Int(_) => None,
            Suffix::Float(f) => Some(*f),
        }
    }
}

/// A token represents a single **G-Code field**.
///
/// A field begins with a single alphabet ([`Prefix`]),
/// followed by a numeric [`Suffix`] (an [`Int`] or [`Float`]).
///
/// Any whitespace is ignored.
///
/// # Examples
/// - G01
/// - M06
/// - X1.2345
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Token {
    /// 'G' in G01
    pub prefix: Prefix,
    /// '1.2345' in X1.2345
    pub suffix: Suffix,
}

/// Represents a sequence of [`Token`]s,
/// generated by tokenizing a **single line** from [`Source`].
/// Tokens are stored in reverse order for efficient retrieval.
#[derive(PartialEq, Clone, Debug)]
pub struct Block(Vec<Token>);

impl Block {
    /// Constructs a [`Block`] by tokenizing raw **ASCII G-Code line**.
    ///
    /// Accepts a **sanitized** line as a string slice, taken from [`Source`].
    ///
    /// # Errors
    /// Returns a [`LexerError`] on failure.
    fn tokenize(line: &str) -> Result<Self, LexerError> {
        let mut tokens = vec![];

        let mut prefix_opt: Option<Prefix> = None;
        let mut suffix_opt: Option<usize> = None; // stores the index of start of string slice

        for (index, byte) in line.trim().as_bytes().iter().enumerate() {
            if !byte.is_ascii() {
                return Err(LexerError::IllegalChar);
            } else if byte.is_ascii_control() {
                return Err(LexerError::NonUsableChar(*byte));
            }

            debug_assert!(
                *byte != b';' && *byte != b'(' && *byte != b'%',
                "Invalid character passed Source sanitization. Logic Error!"
            );

            match prefix_opt {
                // start new word
                None => {
                    if *byte == b' ' {
                        continue; // try to start new on next()
                    } else if byte.is_ascii_alphabetic() {
                        prefix_opt = Some(byte.to_ascii_uppercase()); // read numeric on next()
                        continue;
                    } else {
                        return Err(LexerError::NonUsableChar(*byte));
                    }
                }
                // suffix complete
                Some(prefix) => {
                    if *byte == b' ' || byte.is_ascii_alphabetic() {
                        // map the suffix start index to a string slice containing the suffix
                        let suffix =
                            parse_suffix(suffix_opt.map(|start| &line[start..index]), prefix)?;

                        tokens.push(Token { prefix, suffix }); // add to vec and read next

                        // reset for the next word and use the alphabet, if found
                        prefix_opt = if byte.is_ascii_alphabetic() {
                            Some(byte.to_ascii_uppercase())
                        } else {
                            None
                        };
                        suffix_opt = None;
                    } else if byte.is_ascii_digit() || *byte == b'.' || *byte == b'-' {
                        // read suffix
                        match suffix_opt {
                            None => suffix_opt = Some(index),
                            Some(_) => {
                                if *byte == b'-' {
                                    // cannot have - in the middle of suffix
                                    return Err(LexerError::NonUsableChar(*byte));
                                }
                            }
                        }
                    }
                    // invalid suffix
                    else {
                        return Err(LexerError::NonUsableChar(*byte));
                    }
                }
            }
        }

        // parse last suffix, if present
        if let Some(prefix) = prefix_opt {
            let suffix = parse_suffix(suffix_opt.map(|start| &line[start..line.len()]), prefix)?;

            // add to vec and read next
            tokens.push(Token { prefix, suffix });
        }

        tokens.reverse();
        Ok(Self(tokens))
    }
}

impl Iterator for Block {
    type Item = Token;

    /// **Optionally** returns the next [`Token`].
    /// Returns [`None`] when the data has exhausted.
    fn next(&mut self) -> Option<Self::Item> {
        self.0.pop()
    }
}

/// Represents the whole G-Code as [`Block`]s,
/// generated by tokenizing **each line** from [`Source`].
///
/// Each [`Block`] is tokenized **lazily** on request from the [`Source`].
#[derive(Debug)]
pub struct Lexer(Source);

impl Lexer {
    /// Constructs [`Lexer`] from a [`Source`].
    ///
    /// This function **does not tokenize** any [`Line`](crate::source::Line)s.
    /// Tokenization is done on demand with a call to [`Lexer::next`].
    pub fn new(src: Source) -> Self {
        Self(src)
    }

    /// Reloads the [`Lexer`] to start from beginning of the [`Source`].
    pub fn reload(&mut self) {
        self.0.reload();
    }

    /// **Optionally** returns the next [`Line`](crate::source::Line) as a string slice from the [`Source`].
    pub fn get_line(&self, index: usize) -> Option<&str> {
        self.0.get(index)
    }
}

impl Iterator for Lexer {
    type Item = Result<Block, LexerError>;

    /// **Optionally** tokenizes the next [`Line`](crate::source::Line) from stored [`Source`].
    /// Returns [`Some`] variant with:
    /// - [`Block`] if the tokenization was successful.
    /// - [`LexerError`] on tokenization failure.
    ///
    /// Returns [`None`] when no more [`Line`](crate::source::Line)s are available from the [`Source`].
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|line| Block::tokenize(line.as_str()))
    }
}

/// Possible errors that can happen during tokenization.
#[derive(PartialEq, Debug, thiserror::Error)]
pub enum LexerError {
    /// A non-ASCII char is detected.
    #[error("non-ASCII character found in source")]
    IllegalChar,
    /// An invalid ASCII G-code char is detected.
    #[error("unsupported ASCII character: '{}'", *.0 as char)]
    NonUsableChar(Prefix),
    /// No numeric suffix found.
    #[error("no numeric suffix found after prefix: '{}'", *.0 as char)]
    NoSuffix(Prefix),
    /// Error while parsing numeric suffix.
    #[error("failed to parse numeric suffix after prefix: '{}'", *.0 as char)]
    ParseSuffix(Prefix),
}

/// Suffix parsing helper.
///
/// Optionally accepts a *potential suffix string*, and the prefix for error-handling.
///
/// Returns an appropriate [`Suffix`] variant on success and [`LexerError`] on failure.
fn parse_suffix(suffix_str: Option<&str>, prefix: Prefix) -> Result<Suffix, LexerError> {
    let suffix_str = match suffix_str {
        Some(s) => s,
        None => return Err(LexerError::NoSuffix(prefix)),
    };

    if suffix_str.is_empty() {
        return Err(LexerError::NoSuffix(prefix));
    }

    // parse as float
    if suffix_str.contains('.') {
        return match suffix_str.parse::<Float>() {
            Ok(s) => Ok(Suffix::Float(s)),
            Err(_) => Err(LexerError::ParseSuffix(prefix)),
        };
    }

    // try to parse as int
    match suffix_str.parse::<Int>() {
        Ok(s) => Ok(Suffix::Int(s)),
        Err(_) => Err(LexerError::ParseSuffix(prefix)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    // Tests the most common format of G-code.
    // Tests with all alphabets in uppercase, both with and without whitespace between them.
    fn tokenize_upper() {
        // with whitespace
        let whitespace = String::from("G01 X0.0 Y.0 Z-1.");
        let tokens = Block::tokenize(whitespace.as_str());
        let expected = Block(vec![
            Token {
                prefix: b'Z',
                suffix: Suffix::Float(-1.0),
            },
            Token {
                prefix: b'Y',
                suffix: Suffix::Float(0.0),
            },
            Token {
                prefix: b'X',
                suffix: Suffix::Float(0.0),
            },
            Token {
                prefix: b'G',
                suffix: Suffix::Int(1),
            },
        ]);

        assert_eq!(tokens.unwrap(), expected);

        // no whitespace
        let no_whitespace = String::from("G01X0.0Y.0Z-1.");
        let tokens = Block::tokenize(no_whitespace.as_str());

        assert_eq!(tokens.unwrap(), expected);
    }

    #[test]
    // Tests with all alphabets in lowercase, both with and without whitespace between them.
    fn tokenize_lower() {
        // with whitespace
        let whitespace = String::from("g01 x0.0 y.0 z-1.");
        let tokens = Block::tokenize(whitespace.as_str());
        // stored in REVERSE
        let expected = Block(vec![
            Token {
                prefix: b'Z',
                suffix: Suffix::Float(-1.0),
            },
            Token {
                prefix: b'Y',
                suffix: Suffix::Float(0.0),
            },
            Token {
                prefix: b'X',
                suffix: Suffix::Float(0.0),
            },
            Token {
                prefix: b'G',
                suffix: Suffix::Int(1),
            },
        ]);

        assert_eq!(tokens.unwrap(), expected);

        // no whitespace
        let no_whitespace = String::from("g01x0.0y.0z-1.");
        let tokens = Block::tokenize(no_whitespace.as_str());

        assert_eq!(tokens.unwrap(), expected);
    }

    #[test]
    #[should_panic]
    // Test with semicolon
    // ; should be removed during Source construction
    fn tokenize_semicolon() {
        let _ = Block::tokenize("G01;");
    }

    #[test]
    // Test non usable ASCII character
    fn tokenize_non_usable() {
        assert_eq!(
            Block::tokenize("G53 {").unwrap_err(),
            LexerError::NonUsableChar(b'{')
        );
        assert_eq!(
            Block::tokenize("G53 X1-1.").unwrap_err(),
            LexerError::NonUsableChar(b'-')
        );
    }

    #[test]
    // Test non-ASCII character
    fn tokenize_non_ascii() {
        assert_eq!(
            Block::tokenize("G53 नमस्ते").unwrap_err(),
            LexerError::IllegalChar
        );
    }

    #[test]
    // Test a field with no suffix
    fn tokenize_no_suffix() {
        assert_eq!(
            Block::tokenize("G53 X0.Y Z-1.").unwrap_err(),
            LexerError::NoSuffix(b'Y')
        );
    }

    #[test]
    // Test an invalid suffix.
    fn tokenize_invalid_suffix() {
        assert_eq!(
            Block::tokenize("G53 X.").unwrap_err(),
            LexerError::ParseSuffix(b'X')
        );
    }

    #[test]
    // Test a signed int.
    fn tokenize_signed_int() {
        assert_eq!(
            Block::tokenize("G-53").unwrap_err(),
            LexerError::ParseSuffix(b'G')
        );
    }
}
