use ecow::EcoString;
use num_bigint::BigInt;

use crate::cranelift::mir::ast::{Expression, Function, Module, Var};

pub trait Visit<'mir> {
    fn visit_module(&mut self, module: &'mir mut Module) {
        visit_module(self, module);
    }

    fn visit_function(&mut self, function: &'mir mut Function) {
        visit_function(self, function);
    }

    fn visit_expression(&mut self, expression: &'mir mut Expression) {
        visit_expression(self, expression);
    }

    fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression>) {
        visit_expression_block(self, expressions);
    }

    fn visit_expression_function_ref(
        &mut self,
        module: &mut EcoString,
        name: &mut EcoString,
        arity: &mut usize,
    ) {
        visit_expression_function_ref(self, module, name, arity);
    }

    fn visit_expression_var(&mut self, var: &'mir mut Var) {
        visit_expression_var(self, var);
    }

    fn visit_expression_int(&mut self, value: &'mir mut BigInt) {
        visit_expression_int(self, value);
    }

    fn visit_expression_float(&mut self, value: &'mir mut EcoString) {
        visit_expression_float(self, value);
    }

    fn visit_expression_bool(&mut self, value: &'mir mut bool) {
        visit_expression_bool(self, value);
    }

    fn visit_expression_string(&mut self, value: &'mir mut EcoString) {
        visit_expression_string(self, value);
    }

    fn visit_expression_equals(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_equals(self, lhs, rhs);
    }

    fn visit_expression_not_equals(
        &mut self,
        lhs: &'mir mut Expression,
        rhs: &'mir mut Expression,
    ) {
        visit_expression_not_equals(self, lhs, rhs);
    }

    fn visit_expression_int_gt(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_gt(self, lhs, rhs);
    }

    fn visit_expression_int_gt_eq(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_gt_eq(self, lhs, rhs);
    }

    fn visit_expression_int_lt(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_lt(self, lhs, rhs);
    }

    fn visit_expression_int_lt_eq(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_lt_eq(self, lhs, rhs);
    }

    fn visit_expression_int_add(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_add(self, lhs, rhs);
    }

    fn visit_expression_int_sub(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_sub(self, lhs, rhs);
    }

    fn visit_expression_int_mul(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_mul(self, lhs, rhs);
    }

    fn visit_expression_int_div(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_div(self, lhs, rhs);
    }

    fn visit_expression_int_rem(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_int_rem(self, lhs, rhs);
    }

    fn visit_expression_float_gt(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_gt(self, lhs, rhs);
    }

    fn visit_expression_float_gt_eq(
        &mut self,
        lhs: &'mir mut Expression,
        rhs: &'mir mut Expression,
    ) {
        visit_expression_float_gt_eq(self, lhs, rhs);
    }

    fn visit_expression_float_lt(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_lt(self, lhs, rhs);
    }

    fn visit_expression_float_lt_eq(
        &mut self,
        lhs: &'mir mut Expression,
        rhs: &'mir mut Expression,
    ) {
        visit_expression_float_lt_eq(self, lhs, rhs);
    }

    fn visit_expression_float_add(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_add(self, lhs, rhs);
    }

    fn visit_expression_float_sub(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_sub(self, lhs, rhs);
    }

    fn visit_expression_float_mul(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_mul(self, lhs, rhs);
    }

    fn visit_expression_float_div(&mut self, lhs: &'mir mut Expression, rhs: &'mir mut Expression) {
        visit_expression_float_div(self, lhs, rhs);
    }

    fn visit_expression_string_concat(
        &mut self,
        lhs: &'mir mut Expression,
        rhs: &'mir mut Expression,
    ) {
        visit_expression_string_concat(self, lhs, rhs);
    }

    fn visit_expression_list(
        &mut self,
        items: &'mir mut Vec<Expression>,
        tail: &'mir mut Option<Box<Expression>>,
    ) {
        visit_expression_list(self, items, tail);
    }

    fn visit_expression_struct(
        &mut self,
        tag: &'mir mut Option<u32>,
        items: &'mir mut Vec<Expression>,
    ) {
        visit_expression_struct(self, tag, items);
    }

    fn visit_expression_struct_tag(&mut self, value: &'mir mut Expression) {
        visit_expression_struct_tag(self, value);
    }

    fn visit_expression_struct_access(
        &mut self,
        value: &'mir mut Expression,
        index: &'mir mut u64,
    ) {
        visit_expression_struct_access(self, value, index);
    }

    fn visit_expression_set(&mut self, name: &'mir mut Var, value: &'mir mut Expression) {
        visit_expression_set(self, name, value);
    }

    fn visit_expression_if(
        &mut self,
        cond: &'mir mut Expression,
        then: &'mir mut Expression,
        else_: &'mir mut Expression,
    ) {
        visit_expression_if(self, cond, then, else_);
    }

    fn visit_expression_call(
        &mut self,
        target: &'mir mut Expression,
        args: &'mir mut Vec<Expression>,
    ) {
        visit_expression_call(self, target, args);
    }
}

