use crate::formatter::{FormatTable, Render, format_args::FormatArg};

#[derive(Debug, Clone, Default)]
pub(super) struct Condition<'a> {
    pub tokens: Vec<ConditionalToken<'a>>,
}

impl<'a> Condition<'a> {
    pub(super) fn solve(&self, format_table: &FormatTable) -> bool {
        enum Operator {
            Or(bool),
            And(bool),
            Nor(bool),
            Nand(bool),
        }

        let mut operators = Vec::new();
        let mut condition = false;
        let mut inverted = false;
        let mut can_invert = true;

        let mut iter = self.tokens.iter().peekable();
        while let Some(token) = iter.next() {
            match token {
                ConditionalToken::Arg(arg) => {
                    let mut state = !arg.render(format_table).is_empty();

                    while let Some(ConditionalToken::Arg(peek_arg)) = iter.peek() {
                        if !state {
                            state = !peek_arg.render(format_table).is_empty()
                        }
                        iter.next();
                    }

                    condition = conditional_invert(state, inverted);
                    inverted = false;
                    can_invert = false;
                }
                ConditionalToken::Or => {
                    if let Some(ConditionalToken::Or) = iter.peek() {
                        operators.push(Operator::Or(condition));
                        iter.next();
                        can_invert = true
                    } else {
                        condition = true;
                        can_invert = false;
                    }
                }
                ConditionalToken::And => {
                    if let Some(ConditionalToken::And) = iter.peek() {
                        operators.push(Operator::And(condition));
                        iter.next();
                        can_invert = true
                    } else {
                        condition = true;
                        can_invert = false;
                    }
                }
                ConditionalToken::Not => match iter.peek() {
                    Some(ConditionalToken::Or) => {
                        operators.push(Operator::Nor(condition));
                        iter.next();
                        can_invert = true
                    }
                    Some(ConditionalToken::And) => {
                        operators.push(Operator::Nand(condition));
                        iter.next();
                        can_invert = true
                    }
                    _ if can_invert => inverted = !inverted,
                    _ => {
                        condition = true;
                        can_invert = false;
                    }
                },
            }
        }

        condition = conditional_invert(condition, inverted);
        operators.reverse();
        for operator in operators {
            condition = match operator {
                Operator::Or(lhs) => lhs || condition,
                Operator::And(lhs) => lhs && condition,
                Operator::Nor(lhs) => !(lhs || condition),
                Operator::Nand(lhs) => !(lhs && condition),
            }
        }

        condition
    }
}

#[derive(Debug, Clone)]
pub(super) enum ConditionalToken<'a> {
    Arg(FormatArg<'a>),
    Or,
    And,
    Not,
}

impl<'a> ConditionalToken<'a> {
    pub fn to_str(&self) -> &'static str {
        match self {
            ConditionalToken::Or => "|",
            ConditionalToken::And => "&",
            ConditionalToken::Not => "!",
            ConditionalToken::Arg(_) => panic!(
                "Attempted to turn ConditionalToken::Arg into a &str. This should never happen"
            ),
        }
    }
}

fn conditional_invert(condition: bool, invert: bool) -> bool {
    if invert { !condition } else { condition }
}
