use std::rc::Rc;

use crate::{
    compile::syntax::{
        CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
    },
    vm::value::{ConsCell, Value},
};

#[derive(Debug, PartialEq)]
pub struct CallExpression {
    pub callee: Rc<Expression>,
    pub arguments: Vec<Rc<Expression>>,
}

impl CallExpression {
    pub(super) fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
        let ConsCell(car, cdr) = cons;
        let callee = Expression::parse_inner(car);

        let mut arguments = vec![];
        let mut list = cdr;
        while !list.is_nil() {
            let Value::Cons(cons) = list else {
                return Err(ParseError {
                    input: input.clone(),
                    error: ParseErrorKind::Expected(
                        ExpectedWhat::ProperList,
                        ExpectedWhere::InCallExpression,
                    ),
                });
            };
            let ConsCell(car, cdr) = cons.as_ref();

            let expression = Expression::parse_inner(car);
            arguments.push(expression);

            list = cdr;
        }

        Ok(Self { callee, arguments })
    }
}

impl CollectErrors<ParseError> for CallExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        let mut r = self.callee.collect_errors(errors);
        for expr in self.arguments.iter() {
            r |= expr.collect_errors(errors);
        }
        r
    }
}

#[cfg(test)]
mod tests {
    use std::rc::Rc;

    use crate::{
        compile::syntax::{
            CallExpression, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
        },
        vm::value::Value,
    };

    #[test]
    fn test_parse_call() {
        let v = Value::list_or_nil([Value::Identifier("a".into())]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Call(CallExpression {
                callee: Rc::new(Expression::Identifier("a".into())),
                arguments: vec![]
            })
        );

        // Syntax errors
        let v = Value::Identifier("a".into()).cons(Value::Number(1.into()));
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::InCallExpression
                ),
            }]
        );
    }
}
