use std::{
    collections::HashMap,
    ops::{AddAssign, DivAssign, MulAssign, SubAssign},
};
use fontdrasil::coords::NormalizedLocation;
use crate::typed;
#[derive(Debug, PartialEq)]
pub enum ResolvedValue {
    Scalar(i16),
    Variable(HashMap<NormalizedLocation, i16>),
}
#[derive(Debug)]
enum Value {
    Lit(f64),
    Named(HashMap<NormalizedLocation, f64>),
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum Operator {
    Plus,
    Minus,
    Mul,
    Div,
}
impl Operator {
    fn priority(self) -> u8 {
        match self {
            Operator::Plus | Operator::Minus => 5,
            Operator::Mul | Operator::Div => 42,
        }
    }
}
impl Value {
                    fn apply(&mut self, mut rhs: Self, op: Operator) {
                        if matches!((&self, &rhs), (Value::Lit(_), Value::Named(_))) {
            std::mem::swap(self, &mut rhs);
        }
        match op {
            Operator::Plus => self.do_op(rhs, f64::add_assign),
            Operator::Minus => self.do_op(rhs, f64::sub_assign),
            Operator::Mul => self.do_op(rhs, f64::mul_assign),
            Operator::Div => self.do_op(rhs, f64::div_assign),
        };
    }
    fn do_op(&mut self, rhs: Self, op: impl Fn(&mut f64, f64)) {
        match (self, rhs) {
            (Value::Lit(v1), Value::Lit(v2)) => op(v1, v2),
            (Value::Named(v1), Value::Named(v2)) => {
                for (k, v) in v1.iter_mut() {
                    op(v, v2.get(k).copied().unwrap_or_default());
                }
            }
            (Value::Named(v1), Value::Lit(v2)) => {
                v1.values_mut().for_each(|v| op(v, v2));
            }
            _ => unreachable!("normalized in apply"),
        }
    }
}
pub(crate) fn resolve_glyphs_app_expr(
    expr: &typed::GlyphsAppNumberExpr,
    mut var_info_fn: impl FnMut(&str) -> HashMap<NormalizedLocation, f64>,
) -> ResolvedValue {
    fn process_op(stack: &mut Vec<Value>, op: Operator) {
        let rhs = stack.pop().unwrap();
        stack.last_mut().unwrap().apply(rhs, op);
    }
    let (mut val_stack, mut op_stack) = (Vec::new(), Vec::<Operator>::new());
    for item in expr.items() {
        match item {
            typed::GlyphsAppExprItem::Ident(ident) => {
                let val = var_info_fn(ident.text());
                val_stack.push(Value::Named(val));
            }
            typed::GlyphsAppExprItem::Lit(lit) => val_stack.push(Value::Lit(lit.parse() as _)),
                        typed::GlyphsAppExprItem::Operator(op) => {
                let op = Operator::from(op);
                while op_stack.last().map(|x| x.priority()).unwrap_or_default() >= op.priority() {
                    process_op(&mut val_stack, op_stack.pop().unwrap());
                }
                op_stack.push(op);
            }
        }
    }
        while let Some(op) = op_stack.pop() {
        process_op(&mut val_stack, op);
    }
    let result = match val_stack.pop() {
                Some(Value::Named(val)) => {
            ResolvedValue::Variable(val.into_iter().map(|(k, v)| (k, v.round() as _)).collect())
        }
        Some(Value::Lit(val)) => ResolvedValue::Scalar(val.round() as _),
        _ => ResolvedValue::Scalar(0),
    };
    assert!(val_stack.is_empty());
    result
}
impl From<typed::GlyphsAppOperator> for Operator {
    fn from(src: typed::GlyphsAppOperator) -> Operator {
        match src {
            typed::GlyphsAppOperator::Plus(_) => Operator::Plus,
            typed::GlyphsAppOperator::Minus(_) => Operator::Minus,
            typed::GlyphsAppOperator::Mul(_) => Operator::Mul,
            typed::GlyphsAppOperator::Div(_) => Operator::Div,
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::TokenSet;
    use self::typed::AstNode;
    use super::*;
    fn parse_expr(text: &str) -> typed::GlyphsAppNumberExpr {
        let node = crate::parse::parse_node(text, |p| {
            crate::parse::grammar::expect_glyphs_number_value(p, TokenSet::EMPTY);
        });
        let node = typed::GlyphsAppNumber::cast(&node.into()).unwrap();
        node.iter()
            .find_map(typed::GlyphsAppNumberExpr::cast)
            .unwrap()
    }
    fn simple_number_value() -> HashMap<NormalizedLocation, f64> {
        let loc1 = NormalizedLocation::for_pos(&[("wght", 0.1)]);
        let loc2 = NormalizedLocation::for_pos(&[("wght", 0.9)]);
        HashMap::from([(loc1, 10.), (loc2, 23.)])
    }
        fn ordered_eq<const N: usize>(lhs: &ResolvedValue, rhs: [i16; N]) -> bool {
        let ResolvedValue::Variable(lhs) = lhs else {
            return false;
        };
        let mut ordered: Vec<_> = lhs.iter().collect();
        ordered.sort_by_key(|(k, _)| *k);
        let ordered = ordered.iter().map(|(_, v)| **v).collect::<Vec<_>>();
        ordered == rhs
    }
    #[test]
    fn scalar_arithmetic() {
        let text = "${2 + 4 * 5 / 7 - 3}";
        let expr = parse_expr(text);
        let val = resolve_glyphs_app_expr(&expr, |_| Default::default());
        let expected = (2f64 + 4. * 5. / 7. - 3.).round() as i16;
        assert_eq!(val, ResolvedValue::Scalar(expected));
    }
    #[test]
    fn mixed_arithmetic() {
        let text = "${padding * 2}";
        let expr = parse_expr(text);
        let val: ResolvedValue = resolve_glyphs_app_expr(&expr, |_| simple_number_value());
        assert!(ordered_eq(&val, [20i16, 46]));
    }
    #[test]
    fn mixed_arithmetic_other_order() {
        let text = "${2 * padding}";
        let expr = parse_expr(text);
        let val: ResolvedValue = resolve_glyphs_app_expr(&expr, |_| simple_number_value());
        assert!(ordered_eq(&val, [20i16, 46]));
    }
    #[test]
    fn negative_numbers() {
        let text = "${2 - -3}";
        let expr = parse_expr(text);
        let val = resolve_glyphs_app_expr(&expr, |_| Default::default());
        assert_eq!(val, ResolvedValue::Scalar(2 + 3));
    }
    #[test]
    fn negative_parse_ambiguity() {
        let text = "${2-3}";
        let expr = parse_expr(text);
        let val = resolve_glyphs_app_expr(&expr, |_| Default::default());
        assert_eq!(val, ResolvedValue::Scalar(2 - 3));
    }
    #[test]
    fn float_math() {
        let text = "${padding/2.4}";
        let expr = parse_expr(text);
        let val: ResolvedValue = resolve_glyphs_app_expr(&expr, |_| simple_number_value());
        assert!(ordered_eq(&val, [4i16, 10]));
    }
    #[test]
    fn bare_values() {
        let expr = parse_expr("${padding}");
        let val: ResolvedValue = resolve_glyphs_app_expr(&expr, |_| simple_number_value());
        assert!(ordered_eq(&val, [10i16, 23]));
        let expr = parse_expr("${42}");
        let val = resolve_glyphs_app_expr(&expr, |_| Default::default());
        assert_eq!(val, ResolvedValue::Scalar(42));
    }
}