use std::rc::Rc;

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

#[derive(Debug, PartialEq)]
pub struct Assignment {
    pub identifier: IdentifierValue,
    pub value: Rc<Expression>,
}

#[derive(Debug, PartialEq)]
pub struct SetqExpression {
    pub pairs: Vec<Assignment>,
}

#[derive(Debug, PartialEq)]
pub struct LetExpression {
    pub sequential: bool,
    pub bindings: Vec<Assignment>,
    pub body: FunctionBody,
}

impl Assignment {
    fn parse_list(value: &Value, input: &Value, after: Keyword) -> Result<Vec<Self>, ParseError> {
        let mut iter = value.syntax_iter(ExpectedWhere::InAssignment);
        let mut pairs = vec![];
        loop {
            let [identifier, value] = match iter.next_chunk::<2>() {
                Ok(pair) => pair,
                Err(rest) => {
                    if !rest.is_empty() {
                        return Err(ParseError {
                            input: input.clone(),
                            error: ParseErrorKind::try_collect_extraneous(
                                rest.map(|x| x.cloned()),
                            )?,
                        });
                    }
                    break;
                }
            };
            let Value::Identifier(identifier) = identifier? else {
                return Err(ParseError {
                    input: input.clone(),
                    error: ParseErrorKind::Expected(
                        ExpectedWhat::Identifier,
                        ExpectedWhere::InAssignment,
                    ),
                });
            };
            let value = Expression::parse_inner(value?);
            pairs.push(Self {
                identifier: identifier.clone(),
                value,
            });
        }
        if pairs.is_empty() {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::AtLeastOneBinding,
                    ExpectedWhere::AfterKeyword(after),
                ),
            });
        }
        Ok(pairs)
    }
}

impl LetExpression {
    pub(super) fn parse(
        value: &Value,
        input: &Value,
        keyword: Keyword,
    ) -> Result<Self, ParseError> {
        let Value::Cons(value) = value else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::AfterKeyword(keyword),
                ),
            });
        };
        let ConsCell(bindings, cdr) = value.as_ref();
        let bindings = Assignment::parse_list(bindings, input, keyword)?;
        let body = FunctionBody::parse(cdr, input, keyword)?;
        let sequential = keyword == Keyword::LetStar;
        Ok(Self {
            sequential,
            bindings,
            body,
        })
    }
}

impl SetqExpression {
    pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
        let pairs = Assignment::parse_list(value, input, Keyword::Setq)?;
        Ok(Self { pairs })
    }
}

impl CollectErrors<ParseError> for SetqExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        self.pairs.iter().fold(false, |acc, v| {
            let r = v.collect_errors(errors);
            acc | r
        })
    }
}

impl CollectErrors<ParseError> for LetExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        let r0 = self.bindings.iter().fold(false, |acc, v| {
            let r = v.collect_errors(errors);
            acc | r
        });
        let r1 = self.body.collect_errors(errors);
        r0 | r1
    }
}

impl CollectErrors<ParseError> for Assignment {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        self.value.collect_errors(errors)
    }
}

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

    use crate::{
        compile::syntax::{
            Assignment, Expression, FunctionBody, LetExpression, SetqExpression,
            error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
        },
        vm::value::{ConsCell, Keyword, Value},
    };

    #[test]
    fn test_parse_let() {
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::Let),
            Value::list_or_nil([Value::Identifier("a".into()), Value::Number(1.into())]),
            Value::Nil,
        ]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Let(LetExpression {
                sequential: false,
                bindings: vec![Assignment {
                    identifier: "a".into(),
                    value: Rc::new(Expression::IntegerLiteral(1.into()))
                },],
                body: FunctionBody {
                    head: vec![],
                    tail: Rc::new(Expression::Nil)
                },
            })
        );
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::LetStar),
            Value::list_or_nil([
                Value::Identifier("a".into()),
                Value::Number(1.into()),
                Value::Identifier("b".into()),
                Value::Number(2.into()),
            ]),
            Value::Nil,
        ]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Let(LetExpression {
                sequential: true,
                bindings: vec![
                    Assignment {
                        identifier: "a".into(),
                        value: Rc::new(Expression::IntegerLiteral(1.into()))
                    },
                    Assignment {
                        identifier: "b".into(),
                        value: Rc::new(Expression::IntegerLiteral(2.into()))
                    }
                ],
                body: FunctionBody {
                    head: vec![],
                    tail: Rc::new(Expression::Nil)
                },
            })
        );

        // syntax errors
        let v = Value::Cons(Rc::new(ConsCell(
            Value::Keyword(Keyword::Let),
            Value::Identifier("a".into()),
        )));
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            &e[..],
            &[ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::AfterKeyword(Keyword::Let)
                )
            }]
        );
    }

    #[test]
    fn test_parse_setq() {
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::Setq),
            Value::Identifier("a".into()),
            Value::Identifier("b".into()),
            Value::Identifier("c".into()),
            Value::Identifier("d".into()),
        ]);
        let e = Expression::parse(&v).unwrap();
        assert_eq!(
            e.as_ref(),
            &Expression::Setq(SetqExpression {
                pairs: vec![
                    Assignment {
                        identifier: "a".into(),
                        value: Rc::new(Expression::Identifier("b".into())),
                    },
                    Assignment {
                        identifier: "c".into(),
                        value: Rc::new(Expression::Identifier("d".into())),
                    }
                ]
            })
        );

        // syntax errors
        let v = Value::list_or_nil([Value::Keyword(Keyword::Setq)]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            &e[..],
            &[ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::AtLeastOneBinding,
                    ExpectedWhere::AfterKeyword(Keyword::Setq)
                )
            }]
        );
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::Setq),
            Value::Identifier("a".into()),
            Value::Number(1.into()),
            Value::Identifier("b".into()),
        ]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            &e[..],
            &[ParseError {
                input: v,
                error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell(
                    Value::Identifier("b".into()),
                    Value::Nil
                )))
            }]
        );
        let v = Value::list_or_nil([
            Value::Keyword(Keyword::Setq),
            Value::Number(1.into()),
            Value::Identifier("a".into()),
        ]);
        let e = Expression::parse(&v).unwrap_err();
        assert_eq!(
            &e[..],
            &[ParseError {
                input: v,
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Identifier,
                    ExpectedWhere::InAssignment
                )
            }]
        );
    }
}
