use std::fmt::{Display, Formatter};

use crate::{location::Location, Lox};

#[derive(Clone, Debug, PartialEq)]
pub enum TokenType {
    // Single-character tokens.
    LeftParen,
    RightParen,
    LeftBrace,
    RightBrace,
    Comma,
    Dot,
    Minus,
    Plus,
    Semicolon,
    Slash,
    Star,
    Tilde,

    // One or two character tokens.
    Bang,
    BangEqual,
    Equal,
    EqualEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,

    // Literals.
    Identifier,
    Literal,

    // Keywords.
    And,
    Class,
    Else,
    Fun,
    For,
    If,
    Or,
    Print,
    Return,
    Super,
    This,
    Var,
    While,

    Eof,
}

pub const BINARY_OPERATORS: [TokenType; 12] = [
    TokenType::Plus,
    TokenType::Minus,
    TokenType::Star,
    TokenType::Slash,
    TokenType::And,
    TokenType::Or,
    TokenType::Greater,
    TokenType::GreaterEqual,
    TokenType::Less,
    TokenType::LessEqual,
    TokenType::EqualEqual,
    TokenType::BangEqual,
];

pub const UNARY_OPERATORS: [TokenType; 2] = [TokenType::Minus, TokenType::Bang];

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Literal {
    String(String),
    Number(f64),
    Boolean(bool),
    Nil,
}

impl Display for Literal {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Literal::String(s) => write!(f, "\"{}\"", s),
            Literal::Number(n) => write!(f, "{}", n),
            Literal::Boolean(b) => write!(f, "{}", b),
            Literal::Nil => write!(f, "nil"),
        }
    }
}

impl Literal {
    pub fn as_string(&self) -> String {
        match self {
            Literal::String(s) => s.clone(),
            _ => panic!("Not a string"),
        }
    }

    pub fn as_number(&self) -> f64 {
        match self {
            Literal::Number(n) => *n,
            _ => panic!("Not a number"),
        }
    }

    pub fn as_boolean(&self) -> bool {
        match self {
            Literal::Boolean(b) => *b,
            _ => panic!("Not a boolean"),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Token {
    pub token_type: TokenType,
    pub lexeme: String,
    pub location: Location,
    pub literal: Option<Literal>,
}

impl Display for Token {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if let Some(literal) = &self.literal {
            write!(f, "{:?} {} {}", self.token_type, self.lexeme, literal)
        } else {
            write!(f, "{:?} {}", self.token_type, self.lexeme)
        }
    }
}

impl Token {
    pub fn new(token_type: TokenType, lexeme: &str, line: usize, column: usize) -> Self {
        Self {
            token_type,
            lexeme: lexeme.to_string(),
            location: Location { line, column },
            literal: None,
        }
    }

    pub fn get_token_type(&self) -> TokenType {
        self.token_type.clone()
    }

    pub fn new_with_literal(
        token_type: TokenType,
        lexeme: &str,
        line: usize,
        column: usize,
        literal: Literal,
    ) -> Self {
        Self {
            token_type,
            lexeme: lexeme.to_string(),
            location: Location { line, column },
            literal: Some(literal),
        }
    }
}

#[derive(Debug)]
pub struct Lexer<'a> {
    lox: &'a mut Lox,
    source: String,
    start: usize,
    current: usize,
    line: usize,
    column: usize,
    tokens: Vec<Token>,
}

impl<'a> Lexer<'a> {
    pub fn new(lox: &'a mut Lox, source: &str) -> Self {
        Self {
            lox,
            source: source.to_string(),
            start: 0,
            current: 0,
            line: 1,
            column: 0,
            tokens: Vec::new(),
        }
    }

    fn is_at_end(&self) -> bool {
        self.current >= self.source.len()
    }

    fn advance(&mut self) -> char {
        let c = self.source.chars().nth(self.current).unwrap();
        self.current += 1;
        self.column += 1;
        c
    }

    fn add_token(&mut self, token_type: TokenType) {
        self.internal_add_token(token_type, None);
    }

    fn add_token_with_literal(&mut self, token_type: TokenType, literal: Literal) {
        self.internal_add_token(token_type, Some(literal))
    }

    fn internal_add_token(&mut self, token_type: TokenType, literal: Option<Literal>) {
        let text = &self.source[self.start..self.current];
        self.tokens.push(Token {
            token_type,
            lexeme: text.to_string(),
            location: Location {
                line: self.line,
                column: self.column,
            },
            literal,
        });
    }

    fn match_next(&mut self, expected: char) -> bool {
        if self.is_at_end() {
            return false;
        }
        if self.source.chars().nth(self.current).unwrap() != expected {
            return false;
        }
        self.current += 1;
        self.column += 1;
        true
    }

