use crate::{
    ast,
    errors::{ErrorHandler, InterpreterError},
    interpreter::Value,
    tokens::{Token, TokenType},
};

use super::Environment;

/// An Interpretable can be evaluated and will return a value.
pub trait Interpretable {
    fn interprete(&self, environment: &mut Environment) -> Result<Value, InterpreterError>;
}

/// Evaluate an interpretable, returning its value.
pub fn evaluate(err_hdl: &mut ErrorHandler, ast: &dyn Interpretable) -> Option<Value> {
    let mut env = Environment::default();
    match ast.interprete(&mut env) {
        Ok(v) => Some(v),
        Err(e) => {
            e.report(err_hdl);
            None
        }
    }
}

/* ----------------------------- *
 * INTERPRETER FOR PROGRAM NODES *
 * ----------------------------- */

impl Interpretable for ast::ProgramNode {
    fn interprete(&self, environment: &mut Environment) -> Result<Value, InterpreterError> {
        for stmt in self.0.iter() {
            stmt.interprete(environment)?;
        }
        Ok(Value::Nil)
    }
}

/* ------------------------------- *
 * INTERPRETER FOR STATEMENT NODES *
 * ------------------------------- */

impl Interpretable for ast::StmtNode {
    fn interprete(&self, environment: &mut Environment) -> Result<Value, InterpreterError> {
        match self {
            ast::StmtNode::Expression(expr) => expr.interprete(environment),
            ast::StmtNode::Print(expr) => {
                let value = expr.interprete(environment)?;
                let output = match value {
                    Value::Nil => String::from("nil"),
                    Value::Boolean(true) => String::from("true"),
                    Value::Boolean(false) => String::from("false"),
                    Value::Number(n) => n.to_string(),
                    Value::String(s) => s,
                };
                println!("{}", output);
                Ok(Value::Nil)
            }
        }
    }
}

/* -------------------------------- *
 * INTERPRETER FOR EXPRESSION NODES *
 * -------------------------------- */

impl Interpretable for ast::ExprNode {
    fn interprete(&self, environment: &mut Environment) -> Result<Value, InterpreterError> {
        match self {
            ast::ExprNode::Binary {
                left,
                operator,
                right,
            } => self.on_binary(environment, left, operator, right),
            ast::ExprNode::Unary { operator, right } => self.on_unary(environment, operator, right),
            ast::ExprNode::Grouping { expression } => expression.interprete(environment),
            ast::ExprNode::Litteral { value } => self.on_litteral(value),
        }
    }
}

impl ast::ExprNode {
    /// Evaluate a binary operator.
    fn on_binary(
        &self,
        environment: &mut Environment,
        left: &ast::ExprNode,
        operator: &Token,
        right: &ast::ExprNode,
    ) -> Result<Value, InterpreterError> {
        let left_value = left.interprete(environment)?;
        let right_value = right.interprete(environment)?;
        match operator.token_type {
            TokenType::Plus => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
                (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Minus => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Star => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)),
                (Value::String(a), Value::Number(b)) => Ok(Value::String(a.repeat(b as usize))),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Slash => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => {
                    if b == 0. {
                        Err(InterpreterError::new(operator, "division by zero"))
                    } else {
                        Ok(Value::Number(a / b))
                    }
                }
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Greater => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a > b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::GreaterEqual => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a >= b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Less => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a < b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::LessEqual => match (left_value, right_value) {
                (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a <= b)),
                _ => Err(InterpreterError::new(operator, "type error")),
            },

            TokenType::Equal => Ok(Value::Boolean(left_value == right_value)),
            TokenType::BangEqual => Ok(Value::Boolean(left_value != right_value)),

            _ => panic!(
                "Unsupported token type for binary operator (token {:?})",
                operator
            ),
        }
    }

    /// Evaluate an unary operator.
    fn on_unary(&self, environment: &mut Environment, operator: &Token, right: &ast::ExprNode) -> Result<Value, InterpreterError> {
        let right_value = right.interprete(environment)?;
        match operator.token_type {
            TokenType::Minus => {
                if let Value::Number(n) = right_value {
                    Ok(Value::Number(-n))
                } else {
                    Err(InterpreterError::new(operator, "number expected"))
                }
            }

            TokenType::Bang => Ok(Value::Boolean(!right_value.is_truthy())),

            _ => panic!(
                "Unsupported token type for unary operator (token {:?})",
                operator
            ),
        }
    }

    /// Evaluate a litteral.
    fn on_litteral(&self, value: &Token) -> Result<Value, InterpreterError> {
        match &value.token_type {
            TokenType::Nil => Ok(Value::Nil),
            TokenType::True => Ok(Value::Boolean(true)),
            TokenType::False => Ok(Value::Boolean(false)),
            TokenType::Number(n) => Ok(Value::Number(*n)),
            TokenType::String(s) => Ok(Value::String(s.clone())),
            _ => panic!("Unsupported token type for litteral (token {:?})", value),
        }
    }
}