use log::trace;
use std::io::{self, Write};
use std::rc::Rc;
use crate::convert::FromMrb;
use crate::def::{ClassLike, Define};
use crate::eval::{EvalContext, MrbEval};
use crate::extn::core::error::{ArgumentError, LoadError, RubyException, RuntimeError};
use crate::sys;
use crate::value::types::Ruby;
use crate::value::{Value, ValueLike};
use crate::{Mrb, MrbError};
mod args;
pub mod require;
pub fn patch(interp: &Mrb) -> Result<(), MrbError> {
    let warning = interp.borrow_mut().def_module::<Warning>("Warning", None);
    warning
        .borrow_mut()
        .add_method("warn", Warning::warn, sys::mrb_args_req(1));
    warning
        .borrow_mut()
        .add_self_method("warn", Warning::warn, sys::mrb_args_req(1));
    warning.borrow().define(interp).map_err(|_| MrbError::New)?;
    let kernel = interp.borrow_mut().def_module::<Kernel>("Kernel", None);
    kernel
        .borrow_mut()
        .add_method("require", Kernel::require, sys::mrb_args_rest());
    kernel.borrow_mut().add_self_method(
        "require_relative",
        Kernel::require_relative,
        sys::mrb_args_rest(),
    );
    kernel
        .borrow_mut()
        .add_method("print", Kernel::print, sys::mrb_args_rest());
    kernel
        .borrow_mut()
        .add_method("puts", Kernel::puts, sys::mrb_args_rest());
    kernel
        .borrow_mut()
        .add_method("warn", Kernel::warn, sys::mrb_args_rest());
    kernel.borrow().define(interp).map_err(|_| MrbError::New)?;
    interp.eval(include_str!("kernel.rb"))?;
    trace!("Patched Kernel#require onto interpreter");
    Ok(())
}
pub struct Warning;
impl Warning {
    unsafe extern "C" fn warn(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let stderr = sys::mrb_gv_get(mrb, interp.borrow_mut().sym_intern("$stderr"));
        if !sys::mrb_sys_value_is_nil(stderr) {
            let args = args::Rest::extract(&interp);
            let stderr = Value::new(&interp, stderr);
            
            let _ = stderr
                .funcall::<Value, _, _>("print", args.map(|args| args.rest).unwrap_or_default());
        }
        sys::mrb_sys_nil_value()
    }
}
pub struct Kernel;
impl Kernel {
    unsafe extern "C" fn require(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let args = require::Args::extract(&interp);
        let result = args.and_then(|args| require::method::require(&interp, args));
        match result {
            Ok(req) => {
                let result = if let Some(req) = req.rust {
                    req(Rc::clone(&interp))
                } else {
                    Ok(())
                };
                if result.is_ok() {
                    if let Some(contents) = req.ruby {
                        interp.unchecked_eval_with_context(contents, EvalContext::new(req.file));
                    }
                    Value::from_mrb(&interp, true).inner()
                } else {
                    LoadError::raisef(interp, "cannot load such file -- %S", vec![req.file])
                }
            }
            Err(require::Error::AlreadyRequired) => Value::from_mrb(&interp, false).inner(),
            Err(require::Error::CannotLoad(file)) => {
                LoadError::raisef(interp, "cannot load such file -- %S", vec![file])
            }
            Err(require::Error::Fatal) => RuntimeError::raise(interp, "fatal Kernel#require error"),
            Err(require::Error::NoImplicitConversionToString) => {
                ArgumentError::raise(interp, "No implicit conversion to String")
            }
        }
    }
    unsafe extern "C" fn require_relative(
        mrb: *mut sys::mrb_state,
        _slf: sys::mrb_value,
    ) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let args = require::Args::extract(&interp);
        let result = args.and_then(|args| require::method::require_relative(&interp, args));
        match result {
            Ok(req) => {
                let result = if let Some(req) = req.rust {
                    req(Rc::clone(&interp))
                } else {
                    Ok(())
                };
                if result.is_ok() {
                    if let Some(contents) = req.ruby {
                        interp.unchecked_eval_with_context(contents, EvalContext::new(req.file));
                    }
                    Value::from_mrb(&interp, true).inner()
                } else {
                    LoadError::raisef(interp, "cannot load such file -- %S", vec![req.file])
                }
            }
            Err(require::Error::AlreadyRequired) => Value::from_mrb(&interp, false).inner(),
            Err(require::Error::CannotLoad(file)) => {
                LoadError::raisef(interp, "cannot load such file -- %S", vec![file])
            }
            Err(require::Error::Fatal) => RuntimeError::raise(interp, "fatal Kernel#require error"),
            Err(require::Error::NoImplicitConversionToString) => {
                ArgumentError::raise(interp, "No implicit conversion to String")
            }
        }
    }
    unsafe extern "C" fn print(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let args = args::Rest::extract(&interp);
        for value in args.map(|args| args.rest).unwrap_or_default() {
            print!("{}", value.to_s());
        }
        let _ = io::stdout().flush();
        sys::mrb_sys_nil_value()
    }
    unsafe extern "C" fn puts(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
        fn do_puts(value: Value) {
            if value.ruby_type() == Ruby::Array {
                if let Ok(array) = value.try_into::<Vec<Value>>() {
                    for value in array {
                        do_puts(value);
                    }
                }
            } else {
                println!("{}", value.to_s());
            }
        }
        let interp = unwrap_interpreter!(mrb);
        let rest = args::Rest::extract(&interp)
            .map(|args| args.rest)
            .unwrap_or_default();
        if rest.is_empty() {
            println!();
        }
        for value in rest {
            do_puts(value);
        }
        sys::mrb_sys_nil_value()
    }
    unsafe extern "C" fn warn(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let args = args::Rest::extract(&interp);
        for value in args.map(|args| args.rest).unwrap_or_default() {
            let mut string = value.to_s();
            if !string.ends_with('\n') {
                string = format!("{}\n", string);
            }
            Warning::warn(mrb, Value::from_mrb(&interp, string).inner());
        }
        sys::mrb_sys_nil_value()
    }
}
#[cfg(test)]
mod tests {
    use crate::convert::TryFromMrb;
    use crate::eval::MrbEval;
    use crate::file::MrbFile;
    use crate::load::MrbLoadSources;
    use crate::{Mrb, MrbError};
    
    
    
    
    
    
    