pub fn visit_module<'mir, V>(v: &mut V, module: &'mir mut Module)
where
    V: Visit<'mir> + ?Sized,
{
    for function in &mut module.functions {
        v.visit_function(function);
    }
}

pub fn visit_function<'mir, V>(v: &mut V, function: &'mir mut Function)
where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(&mut function.body);
}

pub fn visit_expression<'mir, V>(v: &mut V, expression: &'mir mut Expression)
where
    V: Visit<'mir> + ?Sized,
{
    match expression {
        Expression::Block(expressions) => v.visit_expression_block(expressions),
        Expression::FunctionRef {
            module,
            name,
            arity,
        } => v.visit_expression_function_ref(module, name, arity),
        Expression::Var(var) => v.visit_expression_var(var),
        Expression::Int { value } => v.visit_expression_int(value),
        Expression::Float { value } => v.visit_expression_float(value),
        Expression::Bool { value } => v.visit_expression_bool(value),
        Expression::String { value } => v.visit_expression_string(value),
        Expression::Equals { lhs, rhs } => v.visit_expression_equals(lhs, rhs),
        Expression::NotEquals { lhs, rhs } => v.visit_expression_not_equals(lhs, rhs),
        Expression::IntGt { lhs, rhs } => v.visit_expression_int_gt(lhs, rhs),
        Expression::IntGtEq { lhs, rhs } => v.visit_expression_int_gt_eq(lhs, rhs),
        Expression::IntLt { lhs, rhs } => v.visit_expression_int_lt(lhs, rhs),
        Expression::IntLtEq { lhs, rhs } => v.visit_expression_int_lt_eq(lhs, rhs),
        Expression::IntAdd { lhs, rhs } => v.visit_expression_int_add(lhs, rhs),
        Expression::IntSub { lhs, rhs } => v.visit_expression_int_sub(lhs, rhs),
        Expression::IntMul { lhs, rhs } => v.visit_expression_int_mul(lhs, rhs),
        Expression::IntDiv { lhs, rhs } => v.visit_expression_int_div(lhs, rhs),
        Expression::IntRem { lhs, rhs } => v.visit_expression_int_rem(lhs, rhs),
        Expression::FloatGt { lhs, rhs } => v.visit_expression_float_gt(lhs, rhs),
        Expression::FloatGtEq { lhs, rhs } => v.visit_expression_float_gt_eq(lhs, rhs),
        Expression::FloatLt { lhs, rhs } => v.visit_expression_float_lt(lhs, rhs),
        Expression::FloatLtEq { lhs, rhs } => v.visit_expression_float_lt_eq(lhs, rhs),
        Expression::FloatAdd { lhs, rhs } => v.visit_expression_float_add(lhs, rhs),
        Expression::FloatSub { lhs, rhs } => v.visit_expression_float_sub(lhs, rhs),
        Expression::FloatMul { lhs, rhs } => v.visit_expression_float_mul(lhs, rhs),
        Expression::FloatDiv { lhs, rhs } => v.visit_expression_float_div(lhs, rhs),
        Expression::StringConcat { lhs, rhs } => v.visit_expression_string_concat(lhs, rhs),
        Expression::List { items, tail } => v.visit_expression_list(items, tail),
        Expression::Struct { tag, items } => v.visit_expression_struct(tag, items),
        Expression::StructTag { value } => v.visit_expression_struct_tag(value),
        Expression::StructAccess { value, index } => v.visit_expression_struct_access(value, index),
        Expression::Set { name, value } => v.visit_expression_set(name, value),
        Expression::If { cond, then, else_ } => v.visit_expression_if(cond, then, else_),
        Expression::Call { target, args } => v.visit_expression_call(target, args),
    }
}

