use std::ops::{Deref, DerefMut};
use crate::nodes::{
    AssignStatement, BinaryExpression, Block, CompoundAssignStatement, DoStatement, Expression,
    FieldExpression, IndexExpression, LocalAssignStatement, Prefix, Statement, Variable,
};
use crate::process::{DefaultVisitor, IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
use crate::rules::{
    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
};
use super::{verify_no_rule_properties, RemoveCommentProcessor, RemoveWhitespacesProcessor};
struct Processor {
    identifier_tracker: IdentifierTracker,
    remove_comments: RemoveCommentProcessor,
    remove_spaces: RemoveWhitespacesProcessor,
}
impl Processor {
    #[inline]
    fn generate_variable(&mut self) -> String {
        self.identifier_tracker
            .generate_identifier_with_prefix("__DARKLUA_VAR")
    }
    fn simplify_prefix(&self, prefix: &Prefix) -> Option<Prefix> {
        match prefix {
            Prefix::Parenthese(parenthese) => {
                if let Expression::Identifier(identifier) = parenthese.inner_expression() {
                    Some(Prefix::from(identifier.clone()))
                } else {
                    None
                }
            }
            Prefix::Identifier(_) | Prefix::Call(_) | Prefix::Field(_) | Prefix::Index(_) => None,
        }
    }
    fn remove_parentheses(&self, expression: impl Into<Expression>) -> Expression {
        let expression = expression.into();
        if let Expression::Parenthese(parenthese) = expression {
            parenthese.into_inner_expression()
        } else {
            expression
        }
    }
    fn replace_with(&mut self, assignment: &CompoundAssignStatement) -> Option<Statement> {
        match assignment.get_variable() {
            Variable::Index(index) => {
                let prefix_assignment = match index.get_prefix() {
                    Prefix::Identifier(_) => None,
                    Prefix::Parenthese(parenthese)
                        if matches!(
                            parenthese.inner_expression(),
                            Expression::False(_)
                                | Expression::Identifier(_)
                                | Expression::Number(_)
                                | Expression::Nil(_)
                                | Expression::String(_)
                                | Expression::True(_)
                                | Expression::VariableArguments(_)
                        ) =>
                    {
                        None
                    }
                    Prefix::Call(_)
                    | Prefix::Field(_)
                    | Prefix::Index(_)
                    | Prefix::Parenthese(_) => Some(self.generate_variable()),
                };
                let index_assignment = match index.get_index() {
                    Expression::False(_)
                    | Expression::Identifier(_)
                    | Expression::Number(_)
                    | Expression::Nil(_)
                    | Expression::InterpolatedString(_)
                    | Expression::String(_)
                    | Expression::True(_)
                    | Expression::VariableArguments(_) => None,
                    Expression::Parenthese(parenthese)
                        if matches!(
                            parenthese.inner_expression(),
                            Expression::False(_)
                                | Expression::Identifier(_)
                                | Expression::Number(_)
                                | Expression::Nil(_)
                                | Expression::String(_)
                                | Expression::True(_)
                                | Expression::VariableArguments(_)
                        ) =>
                    {
                        None
                    }
                    Expression::Binary(_)
                    | Expression::Call(_)
                    | Expression::Field(_)
                    | Expression::Function(_)
                    | Expression::If(_)
                    | Expression::Index(_)
                    | Expression::Parenthese(_)
                    | Expression::Table(_)
                    | Expression::TypeCast(_)
                    | Expression::Unary(_) => Some(self.generate_variable()),
                };
                match (prefix_assignment, index_assignment) {
                    (None, None) => {
                        let variable = IndexExpression::new(
                            self.simplify_prefix(index.get_prefix())
                                .unwrap_or_else(|| index.get_prefix().clone()),
                            self.remove_parentheses(index.get_index().clone()),
                        );
                        Some(self.create_new_assignment_with_variable(
                            assignment,
                            variable.clone().into(),
                            Some(variable.into()),
                        ))
                    }
                    (None, Some(index_variable)) => {
                        let assign = LocalAssignStatement::from_variable(index_variable.clone())
                            .with_value(self.remove_parentheses(index.get_index().clone()));
                        let variable = IndexExpression::new(
                            self.simplify_prefix(index.get_prefix())
                                .unwrap_or_else(|| index.get_prefix().clone()),
                            Expression::identifier(index_variable),
                        );
                        Some(self.create_do_assignment(assignment, assign, variable))
                    }
                    (Some(prefix_variable), None) => {
                        let assign = LocalAssignStatement::from_variable(prefix_variable.clone())
                            .with_value(self.remove_parentheses(index.get_prefix().clone()));
                        let variable = IndexExpression::new(
                            Prefix::from_name(prefix_variable),
                            index.get_index().clone(),
                        );
                        Some(self.create_do_assignment(assignment, assign, variable))
                    }
                    (Some(prefix_variable), Some(index_variable)) => {
                        let assign = LocalAssignStatement::from_variable(prefix_variable.clone())
                            .with_value(self.remove_parentheses(index.get_prefix().clone()))
                            .with_variable(index_variable.clone())
                            .with_value(self.remove_parentheses(index.get_index().clone()));
                        let variable = IndexExpression::new(
                            Prefix::from_name(prefix_variable),
                            Expression::identifier(index_variable),
                        );
                        Some(self.create_do_assignment(assignment, assign, variable))
                    }
                }
            }
            Variable::Field(field) => match field.get_prefix() {
                Prefix::Identifier(_) => None,
                Prefix::Parenthese(parenthese)
                    if matches!(
                        parenthese.inner_expression(),
                        Expression::False(_)
                            | Expression::Identifier(_)
                            | Expression::Number(_)
                            | Expression::Nil(_)
                            | Expression::String(_)
                            | Expression::True(_)
                            | Expression::VariableArguments(_)
                    ) =>
                {
                    let new_prefix =
                        if let Expression::Identifier(identifier) = parenthese.inner_expression() {
                            Prefix::from(identifier.clone())
                        } else {
                            parenthese.clone().into()
                        };
                    let new_variable = FieldExpression::new(new_prefix, field.get_field().clone());
                    Some(self.create_new_assignment_with_variable(
                        assignment,
                        new_variable.clone().into(),
                        Some(new_variable.into()),
                    ))
                }
                Prefix::Call(_) | Prefix::Field(_) | Prefix::Index(_) | Prefix::Parenthese(_) => {
                    let identifier = self.generate_variable();
                    let new_variable = FieldExpression::new(
                        Prefix::from_name(&identifier),
                        field.get_field().clone(),
                    );
                    let assign = LocalAssignStatement::from_variable(identifier).with_value(
                        match field.get_prefix().clone() {
                            Prefix::Parenthese(parenthese) => parenthese.into_inner_expression(),
                            prefix => prefix.into(),
                        },
                    );
                    Some(self.create_do_assignment(assignment, assign, new_variable))
                }
            },
            Variable::Identifier(_) => None,
        }
    }
    fn create_do_assignment(
        &mut self,
        compound_assignment: &CompoundAssignStatement,
        assign: impl Into<Statement>,
        variable: impl Into<Variable>,
    ) -> Statement {
        let variable = variable.into();
        DoStatement::new(
            Block::default()
                .with_statement(assign.into())
                .with_statement(self.create_new_assignment_with_variable(
                    compound_assignment,
                    variable.clone().into(),
                    Some(variable),
                )),
        )
        .into()
    }
    fn create_new_assignment(
        &mut self,
        assignment: &CompoundAssignStatement,
        variable: impl Into<Expression>,
    ) -> Statement {
        self.create_new_assignment_with_variable(assignment, variable.into(), None)
    }
    fn create_new_assignment_with_variable(
        &mut self,
        assignment: &CompoundAssignStatement,
        mut value: Expression,
        variable: Option<Variable>,
    ) -> Statement {
        let operator = assignment.get_operator().to_binary_operator();
        DefaultVisitor::visit_expression(&mut value, &mut self.remove_comments);
        DefaultVisitor::visit_expression(&mut value, &mut self.remove_spaces);
        let mut expression = BinaryExpression::new(operator, value, assignment.get_value().clone());
        if let Some(token) = assignment.get_tokens().map(|tokens| {
            let mut new_token = tokens.operator.clone();
            new_token.replace_with_content(operator.to_str());
            new_token
        }) {
            expression.set_token(token);
        }
        AssignStatement::from_variable(
            variable.unwrap_or_else(|| assignment.get_variable().clone()),
            expression,
        )
        .into()
    }
}
impl Default for Processor {
    fn default() -> Self {
        Self {
            identifier_tracker: IdentifierTracker::new(),
            remove_comments: RemoveCommentProcessor::default(),
            remove_spaces: RemoveWhitespacesProcessor::default(),
        }
    }
}
impl Deref for Processor {
    type Target = IdentifierTracker;
    fn deref(&self) -> &Self::Target {
        &self.identifier_tracker
    }
}
impl DerefMut for Processor {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.identifier_tracker
    }
}
impl NodeProcessor for Processor {
    fn process_statement(&mut self, statement: &mut Statement) {
        if let Statement::CompoundAssign(assignment) = statement {
            let variable = assignment.get_variable();
            *statement = self
                .replace_with(assignment)
                .unwrap_or_else(|| self.create_new_assignment(assignment, variable.clone()));
        }
    }
}
pub const REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME: &str = "remove_compound_assignment";
#[derive(Debug, Default, PartialEq, Eq)]
pub struct RemoveCompoundAssignment {}
impl FlawlessRule for RemoveCompoundAssignment {
    fn flawless_process(&self, block: &mut Block, _: &Context) {
        let mut processor = Processor::default();
        ScopeVisitor::visit_block(block, &mut processor);
    }
}
impl RuleConfiguration for RemoveCompoundAssignment {
    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
        verify_no_rule_properties(&properties)?;
        Ok(())
    }
    fn get_name(&self) -> &'static str {
        REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME
    }
    fn serialize_to_properties(&self) -> RuleProperties {
        RuleProperties::new()
    }
}
#[cfg(test)]
mod test {
    use super::*;
    use crate::rules::Rule;
    use insta::assert_json_snapshot;
    fn new_rule() -> RemoveCompoundAssignment {
        RemoveCompoundAssignment::default()
    }
    #[test]
    fn serialize_default_rule() {
        let rule: Box<dyn Rule> = Box::new(new_rule());
        assert_json_snapshot!("default_remove_compound_assignment", rule);
    }
    #[test]
    fn configure_with_extra_field_error() {
        let result = json5::from_str::<Box<dyn Rule>>(
            r#"{
            rule: 'remove_compound_assignment',
            prop: "something",
        }"#,
        );
        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
    }
}