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

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

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

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

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

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

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

    fn visit_expression_var(&mut self, var: &'mir mut Var<T>) {
        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<T>,
        rhs: &'mir mut Expression<T>,
    ) {
        visit_expression_equals(self, lhs, rhs);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

pub fn visit_expression<'mir, T, V>(v: &mut V, expression: &'mir mut Expression<T>)
where
    V: Visit<'mir, T> + ?Sized,
{
    match expression {
        Expression::Block(expressions) => v.visit_expression_block(expressions),
        Expression::FunctionRef {
            module,
            name,
            arity,
            type_,
        } => v.visit_expression_function_ref(module, name, arity, type_),
        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, type_ } => v.visit_expression_list(items, tail, type_),
        Expression::Struct { tag, items, type_ } => v.visit_expression_struct(tag, items, type_),
        Expression::StructTag { value } => v.visit_expression_struct_tag(value),
        Expression::StructAccess {
            value,
            index,
            type_,
        } => v.visit_expression_struct_access(value, index, type_),
        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,
            type_,
        } => v.visit_expression_call(target, args, type_),
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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