use syn::{Expr, Ident, ItemEnum};

use crate::parse::Parsed;

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct ValueExpr {
    pub(crate) base: Option<Expr>,
    pub(crate) offset: u64,
}

impl ValueExpr {
    fn new() -> Self {
        Default::default()
    }

    fn from_expr(expr: Expr) -> Self {
        Self {
            base: Some(expr),
            offset: 0,
        }
    }

    fn next(&self) -> Self {
        Self {
            base: self.base.clone(),
            offset: self.offset + 1,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ValueExprMatch(pub(crate) Ident, pub(crate) ValueExpr);

pub(crate) struct Transformed {
    pub(crate) repr_type: Ident,
    pub(crate) variants: Vec<ValueExprMatch>,
    pub(crate) item: ItemEnum,
}

pub(crate) fn transform(ast: Parsed) -> Transformed {
    let Parsed {
        attr: repr_type,
        item,
    } = ast;

    let mut variants = Vec::with_capacity(item.variants.len());
    let mut value = ValueExpr::new();

    for variant in item.variants.iter() {
        if let Some((_, ref expr)) = variant.discriminant {
            value = ValueExpr::from_expr(expr.clone());
        }
        variants.push(ValueExprMatch(variant.ident.clone(), value.clone()));
        value = value.next();
    }

    Transformed {
        repr_type,
        variants,
        item,
    }
}

#[cfg(test)]
mod tests {
    use syn::parse_quote;

    use super::*;

    #[test]
    fn builds_model_for_enum_with_u16_repr() {
        let ast = Parsed {
            attr: parse_quote!(u16),
            item: parse_quote!(
                enum Fieldless {
                    Zeroth,
                    First = 2 * 7,
                    Second,
                    Third = 1,
                }
            ),
        };
        let model = transform(ast);
        assert!(matches!(model.repr_type.to_string().as_str(), "u16"));
        assert_eq!(model.variants.len(), 4);
        assert_eq!(
            model.variants[0],
            ValueExprMatch(
                parse_quote!(Zeroth),
                ValueExpr {
                    base: None,
                    offset: 0,
                }
            )
        );
        assert_eq!(
            model.variants[1],
            ValueExprMatch(
                parse_quote!(First),
                ValueExpr {
                    base: Some(parse_quote! {2 * 7}),
                    offset: 0,
                }
            )
        );
        assert_eq!(
            model.variants[2],
            ValueExprMatch(
                parse_quote!(Second),
                ValueExpr {
                    base: Some(parse_quote! {2 * 7}),
                    offset: 1,
                }
            )
        );
        assert_eq!(
            model.variants[3],
            ValueExprMatch(
                parse_quote!(Third),
                ValueExpr {
                    base: Some(parse_quote! {1}),
                    offset: 0,
                }
            )
        );
    }
}
