use std::rc::Rc;

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

#[derive(Debug, PartialEq)]
pub struct IfExpression {
    pub condition: Rc<Expression>,
    pub if_true: Rc<Expression>,
    pub if_false: Option<Rc<Expression>>,
}

#[derive(Debug, PartialEq)]
pub struct CondExpressionArm {
    pub condition: Rc<Expression>,
    pub then: Rc<Expression>,
}
#[derive(Debug, PartialEq)]
pub struct CondExpression {
    pub arms: Vec<CondExpressionArm>,
    pub otherwise_arm: Option<Rc<Expression>>,
}

impl IfExpression {
    pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
        let Value::Cons(value) = value else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Condition,
                    ExpectedWhere::AfterKeyword(Keyword::If),
                ),
            });
        };
        let ConsCell(condition, cdr) = value.as_ref();
        let condition = Expression::parse_inner(condition);
        let Value::Cons(cdr) = cdr else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Expression,
                    ExpectedWhere::AfterCondition,
                ),
            });
        };
        let ConsCell(if_true, cdr) = cdr.as_ref();
        let if_true = Expression::parse_inner(if_true);
        let if_false = if cdr.is_nil() {
            None
        } else {
            let Value::Cons(cdr) = cdr else { todo!() };
            let ConsCell(if_false, cdr) = cdr.as_ref();
            if !cdr.is_nil() {
                return Err(ParseError {
                    input: input.clone(),
                    error: ParseErrorKind::extraneous(cdr),
                });
            }
            Some(Expression::parse_inner(if_false))
        };
        Ok(Self {
            condition,
            if_true,
            if_false,
        })
    }
}

impl CondExpressionArm {
    fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
        let ConsCell(condition, cdr) = cons;
        let condition = Expression::parse_inner(condition);
        let Value::Cons(cons) = cdr else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Expression,
                    ExpectedWhere::InConditionArm,
                ),
            });
        };
        let ConsCell(then, cdr) = cons.as_ref();
        if !cdr.is_nil() {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::extraneous(cdr),
            });
        }
        let then = Expression::parse_inner(then);

        Ok(Self { condition, then })
    }
}

impl CondExpression {
    pub(super) fn parse(mut value: &Value, input: &Value) -> Result<Self, ParseError> {
        let mut arms = vec![];
        let mut otherwise_arm = None;

        while !value.is_nil() {
            let Value::Cons(cons) = value else {
                return Err(ParseError {
                    input: input.clone(),
                    error: ParseErrorKind::Expected(
                        ExpectedWhat::ConditionList,
                        ExpectedWhere::AfterKeyword(Keyword::Cond),
                    ),
                });
            };
            let ConsCell(arm, cdr) = cons.as_ref();

            // Arm must be a list
            let Value::Cons(arm_cons) = arm else {
                return Err(ParseError {
                    input: arm.clone(),
                    error: ParseErrorKind::Expected(
                        ExpectedWhat::Condition,
                        ExpectedWhere::InConditionList,
                    ),
                });
            };
            let ConsCell(arm_condition, arm_condition_cdr) = arm_cons.as_ref();

            match arm_condition {
                Value::Keyword(Keyword::Otherwise) => {
                    if otherwise_arm.is_some() {
                        return Err(ParseError {
                            input: input.clone(),
                            error: ParseErrorKind::extraneous(arm),
                        });
                    }
                    let Value::Cons(otherwise_cons) = arm_condition_cdr else {
                        return Err(ParseError {
                            input: arm.clone(),
                            error: ParseErrorKind::Expected(
                                ExpectedWhat::ProperList,
                                ExpectedWhere::InConditionList,
                            ),
                        });
                    };
                    let ConsCell(arm_expr, arm_cdr) = otherwise_cons.as_ref();
                    if !arm_cdr.is_nil() {
                        return Err(ParseError {
                            input: arm.clone(),
                            error: ParseErrorKind::extraneous(arm_cdr),
                        });
                    }
                    let arm = Expression::parse_inner(arm_expr);
                    otherwise_arm = Some(arm);
                }
                _ => {
                    if otherwise_arm.is_some() {
                        return Err(ParseError {
                            input: input.clone(),
                            error: ParseErrorKind::extraneous(arm),
                        });
                    }
                    let arm = CondExpressionArm::parse(arm_cons, arm)?;
                    arms.push(arm);
                }
            }

            value = cdr;
        }

        if arms.is_empty() {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ConditionList,
                    ExpectedWhere::AfterKeyword(Keyword::Cond),
                ),
            });
        }

        Ok(Self {
            arms,
            otherwise_arm,
        })
    }
}

impl CollectErrors<ParseError> for IfExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        let a = self.condition.collect_errors(errors);
        let b = self.if_true.collect_errors(errors);
        let c = self
            .if_false
            .as_ref()
            .map(|x| x.collect_errors(errors))
            .unwrap_or_default();
        a | b | c
    }
}

