use crate::Location;
use colored::Colorize;
use std::fmt;

/// Token kinds used in Nexus lexical grammar.
#[derive(Clone, Debug, PartialEq)]
pub enum TokenKind {
    LParen,         // '('
    RParen,         // ')'
    LBrace,         // '{'
    RBrace,         // '}'
    LBracket,       // '['
    RBracket,       // ']'
    Colon,          // ':'
    Semicolon,      // ';'
    Amp,            // '&'
    Plus,           // '+'
    Minus,          // '-'
    Arrow,          // '->'
    Star,           // '*'
    Slash,          // '/'
    Backslash,      // '\'
    Percent,        // '%'
    Comma,          // ','
    Dot,            // '.'
    Range,          // '..'
    RangeInclusive, // '..='
    Underscore,     // '_'
    Is,             // '='
    PlusIs,         // '+='
    MinusIs,        // '-='
    StarIs,         // '*='
    SlashIs,        // '/='
    PercentIs,      // '%='
    Eq,             // '=='
    Gt,             // '>'
    GtEq,           // '>='
    Lt,             // '<'
    LtEq,           // '<='
    Bang,           // '!'
    NotEq,          // '!='
    And,            // '&&'
    DoublePipe, // '||' (ambiguity between "logical AND" and "empty closure" is handled in parser)
    Pipe,       // '|'
    True,       // 'true'
    False,      // 'false'
    Assert,     // 'assert'
    Const,      // 'const'
    Let,        // 'let'
    Mut,        // 'mut'
    Function,   // 'fn'
    If,         // 'if'
    Else,       // 'else'
    For,        // 'for'
    In,         // 'in'
    Loop,       // 'loop'
    While,      // 'while'
    Break,      // 'break'
    Continue,   // 'continue'
    Return,     // 'return'
    Use,        // 'use'
    BoolId,     // 'bool'
    NodeId,     // 'Node'
    GroupId,    // 'Group'
    NumberId,   // 'Number'
    PortRefId,  // 'PortRef'
    StringId,   // 'String'
    Port,       // 'port'
    Print,      // 'print'
    Node,       // 'node'
    Group,      // 'group'
    Type,       // 'type'
    Out,        // 'out'
    Inout,      // 'inout'
    Self_,      // 'self'
    Number(Number),
    Identifier(String),
    String(String),
}

/// Value type used for all number literals.
pub type Number = f64;

/// Scanning/lexing token representation used in the Nexus grammar.
#[derive(Clone, Debug, PartialEq)]
pub struct Token {
    pub kind: TokenKind,
    pub lexeme: String,
    pub loc: Location,
}

impl Token {
    /// Create a new token.
    #[must_use]
    pub fn new(kind: TokenKind, lexeme: &str, loc: Location) -> Self {
        Token {
            kind,
            lexeme: lexeme.to_string(),
            loc,
        }
    }
}

impl fmt::Display for Token {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.kind {
            TokenKind::Number(n) => write!(f, "{}({})", "Number".red(), n.to_string().yellow()),
            TokenKind::Identifier(ref name) => {
                write!(f, "{}({})", "Identifier".red(), name.yellow())
            }
            TokenKind::String(ref s) => {
                write!(f, "{}(\"{}\")", "String".red(), s.yellow())
            }
            _ => write!(f, "{}", format!("{:?}", self.kind).red()),
        }
    }
}

/// Collection of tokens.
pub type Tokens = Vec<Token>;