pub fn visit_expression_block<'mir, V>(v: &mut V, expressions: &'mir mut Vec<Expression>)
where
    V: Visit<'mir> + ?Sized,
{
    for expression in expressions {
        v.visit_expression(expression);
    }
}

pub fn visit_expression_function_ref<'mir, V>(
    _v: &mut V,
    _module: &mut EcoString,
    _name: &mut EcoString,
    _arity: &mut usize,
) where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_var<'mir, V>(_v: &mut V, _var: &mut Var)
where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_int<'mir, V>(_v: &mut V, _value: &mut BigInt)
where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_float<'mir, V>(_v: &mut V, _value: &mut EcoString)
where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_bool<'mir, V>(_v: &mut V, _value: &mut bool)
where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_string<'mir, V>(_v: &mut V, _value: &mut EcoString)
where
    V: Visit<'mir> + ?Sized,
{
}

pub fn visit_expression_equals<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_not_equals<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_gt<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_gt_eq<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_lt<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_lt_eq<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_add<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_sub<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_mul<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_div<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_int_rem<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_gt<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_gt_eq<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_lt<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_lt_eq<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_add<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_sub<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_mul<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_float_div<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_string_concat<'mir, V>(
    v: &mut V,
    lhs: &'mir mut Expression,
    rhs: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(lhs);
    v.visit_expression(rhs);
}

pub fn visit_expression_list<'mir, V>(
    v: &mut V,
    items: &'mir mut Vec<Expression>,
    tail: &'mir mut Option<Box<Expression>>,
) where
    V: Visit<'mir> + ?Sized,
{
    for item in items {
        v.visit_expression(item);
    }
    if let Some(tail) = tail {
        v.visit_expression(tail);
    }
}

pub fn visit_expression_struct<'mir, V>(
    v: &mut V,
    _tag: &'mir mut Option<u32>,
    items: &'mir mut Vec<Expression>,
) where
    V: Visit<'mir> + ?Sized,
{
    for item in items {
        v.visit_expression(item);
    }
}

pub fn visit_expression_struct_tag<'mir, V>(v: &mut V, value: &'mir mut Expression)
where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(value);
}

pub fn visit_expression_struct_access<'mir, V>(
    v: &mut V,
    value: &'mir mut Expression,
    _index: &'mir mut u64,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(value);
}

pub fn visit_expression_set<'mir, V>(v: &mut V, _name: &'mir mut Var, value: &'mir mut Expression)
where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(value);
}

pub fn visit_expression_if<'mir, V>(
    v: &mut V,
    cond: &'mir mut Expression,
    then: &'mir mut Expression,
    else_: &'mir mut Expression,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(cond);
    v.visit_expression(then);
    v.visit_expression(else_);
}

pub fn visit_expression_call<'mir, V>(
    v: &mut V,
    target: &'mir mut Expression,
    args: &'mir mut Vec<Expression>,
) where
    V: Visit<'mir> + ?Sized,
{
    v.visit_expression(target);
    for arg in args {
        v.visit_expression(arg);
    }
}