    fn peek(&self) -> char {
        if self.is_at_end() {
            return '\0';
        }
        self.source.chars().nth(self.current).unwrap()
    }

    fn peek_next(&self) -> char {
        if self.current + 1 >= self.source.len() {
            return '\0';
        }
        self.source.chars().nth(self.current + 1).unwrap()
    }

    fn string(&mut self) {
        while self.peek() != '"' && !self.is_at_end() {
            if self.peek() == '\n' {
                self.line += 1;
                self.column = 0;
            }
            self.advance();
        }
        if self.is_at_end() {
            self.lox.error(self.line, "Unterminated string.");
            return;
        }
        self.advance();
        let value = self.source[self.start + 1..self.current - 1].to_string();
        self.add_token_with_literal(TokenType::Literal, Literal::String(value));
    }

    fn number(&mut self) {
        while self.peek().is_digit(10) {
            self.advance();
        }
        if self.peek() == '.' && self.peek_next().is_digit(10) {
            self.advance();
            while self.peek().is_digit(10) {
                self.advance();
            }
        }
        let value = self.source[self.start..self.current].parse().unwrap();
        self.add_token_with_literal(TokenType::Literal, Literal::Number(value));
    }

    fn identifier(&mut self) {
        let column = self.column;
        while self.peek().is_alphanumeric() {
            self.advance();
        }
        let text = &self.source[self.start..self.current];
        let mut literal = None;
        let token_type = match text {
            "false" | "true" => {
                literal = Some(Literal::Boolean(text == "true"));
                TokenType::Literal
            }
            "nil" => {
                literal = Some(Literal::Nil);
                TokenType::Literal
            }
            "and" => TokenType::And,
            "class" => TokenType::Class,
            "else" => TokenType::Else,
            "for" => TokenType::For,
            "fun" => TokenType::Fun,
            "if" => TokenType::If,
            "or" => TokenType::Or,
            "print" => TokenType::Print,
            "return" => TokenType::Return,
            "super" => TokenType::Super,
            "this" => TokenType::This,
            "var" => TokenType::Var,
            "while" => TokenType::While,
            _ => TokenType::Identifier,
        };
        let next_column = self.column;
        self.column = column;
        if let Some(literal) = literal {
            self.add_token_with_literal(token_type, literal);
        } else {
            self.add_token(token_type);
        }
        self.column = next_column;
    }

    fn scan_token(&mut self) {
        let c = self.advance();
        match c {
            '(' => self.add_token(TokenType::LeftParen),
            ')' => self.add_token(TokenType::RightParen),
            '{' => self.add_token(TokenType::LeftBrace),
            '}' => self.add_token(TokenType::RightBrace),
            ',' => self.add_token(TokenType::Comma),
            '.' => self.add_token(TokenType::Dot),
            '-' => self.add_token(TokenType::Minus),
            '+' => self.add_token(TokenType::Plus),
            ';' => self.add_token(TokenType::Semicolon),
            '*' => self.add_token(TokenType::Star),
            '~' => self.add_token(TokenType::Tilde),
            '!' => {
                let token_type = if self.match_next('=') {
                    TokenType::BangEqual
                } else {
                    TokenType::Bang
                };
                self.add_token(token_type);
            }
            '=' => {
                let token_type = if self.match_next('=') {
                    TokenType::EqualEqual
                } else {
                    TokenType::Equal
                };
                self.add_token(token_type);
            }
            '<' => {
                let token_type = if self.match_next('=') {
                    TokenType::LessEqual
                } else {
                    TokenType::Less
                };
                self.add_token(token_type);
            }
            '>' => {
                let token_type = if self.match_next('=') {
                    TokenType::GreaterEqual
                } else {
                    TokenType::Greater
                };
                self.add_token(token_type);
            }
            '/' => {
                if self.match_next('/') {
                    while self.peek() != '\n' && !self.is_at_end() {
                        self.advance();
                    }
                } else {
                    self.add_token(TokenType::Slash);
                }
            }
            '\n' => {
                self.line += 1;
                self.column = 0;
            }
            '"' => {
                self.string();
            }
            c if c.is_whitespace() => {
                // skip whitespace
            }
            c if c.is_digit(10) => {
                self.number();
            }
            c if c.is_alphabetic() => {
                self.identifier();
            }
            c => {
                self.lox
                    .error(self.line, &format!("Unexpected character: {}", c));
            }
        }
    }

    pub fn scan_tokens(&mut self) -> Vec<Token> {
        while !self.is_at_end() {
            self.start = self.current;
            self.scan_token();
        }
        self.tokens
            .push(Token::new(TokenType::Eof, "", self.line, self.column));
        self.tokens.clone()
    }
}