#![allow(dead_code)]

use std::{borrow::Borrow, fmt::Debug, io::Write, path::Path};

use display_tree::{format_tree, Style};
use location::{Location, LocationContainer};
use thiserror::Error;

mod lexer;
mod location;
mod parser;

use lexer::{Lexer, Literal, Token, TokenType, BINARY_OPERATORS, UNARY_OPERATORS};
use parser::{Expression, Parser};

pub enum OutputStyle {
    Tree(Style),
    Sexp,
    Evaluate,
}

pub struct LoxOptions {
    pub output_style: OutputStyle,
}

#[derive(Error, Debug)]
pub enum EvaluationError {
    #[error("[{location}] Invalid binary operator types: {left} {operator:?} {right}")]
    InvalidBinaryOperatorType {
        location: Location,
        left: Literal,
        operator: TokenType,
        right: Literal,
    },

    #[error("[{location}] Invalid binary operator: {operator:?}")]
    InvalidBinaryOperator {
        location: Location,
        operator: TokenType,
    },

    #[error("[{location}] Invalid unary operator type: {operator:?} {right}")]
    InvalidUnaryOperatorType {
        location: Location,
        operator: TokenType,
        right: Literal,
    },

    #[error("[{location}] Invalid unary operator: {operator:?}")]
    InvalidUnaryOperator {
        location: Location,
        operator: TokenType,
    },
}

pub struct Lox {
    had_error: bool,
    pub options: LoxOptions,
}

impl Debug for Lox {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Lox {{ had_error: {} }}", self.had_error)
    }
}

impl Lox {
    pub fn new() -> Self {
        Self {
            had_error: false,
            options: LoxOptions {
                output_style: OutputStyle::Evaluate,
            },
        }
    }

    pub fn error_token(&mut self, token: Token, message: &str) {
        if token.token_type == lexer::TokenType::Eof {
            self.report(token.location.line, " at end", message);
        } else {
            self.report(
                token.location.line,
                &format!(" at '{}'", token.lexeme),
                message,
            );
        }
    }

    pub fn error(&mut self, line: usize, message: &str) {
        self.report(line, "", message);
    }

    fn report(&mut self, line: usize, location: &str, message: &str) {
        eprintln!("[line {line}] Error {location}: {message}");
        self.had_error = true;
    }

    fn evaluate(&self, expr: &Expression) -> Result<LocationContainer<Literal>, EvaluationError> {
        match expr {
            Expression::Binary {
                left,
                operator,
                right,
            } => {
                let left_loc = self.evaluate(left.borrow())?;
                let right_loc = self.evaluate(right.borrow())?;
                let operator_tok = operator;
                match (
                    left_loc.inner,
                    operator_tok.get_token_type(),
                    right_loc.inner,
                ) {
                    (Literal::Number(left), TokenType::Plus, Literal::Number(right)) => {
                        Ok(LocationContainer {
                            inner: Literal::Number(left + right),
                            location: left_loc.location.clone(),
                        })
                    }
                    (Literal::Number(left), TokenType::Minus, Literal::Number(right)) => {
                        Ok(LocationContainer {
                            inner: Literal::Number(left - right),
                            location: left_loc.location.clone(),
                        })
                    }
                    (Literal::Number(left), TokenType::Star, Literal::Number(right)) => {
                        Ok(LocationContainer {
                            inner: Literal::Number(left * right),
                            location: left_loc.location.clone(),
                        })
                    }
                    (Literal::Number(left), TokenType::Slash, Literal::Number(right)) => {
                        Ok(LocationContainer {
                            inner: Literal::Number(left / right),
                            location: left_loc.location.clone(),
                        })
                    }
                    (left, TokenType::Greater, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left > right),
                        location: left_loc.location.clone(),
                    }),
                    (left, TokenType::GreaterEqual, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left >= right),
                        location: left_loc.location.clone(),
                    }),
                    (left, TokenType::Less, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left < right),
                        location: left_loc.location.clone(),
                    }),
                    (left, TokenType::LessEqual, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left <= right),
                        location: left_loc.location.clone(),
                    }),
                    (left, TokenType::EqualEqual, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left == right),
                        location: left_loc.location.clone(),
                    }),
                    (left, TokenType::BangEqual, right) => Ok(LocationContainer {
                        inner: Literal::Boolean(left != right),
                        location: left_loc.location.clone(),
                    }),
                    (Literal::String(left), TokenType::Tilde, Literal::String(right)) => {
                        Ok(LocationContainer {
                            inner: Literal::String(format!("{}{}", left, right)),
                            location: left_loc.location.clone(),
                        })
                    }
                    (left, operator, right) if BINARY_OPERATORS.contains(&operator) => {
                        Err(EvaluationError::InvalidBinaryOperatorType {
                            location: right_loc.location.clone(),
                            left,
                            operator,
                            right,
                        })
                    }
                    (_, operator, _) => Err(EvaluationError::InvalidBinaryOperator {
                        location: operator_tok.location.clone(),
                        operator,
                    }),
                }
            }
            Expression::Grouping { expression } => self.evaluate(expression),
            Expression::Literal { value } => Ok(value.clone()),
            Expression::Unary { operator, right } => {
                let right_loc = self.evaluate(right.borrow())?;
                let operator_tok = operator;
                match (operator_tok.get_token_type(), right_loc.inner) {
                    (lexer::TokenType::Minus, Literal::Number(n)) => Ok(LocationContainer {
                        inner: Literal::Number(-n),
                        location: right_loc.location.clone(),
                    }),
                    (lexer::TokenType::Bang, Literal::Boolean(b)) => Ok(LocationContainer {
                        inner: Literal::Boolean(!b),
                        location: right_loc.location.clone(),
                    }),
                    (operator, right) if UNARY_OPERATORS.contains(&operator) => {
                        Err(EvaluationError::InvalidUnaryOperatorType {
                            location: right_loc.location.clone(),
                            operator,
                            right,
                        })
                    }
                    (operator, _) => Err(EvaluationError::InvalidUnaryOperator {
                        location: operator_tok.location.clone(),
                        operator,
                    }),
                }
            }
        }
    }

    fn run(&mut self, source: String) {
        let mut lexer = Lexer::new(self, &source);
        let tokens = lexer.scan_tokens();
        let mut parser = Parser::new(self, tokens);
        let expression = parser.parse().unwrap();
        match self.options.output_style {
            OutputStyle::Tree(style) => println!("{}", format_tree!(expression, style)),
            OutputStyle::Sexp => println!("{}", expression),
            OutputStyle::Evaluate => {
                let result = self.evaluate(&expression);
                match result {
                    Ok(result) => println!("{}", result),
                    Err(e) => eprintln!("{}", e),
                }
            }
        }
    }

    pub fn run_file<P: AsRef<Path>>(&mut self, file: P) -> anyhow::Result<()> {
        let source = std::fs::read_to_string(file)?;
        self.run(source);
        if self.had_error {
            std::process::exit(65);
        }
        Ok(())
    }

    pub fn run_prompt(&mut self) -> anyhow::Result<()> {
        loop {
            let mut input = String::new();
            print!("> ");
            std::io::stdout().flush()?;
            match std::io::stdin().read_line(&mut input) {
                Ok(0) => break,
                Ok(_) => {
                    self.run(input);
                    self.had_error = false;
                }
                Err(e) => return Err(e.into()),
            }
        }
        Ok(())
    }
}