use std::ops::Index;
use std::{fmt, rc::Rc};
use itertools::Itertools;
use rustc_hash::FxHashSet;
use crate::{block_id::BlockId, item_id::ItemId, type_context::TypeContext};
use super::{
    json_format::{FormattedText, JsonFormatComponent},
    minecraft_utils::{ScoreboardComparison, ScoreboardOperation, ScoreboardValue},
    ObjectRef,
};
#[derive(Debug)]
pub struct Function {
    pub id: BlockId,
    pub(crate) nodes: Vec<Node>,
    pub(crate) return_value: ObjectRef,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FastStore {
    pub id: ItemId,
    pub value: ScoreboardValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FastStoreFromResult {
    pub id: ItemId,
    pub command: Box<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BinaryOperation {
    pub id: ItemId,
    pub lhs: ScoreboardValue,
    pub rhs: ScoreboardValue,
    pub operation: ScoreboardOperation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Call {
    pub id: BlockId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Condition {
    Compare {
        lhs: ScoreboardValue,
        rhs: ScoreboardValue,
        comparison: ScoreboardComparison,
    },
    And(Vec<Condition>),
    Or(Vec<Condition>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
    PosBranch,
    NegBranch,
    Both,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Branch {
    pub condition: Condition,
    pub pos_branch: Box<Node>,
    pub neg_branch: Box<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecuteRawComponent {
    String(Rc<str>),
    ScoreboardValue(ScoreboardValue),
    Node(Node),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecuteRaw(pub Vec<ExecuteRawComponent>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteMessage {
    pub target: WriteTarget,
    pub message: FormattedText,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteTarget {
    Chat,
    Actionbar,
    Title,
    Subtitle,
}
impl fmt::Display for WriteTarget {
    #[allow(clippy::use_debug)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
    FastStore(FastStore),
    FastStoreFromResult(FastStoreFromResult),
    BinaryOperation(BinaryOperation),
    Call(Call),
    Condition(Condition),
    Branch(Branch),
    Execute(ExecuteRaw),
    Write(WriteMessage),
    Nop,
}
pub enum VariableAccess<'a> {
    Read(&'a ScoreboardValue),
    Write(&'a ItemId, Option<i32>),
    ReadWrite(&'a ScoreboardValue),
}
pub enum VariableAccessMut<'a> {
    Read(&'a mut ScoreboardValue),
    Write(&'a mut ItemId, Option<i32>),
    ReadWrite(&'a mut ScoreboardValue),
}
macro_rules! make_access_visitor {
    (node, $self:ident, $visitor:ident, $fn_name:ident, $VariableAccess:ident) => {
        match $self {
            Node::BinaryOperation(BinaryOperation {
                id,
                lhs,
                rhs,
                operation: _,
            }) => {
                $visitor($VariableAccess::Write(id, None));
                $visitor($VariableAccess::Read(lhs));
                $visitor($VariableAccess::Read(rhs));
            }
            Node::Branch(Branch {
                condition,
                pos_branch,
                neg_branch,
            }) => {
                pos_branch.$fn_name($visitor);
                neg_branch.$fn_name($visitor);
                condition.$fn_name($visitor);
            }
            Node::Call(Call { id: _ }) => {}
            Node::Condition(condition) => {
                condition.$fn_name($visitor);
            }
            Node::Execute(ExecuteRaw(components)) => {
                for component in components {
                    match component {
                        ExecuteRawComponent::ScoreboardValue(value) => {
                            $visitor($VariableAccess::ReadWrite(value));
                        }
                        ExecuteRawComponent::Node(node) => node.$fn_name($visitor),
                        ExecuteRawComponent::String(_) => {}
                    }
                }
            }
            Node::FastStore(FastStore { id, value }) => {
                let static_val = if let ScoreboardValue::Static(value) = value {
                    Some(*value)
                } else {
                    None
                };
                $visitor($VariableAccess::Write(id, static_val));
                $visitor($VariableAccess::Read(value));
            }
            Node::FastStoreFromResult(FastStoreFromResult { id, command }) => {
                $visitor($VariableAccess::Write(id, None));
                command.$fn_name($visitor);
            }
            Node::Nop => {}
            Node::Write(WriteMessage {
                target: _,
                message: FormattedText { components },
            }) => {
                for component in components {
                    match component {
                        JsonFormatComponent::Score(value) => {
                            $visitor($VariableAccess::Read(value));
                        }
                        JsonFormatComponent::RawText(_) | JsonFormatComponent::Function(_) => {}
                    }
                }
            }
        }
    };
    (condition, $self:ident, $visitor:ident, $fn_name:ident, $VariableAccess:ident) => {
        match $self {
            Condition::Compare {
                lhs,
                rhs,
                comparison: _,
            } => {
                $visitor($VariableAccess::Read(lhs));
                $visitor($VariableAccess::Read(rhs));
            }
            Condition::And(conditions) => {
                for condition in conditions {
                    condition.$fn_name($visitor);
                }
            }
            Condition::Or(conditions) => {
                for condition in conditions {
                    condition.$fn_name($visitor);
                }
            }
        }
    };
}
impl Function {
    pub fn nodes(&self) -> &[Node] {
        self.nodes.as_slice()
    }
    pub fn calls_function(&self, function_id: &BlockId) -> bool {
        for node in self.nodes() {
            let mut contains_call = false;
            node.scan(&mut |inner_node| match inner_node {
                Node::Call(Call { id }) if id == function_id => {
                    contains_call = true;
                }
                _ => {}
            });
            if contains_call {
                return true;
            }
        }
        false
    }
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }
    #[doc(hidden)]
    pub fn _dummy(id: BlockId, nodes: Vec<Node>) -> Self {
        Function {
            id,
            nodes,
            return_value: TypeContext::default().null(),
        }
    }
}
impl Condition {
    pub fn not(&self) -> Condition {
        match self {
            Condition::Compare {
                comparison,
                lhs,
                rhs,
            } => Condition::Compare {
                comparison: comparison.invert(),
                lhs: *lhs,
                rhs: *rhs,
            },
            Condition::And(conditions) => {
                Condition::Or(conditions.iter().map(Condition::not).collect())
            }
            Condition::Or(conditions) => {
                Condition::And(conditions.iter().map(Condition::not).collect())
            }
        }
    }
    pub fn is_effect_free(&self) -> bool {
        match self {
            Condition::And(nested) | Condition::Or(nested) => {
                nested.iter().all(Condition::is_effect_free)
            }
            Condition::Compare { .. } => true,
        }
    }
    pub fn is_simple(&self) -> bool {
        matches!(self, Condition::Compare { .. })
    }
    pub fn variable_reads(&self) -> FxHashSet<ItemId> {
        let mut reads = FxHashSet::default();
        self.variable_accesses(&mut |access| {
            if let VariableAccess::Read(var) | VariableAccess::ReadWrite(var) = access {
                if let ScoreboardValue::Scoreboard(item_id) = var {
                    reads.insert(*item_id);
                }
            }
        });
        reads
    }
    pub fn variable_accesses<F: FnMut(VariableAccess)>(&self, visitor: &mut F) {
        make_access_visitor!(condition, self, visitor, variable_accesses, VariableAccess);
    }
    pub fn variable_accesses_mut<F: FnMut(VariableAccessMut)>(&mut self, visitor: &mut F) {
        make_access_visitor!(
            condition,
            self,
            visitor,
            variable_accesses_mut,
            VariableAccessMut
        );
    }
}
impl fmt::Display for Condition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Condition::Compare {
                lhs,
                rhs,
                comparison,
            } => write!(f, "{lhs} {} {rhs}", comparison.str_value()),
            Condition::And(parts) => {
                let nested = parts.iter().map(Condition::to_string).join(" and ");
                write!(f, "({nested})")
            }
            Condition::Or(parts) => {
                let nested = parts.iter().map(Condition::to_string).join(" or ");
                write!(f, "({nested})")
            }
        }
    }
}
impl Node {
    pub fn scan<F>(&self, func: &mut F)
    where
        F: FnMut(&Node),
    {
        func(self);
        match self {
            Node::Branch(branch) => {
                branch.pos_branch.scan(func);
                branch.neg_branch.scan(func);
            }
            Node::FastStoreFromResult(FastStoreFromResult { command, .. }) => command.scan(func),
            Node::Execute(ExecuteRaw(components)) => {
                for component in components {
                    match component {
                        ExecuteRawComponent::Node(node) => {
                            node.scan(func);
                        }
                        ExecuteRawComponent::ScoreboardValue(_)
                        | ExecuteRawComponent::String(_) => {}
                    }
                }
            }
            _ => {}
        }
    }
    pub fn scan_mut<F>(&mut self, func: &mut F)
    where
        F: FnMut(&mut Node),
    {
        func(self);
        match self {
            Node::Branch(branch) => {
                branch.pos_branch.scan_mut(func);
                branch.neg_branch.scan_mut(func);
            }
            Node::FastStoreFromResult(FastStoreFromResult { command, .. }) => {
                command.scan_mut(func);
            }
            Node::Execute(ExecuteRaw(components)) => {
                for component in components {
                    match component {
                        ExecuteRawComponent::Node(node) => {
                            node.scan_mut(func);
                        }
                        ExecuteRawComponent::ScoreboardValue(_)
                        | ExecuteRawComponent::String(_) => {}
                    }
                }
            }
            _ => {}
        }
    }
    pub fn is_effect_free<'a, 'b: 'a, T>(&'a self, function_map: &'b T) -> bool
    where
        T: Index<&'a BlockId, Output = Function>,
    {
        match self {
            Node::Branch(branch) => {
                branch.condition.is_effect_free()
                    && branch.pos_branch.is_effect_free(function_map)
                    && branch.neg_branch.is_effect_free(function_map)
            }
            Node::Call(Call { id }) => function_map[id]
                .nodes
                .iter()
                .all(|node| node.is_effect_free(function_map)),
            Node::Condition(condition) => condition.is_effect_free(),
            Node::BinaryOperation(_)
            | Node::Execute(_)
            | Node::FastStore(_)
            | Node::FastStoreFromResult(_)
            | Node::Write(_) => false,
            Node::Nop => true,
        }
    }
    pub fn has_call(&self) -> bool {
        let mut has_call = false;
        self.scan(&mut |node| {
            if matches!(node, Node::Call(_)) {
                has_call = true;
            }
        });
        has_call
    }
    pub fn variable_accesses<F: FnMut(VariableAccess)>(&self, visitor: &mut F) {
        make_access_visitor!(node, self, visitor, variable_accesses, VariableAccess);
    }
    pub fn variable_accesses_mut<F: FnMut(VariableAccessMut)>(&mut self, visitor: &mut F) {
        make_access_visitor!(
            node,
            self,
            visitor,
            variable_accesses_mut,
            VariableAccessMut
        );
    }
    pub fn writes_to(&self, item_id: ItemId) -> bool {
        let mut found_write = false;
        self.variable_accesses(&mut |access| match access {
            VariableAccess::Write(id, _)
            | VariableAccess::ReadWrite(ScoreboardValue::Scoreboard(id))
                if *id == item_id =>
            {
                found_write = true;
            }
            _ => {}
        });
        found_write
    }
    pub fn reads_from(&self, item_id: ItemId) -> bool {
        let mut has_read = false;
        self.variable_accesses(&mut |access| match access {
            VariableAccess::Read(ScoreboardValue::Scoreboard(id))
            | VariableAccess::ReadWrite(ScoreboardValue::Scoreboard(id))
                if *id == item_id =>
            {
                has_read = true;
            }
            _ => {}
        });
        has_read
    }
}
impl fmt::Display for Function {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let id = self.id.0;
        let content = self.nodes.iter().join("\n");
        write!(f, "Function {id}:\n{content}\n")
    }
}
impl fmt::Display for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Node::BinaryOperation(binop) => write!(
                f,
                "{} = {} {} {}",
                binop.id,
                binop.lhs,
                binop.operation.str_value(),
                binop.rhs
            ),
            Node::Branch(branch) => write!(
                f,
                "if ({}):\n\t{}\n\t{}",
                branch.condition, branch.pos_branch, branch.neg_branch
            ),
            Node::Call(call) => write!(f, "call {}", call.id.0),
            Node::Condition(condition) => condition.fmt(f),
            Node::Execute(ExecuteRaw(components)) => {
                for component in components {
                    match component {
                        ExecuteRawComponent::ScoreboardValue(
                            value @ ScoreboardValue::Scoreboard { .. },
                        ) => write!(f, "${value}")?,
                        ExecuteRawComponent::ScoreboardValue(ScoreboardValue::Static(value)) => {
                            write!(f, "{value}")?;
                        }
                        ExecuteRawComponent::Node(node) => write!(f, "${{{node}}}")?,
                        ExecuteRawComponent::String(string) => f.write_str(string)?,
                    }
                }
                Ok(())
            }
            Node::FastStore(FastStore { id, value }) => write!(f, "{id} = {value}"),
            Node::FastStoreFromResult(FastStoreFromResult { id, command }) => {
                write!(f, "{id} = {command}")
            }
            Node::Write(WriteMessage { target, message }) => {
                write!(f, "write {target}: {message}")
            }
            Node::Nop => f.write_str("Nop"),
        }
    }
}