use crate::{
    errors::FindItError,
    evaluators::expr::{BindingsTypes, Evaluator, EvaluatorFactory},
    file_wrapper::FileWrapper,
    parser::ast::if_expression::If as IfExpression,
    value::{Value, ValueType},
};

struct NoElseIf {
    condition: Box<dyn Evaluator>,
    then: Box<dyn Evaluator>,
}
struct IfWithElse {
    condition: Box<dyn Evaluator>,
    then: Box<dyn Evaluator>,
    else_branch: Box<dyn Evaluator>,
}

impl Evaluator for NoElseIf {
    fn eval(&self, file: &FileWrapper) -> Value {
        match self.condition.eval(file) {
            Value::Bool(true) => self.then.eval(file),
            _ => Value::Empty,
        }
    }
    fn expected_type(&self) -> ValueType {
        self.then.expected_type()
    }
}

impl Evaluator for IfWithElse {
    fn eval(&self, file: &FileWrapper) -> Value {
        match self.condition.eval(file) {
            Value::Bool(true) => self.then.eval(file),
            Value::Bool(false) => self.else_branch.eval(file),
            _ => Value::Empty,
        }
    }
    fn expected_type(&self) -> ValueType {
        self.then.expected_type()
    }
}

impl EvaluatorFactory for IfExpression {
    fn build(&self, bindings: &BindingsTypes) -> Result<Box<dyn Evaluator>, FindItError> {
        let condition = self.condition.build(bindings)?;
        if condition.expected_type() != ValueType::Bool {
            return Err(FindItError::BadExpression(
                "IF condition must be boolean.".into(),
            ));
        }
        let then = self.then_branch.build(bindings)?;
        if let Some(else_branch) = &self.else_branch {
            let else_branch = else_branch.build(bindings)?;
            if else_branch.expected_type() != then.expected_type() {
                return Err(FindItError::BadExpression(
                    "IF results must be the same.".into(),
                ));
            }
            Ok(Box::new(IfWithElse {
                condition,
                then,
                else_branch,
            }))
        } else {
            Ok(Box::new(NoElseIf { condition, then }))
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{evaluators::expr::read_expr, value::ValueType};

    #[test]
    fn test_if_with_different_type() {
        let sql = "IF TRUE THEN 1 ELSE \"one\" END";
        let err = read_expr(sql).err();

        assert!(err.is_some());
    }

    #[test]
    fn test_if_expected_value() {
        let sql = "IF TRUE THEN 1 ELSE 10  END";
        let expr = read_expr(sql).unwrap();

        assert_eq!(expr.expected_type(), ValueType::Number);
    }

    #[test]
    fn test_if_no_else_expected_value() {
        let sql = "IF TRUE THEN 1 END";
        let expr = read_expr(sql).unwrap();

        assert_eq!(expr.expected_type(), ValueType::Number);
    }

    #[test]
    fn test_if_with_numeric_condition() {
        let sql = "IF 200 THEN \"no\" ELSE \"one\" END";
        let err = read_expr(sql).err();

        assert!(err.is_some());
    }
}
