use std::rc::Rc;

use crate::{
    Error,
    yamrb::{
        helpers::{mrb_call_block, mrb_define_cmethod},
        prelude::module::mrb_include_module,
        value::{RObject, RValue},
        vm::VM,
    },
};

pub(crate) fn initialize_range(vm: &mut VM) {
    let range_class = vm.define_standard_class("Range");

    mrb_define_cmethod(
        vm,
        range_class.clone(),
        "include?",
        Box::new(mrb_range_is_include),
    );
    mrb_define_cmethod(vm, range_class.clone(), "each", Box::new(mrb_range_each));

    let enumerable_module = vm.get_module_by_name("Enumerable");
    mrb_include_module(&range_class, enumerable_module).expect("failed to include Enumerable");
}

pub fn mrb_range_is_include(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    match &this.value {
        RValue::Range(start, end, exclusive) => {
            let obj = args[0].clone();
            match (&start.value, &end.value, &obj.value) {
                (RValue::Integer(start), RValue::Integer(end), RValue::Integer(obj)) => {
                    if *exclusive {
                        Ok(Rc::new(RObject::boolean(*start <= *obj && *obj < *end)))
                    } else {
                        Ok(Rc::new(RObject::boolean(*start <= *obj && *obj <= *end)))
                    }
                }
                (RValue::Integer(start), RValue::Integer(end), RValue::Float(obj)) => {
                    let obj = *obj as i64;
                    if *exclusive {
                        Ok(Rc::new(RObject::boolean(*start <= obj && obj < *end)))
                    } else {
                        Ok(Rc::new(RObject::boolean(*start <= obj && obj <= *end)))
                    }
                }
                _ => Ok(Rc::new(RObject::boolean(false))),
            }
        }
        _ => Err(Error::RuntimeError(
            "Range#include? must be called on a Range".to_string(),
        )),
    }
}

pub fn mrb_range_each(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let this = vm.getself()?;
    let block = &args[0];
    match &this.value {
        RValue::Range(start, end, exclusive) => match (&start.value, &end.value) {
            (RValue::Integer(start), RValue::Integer(end)) => {
                let start = *start;
                let mut end = *end;
                if *exclusive {
                    end -= 1;
                }
                for i in start..=end {
                    let args = vec![Rc::new(RObject::integer(i))];
                    mrb_call_block(vm, block.clone(), None, &args, 0)?;
                }
            }
            _ => {
                return Err(Error::RuntimeError(
                    "Range#each must be called on a integer Range with block (for now)".to_string(),
                ));
            }
        },
        _ => {
            return Err(Error::RuntimeError(
                "Range#each must be called on a Range".to_string(),
            ));
        }
    }
    Ok(this.clone())
}