impl CollectErrors<ParseError> for CondExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        let mut r = false;
        for arm in self.arms.iter() {
            r |= arm.condition.collect_errors(errors);
            r |= arm.then.collect_errors(errors);
        }
        if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
            r |= otherwise_arm.collect_errors(errors);
        }
        r
    }
}

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

    use crate::{
        compile::syntax::{
            CondExpression, CondExpressionArm, ExpectedWhat, ExpectedWhere, Expression,
            IfExpression, ParseError, ParseErrorKind,
        },
        vm::value::{ConsCell, Keyword, Value},
    };

    #[test]
    fn test_parse_cond() {
        // without &otherwise branch
        let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
        let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Identifier("b".into())]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Cond(CondExpression {
                arms: vec![
                    CondExpressionArm {
                        condition: Rc::new(Expression::BooleanLiteral(true.into())),
                        then: Rc::new(Expression::Identifier("a".into()))
                    },
                    CondExpressionArm {
                        condition: Rc::new(Expression::BooleanLiteral(false.into())),
                        then: Rc::new(Expression::Identifier("b".into()))
                    }
                ],
                otherwise_arm: None
            })
        );

        // with &otherwise branch
        let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
        let b1 = Value::list_or_nil([
            Value::Keyword(Keyword::Otherwise),
            Value::Identifier("b".into()),
        ]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Cond(CondExpression {
                arms: vec![CondExpressionArm {
                    condition: Rc::new(Expression::BooleanLiteral(true.into())),
                    then: Rc::new(Expression::Identifier("a".into()))
                },],
                otherwise_arm: Some(Rc::new(Expression::Identifier("b".into())))
            })
        );

        // syntax error
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond)]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ConditionList,
                    ExpectedWhere::AfterKeyword(Keyword::Cond)
                )
            }]
        );

        let v = Value::Keyword(Keyword::Cond).cons(Value::Identifier("a".into()));
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ConditionList,
                    ExpectedWhere::AfterKeyword(Keyword::Cond)
                )
            }]
        );

        let b0 = Value::list_or_nil([Value::Boolean(false.into())]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Expression,
                    ExpectedWhere::InConditionArm
                )
            }]
        );
        let b0 = Value::Boolean(false.into());
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Condition,
                    ExpectedWhere::InConditionList
                )
            }]
        );
        let b0 = Value::Keyword(Keyword::Otherwise);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Condition,
                    ExpectedWhere::InConditionList
                )
            }]
        );
        let b0 = Value::Keyword(Keyword::Otherwise).cons(Value::Number(1.into()));
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::InConditionList
                )
            }]
        );
        let b0 = Value::Boolean(false.into()).cons(Value::Boolean(false.into()));
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Expression,
                    ExpectedWhere::InConditionArm
                )
            }]
        );
        let b0 = Value::list_or_nil([
            Value::Boolean(false.into()),
            Value::Number(1.into()),
            Value::Number(2.into()),
        ]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
            }]
        );
        let b0 = Value::list_or_nil([
            Value::Keyword(Keyword::Otherwise),
            Value::Number(1.into()),
            Value::Number(2.into()),
        ]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: b0,
                error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
            }]
        );
        let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone(), b0.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::extraneous(&b0)
            }]
        );
        let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
        let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Number(1.into())]);
        let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1.clone()]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::extraneous(&b1)
            }]
        );
    }

    #[test]
    fn test_parse_if() {
        // without false branch
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::If),
            Value::Boolean(true.into()),
            Value::Number(1.into()),
        ]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::If(IfExpression {
                condition: Rc::new(Expression::BooleanLiteral(true.into())),
                if_true: Rc::new(Expression::IntegerLiteral(1.into())),
                if_false: None
            })
        );

        // with false branch
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::If),
            Value::Boolean(false.into()),
            Value::Number(1.into()),
            Value::Number(2.into()),
        ]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::If(IfExpression {
                condition: Rc::new(Expression::BooleanLiteral(false.into())),
                if_true: Rc::new(Expression::IntegerLiteral(1.into())),
                if_false: Some(Rc::new(Expression::IntegerLiteral(2.into())))
            })
        );

        // invalid syntax
        let v = Value::list_or_nil([Value::Keyword(Keyword::If)]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Condition,
                    ExpectedWhere::AfterKeyword(Keyword::If)
                )
            }]
        );
        let v = Value::list_or_nil([Value::Keyword(Keyword::If), Value::Boolean(true.into())]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Expression,
                    ExpectedWhere::AfterCondition
                )
            }]
        );
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::If),
            Value::Boolean(true.into()),
            Value::Number(1.into()),
            Value::Number(2.into()),
            Value::Number(3.into()),
        ]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            e,
            vec![ParseError {
                input: v,
                error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell::end(
                    Value::Number(3.into())
                )))
            }]
        );
    }
}