    #[test]
    fn require() {
        struct File;
        impl MrbFile for File {
            fn require(interp: Mrb) -> Result<(), MrbError> {
                interp.eval("@i = 255")?;
                Ok(())
            }
        }
        let interp = crate::interpreter().expect("mrb init");
        interp
            .def_file_for_type::<_, File>("file.rb")
            .expect("def file");
        let result = interp.eval("require 'file'").expect("eval");
        let require_result = unsafe { bool::try_from_mrb(&interp, result) };
        assert_eq!(require_result, Ok(true));
        let result = interp.eval("@i").expect("eval");
        let i_result = unsafe { i64::try_from_mrb(&interp, result) };
        assert_eq!(i_result, Ok(255));
        let result = interp.eval("@i = 1000; require 'file'").expect("eval");
        let second_require_result = unsafe { bool::try_from_mrb(&interp, result) };
        assert_eq!(second_require_result, Ok(false));
        let result = interp.eval("@i").expect("eval");
        let second_i_result = unsafe { i64::try_from_mrb(&interp, result) };
        assert_eq!(second_i_result, Ok(1000));
        let result = interp.eval("require 'non-existent-source'").map(|_| ());
        let expected = r#"
(eval):1: cannot load such file -- non-existent-source (LoadError)
(eval):1
            "#;
        assert_eq!(result, Err(MrbError::Exec(expected.trim().to_owned())));
    }
    #[test]
    fn require_absolute_path() {
        let interp = crate::interpreter().expect("mrb init");
        interp
            .def_rb_source_file("/foo/bar/source.rb", "# a source file")
            .expect("def file");
        let result = interp.eval("require '/foo/bar/source.rb'").expect("value");
        assert!(unsafe { bool::try_from_mrb(&interp, result).expect("convert") });
        let result = interp.eval("require '/foo/bar/source.rb'").expect("value");
        assert!(!unsafe { bool::try_from_mrb(&interp, result).expect("convert") });
    }
    #[test]
    fn require_relative_with_dotted_path() {
        let interp = crate::interpreter().expect("mrb init");
        interp
            .def_rb_source_file("/foo/bar/source.rb", "require_relative '../bar.rb'")
            .expect("def file");
        interp
            .def_rb_source_file("/foo/bar.rb", "# a source file")
            .expect("def file");
        let result = interp.eval("require '/foo/bar/source.rb'").expect("value");
        assert!(unsafe { bool::try_from_mrb(&interp, result).expect("convert") });
    }
    #[test]
    fn require_directory() {
        let interp = crate::interpreter().expect("mrb init");
        let result = interp.eval("require '/src'").map(|_| ());
        let expected = r#"
(eval):1: cannot load such file -- /src (LoadError)
(eval):1
        "#;
        assert_eq!(result, Err(MrbError::Exec(expected.trim().to_owned())));
    }
    #[test]
    fn require_path_defined_as_source_then_mrbfile() {
        struct Foo;
        impl MrbFile for Foo {
            fn require(interp: Mrb) -> Result<(), MrbError> {
                interp.eval("module Foo; RUST = 7; end")?;
                Ok(())
            }
        }
        let interp = crate::interpreter().expect("mrb init");
        interp
            .def_rb_source_file("foo.rb", "module Foo; RUBY = 3; end")
            .expect("def");
        interp.def_file_for_type::<_, Foo>("foo.rb").expect("def");
        let result = interp.eval("require 'foo'").expect("eval");
        let result = unsafe { bool::try_from_mrb(&interp, result).expect("convert") };
        assert!(result, "successfully required foo.rb");
        let result = interp.eval("Foo::RUBY + Foo::RUST").expect("eval");
        let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
        assert_eq!(
            result, 10,
            "defined Ruby and Rust sources from single require"
        );
    }
    #[test]
    fn require_path_defined_as_mrbfile_then_source() {
        struct Foo;
        impl MrbFile for Foo {
            fn require(interp: Mrb) -> Result<(), MrbError> {
                interp.eval("module Foo; RUST = 7; end")?;
                Ok(())
            }
        }
        let interp = crate::interpreter().expect("mrb init");
        interp.def_file_for_type::<_, Foo>("foo.rb").expect("def");
        interp
            .def_rb_source_file("foo.rb", "module Foo; RUBY = 3; end")
            .expect("def");
        let result = interp.eval("require 'foo'").expect("eval");
        let result = unsafe { bool::try_from_mrb(&interp, result).expect("convert") };
        assert!(result, "successfully required foo.rb");
        let result = interp.eval("Foo::RUBY + Foo::RUST").expect("eval");
        let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
        assert_eq!(
            result, 10,
            "defined Ruby and Rust sources from single require"
        );
    }
    #[test]
    #[allow(clippy::shadow_unrelated)]
    fn kernel_throw_catch() {
        
        let interp = crate::interpreter().expect("mrb init");
        let result = interp
            .eval("catch(1) { 123 }")
            .unwrap()
            .try_into::<i64>()
            .unwrap();
        assert_eq!(result, 123);
        let result = interp
            .eval("catch(1) { throw(1, 456) }")
            .unwrap()
            .try_into::<i64>()
            .unwrap();
        assert_eq!(result, 456);
        let result = interp
            .eval("catch(1) { throw(1) }")
            .unwrap()
            .try_into::<Option<i64>>()
            .unwrap();
        assert_eq!(result, None);
        let result = interp
            .eval("catch(1) {|x| x + 2 }")
            .unwrap()
            .try_into::<i64>()
            .unwrap();
        assert_eq!(result, 3);
        let result = interp
            .eval(
                r#"
catch do |obj_A|
  catch do |obj_B|
    throw(obj_B, 123)
    # puts "This puts is not reached"
  end
  # puts "This puts is displayed"
  456
end
            "#,
            )
            .unwrap()
            .try_into::<i64>()
            .unwrap();
        assert_eq!(result, 456);
        let result = interp
            .eval(
                r#"
catch do |obj_A|
  catch do |obj_B|
    throw(obj_A, 123)
    # puts "This puts is still not reached"
  end
  # puts "Now this puts is also not reached"
  456
end
            "#,
            )
            .unwrap()
            .try_into::<i64>()
            .unwrap();
        assert_eq!(result, 123);
    }
}