use std::rc::Rc;

use crate::Error;
use crate::yamrb::helpers::mrb_define_cmethod;

use crate::yamrb::{value::RObject, vm::VM};

pub(crate) fn initialize_float(vm: &mut VM) {
    let float_class = vm.define_standard_class("Float");
    mrb_define_cmethod(vm, float_class.clone(), "to_i", Box::new(mrb_float_to_i));
    mrb_define_cmethod(vm, float_class.clone(), "to_f", Box::new(mrb_float_to_f));
    mrb_define_cmethod(vm, float_class.clone(), "+", Box::new(mrb_float_add));
    mrb_define_cmethod(vm, float_class.clone(), "-", Box::new(mrb_float_sub));
    mrb_define_cmethod(vm, float_class.clone(), "*", Box::new(mrb_float_mul));
    mrb_define_cmethod(vm, float_class.clone(), "/", Box::new(mrb_float_div));
    mrb_define_cmethod(vm, float_class.clone(), "+@", Box::new(mrb_float_positive));
    mrb_define_cmethod(vm, float_class.clone(), "-@", Box::new(mrb_float_negative));
    mrb_define_cmethod(vm, float_class.clone(), "**", Box::new(mrb_float_power));
    mrb_define_cmethod(vm, float_class.clone(), "abs", Box::new(mrb_float_abs));
    mrb_define_cmethod(
        vm,
        float_class.clone(),
        "finite?",
        Box::new(mrb_float_finite),
    );
    mrb_define_cmethod(
        vm,
        float_class.clone(),
        "infinite?",
        Box::new(mrb_float_infinite),
    );
    mrb_define_cmethod(vm, float_class.clone(), "nan?", Box::new(mrb_float_nan));
    mrb_define_cmethod(
        vm,
        float_class.clone(),
        "inspect",
        Box::new(mrb_float_inspect),
    );
    mrb_define_cmethod(vm, float_class.clone(), "to_s", Box::new(mrb_float_inspect));
    mrb_define_cmethod(vm, float_class.clone(), "clamp", Box::new(mrb_float_clamp));

    let mut const_table = float_class.consts.borrow_mut();
    const_table.insert(
        "INFINITY".to_string(),
        RObject::float(f64::INFINITY).to_refcount_assigned(),
    );
    const_table.insert(
        "NAN".to_string(),
        RObject::float(f64::NAN).to_refcount_assigned(),
    );
    const_table.insert(
        "EPSILON".to_string(),
        RObject::float(f64::EPSILON).to_refcount_assigned(),
    );
}

pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => {
            let int_value = *f as i64;
            Ok(RObject::integer(int_value).to_refcount_assigned())
        }
        _ => Err(Error::RuntimeError(
            "Float#to_i must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_to_f(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(*f).to_refcount_assigned()),
        _ => Err(Error::RuntimeError(
            "Float#to_f must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_finite(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => {
            Ok(RObject::boolean(f.is_finite()).to_refcount_assigned())
        }
        _ => Err(Error::RuntimeError(
            "Float#finite? must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_infinite(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => {
            Ok(RObject::boolean(f.is_infinite()).to_refcount_assigned())
        }
        _ => Err(Error::RuntimeError(
            "Float#infinite? must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_nan(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => {
            Ok(RObject::boolean(f.is_nan()).to_refcount_assigned())
        }
        _ => Err(Error::RuntimeError(
            "Float#nan? must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_inspect(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => {
            let s = format!("{}", f);
            Ok(RObject::string(s).to_refcount_assigned())
        }
        _ => Err(Error::RuntimeError(
            "Float#inspect must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_clamp(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.len() < 2 {
        return Err(Error::ArgumentError(format!(
            "wrong number of arguments (given {}, expected 2)",
            args.len()
        )));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#clamp must be called on a Float".to_string(),
            ));
        }
    };

    // Convert min and max to f64
    let min = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    let max = match &args[1].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    if min > max {
        return Err(Error::ArgumentError(
            "min argument must be smaller than max argument".to_string(),
        ));
    }

    let result = if this_float < min {
        min
    } else if this_float > max {
        max
    } else {
        this_float
    };

    Ok(RObject::float(result).to_refcount_assigned())
}

pub fn mrb_float_add(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.is_empty() {
        return Err(Error::ArgumentError(
            "wrong number of arguments (given 0, expected 1)".to_string(),
        ));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#+ must be called on a Float".to_string(),
            ));
        }
    };

    let other = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    Ok(RObject::float(this_float + other).to_refcount_assigned())
}

pub fn mrb_float_sub(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.is_empty() {
        return Err(Error::ArgumentError(
            "wrong number of arguments (given 0, expected 1)".to_string(),
        ));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#- must be called on a Float".to_string(),
            ));
        }
    };

    let other = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    Ok(RObject::float(this_float - other).to_refcount_assigned())
}

pub fn mrb_float_mul(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.is_empty() {
        return Err(Error::ArgumentError(
            "wrong number of arguments (given 0, expected 1)".to_string(),
        ));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#* must be called on a Float".to_string(),
            ));
        }
    };

    let other = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    Ok(RObject::float(this_float * other).to_refcount_assigned())
}

pub fn mrb_float_div(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.is_empty() {
        return Err(Error::ArgumentError(
            "wrong number of arguments (given 0, expected 1)".to_string(),
        ));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#/ must be called on a Float".to_string(),
            ));
        }
    };

    let other = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    if other == 0.0 {
        return Err(Error::ZeroDivisionError);
    }

    Ok(RObject::float(this_float / other).to_refcount_assigned())
}

pub fn mrb_float_positive(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(*f).to_refcount_assigned()),
        _ => Err(Error::RuntimeError(
            "Float#+@ must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_negative(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(-*f).to_refcount_assigned()),
        _ => Err(Error::RuntimeError(
            "Float#-@ must be called on a Float".to_string(),
        )),
    }
}

pub fn mrb_float_power(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    if args.is_empty() {
        return Err(Error::ArgumentError(
            "wrong number of arguments (given 0, expected 1)".to_string(),
        ));
    }

    let this = vm.getself()?;
    let this_float = match &this.value {
        crate::yamrb::value::RValue::Float(f) => *f,
        _ => {
            return Err(Error::RuntimeError(
                "Float#** must be called on a Float".to_string(),
            ));
        }
    };

    let other = match &args[0].value {
        crate::yamrb::value::RValue::Float(f) => *f,
        crate::yamrb::value::RValue::Integer(i) => *i as f64,
        _ => return Err(Error::TypeMismatch),
    };

    Ok(RObject::float(this_float.powf(other)).to_refcount_assigned())
}

pub fn mrb_float_abs(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(f.abs()).to_refcount_assigned()),
        _ => Err(Error::RuntimeError(
            "Float#abs must be called on a Float".to_string(),
        )),
    }
}
