use std::rc::Rc;

use crate::{
    error::MachineError,
    read::{self, InteractiveReader},
    vm::{
        Value,
        env::Environment,
        value::{IdentifierValue, NativeObject, StringValue, convert::TryFromValue},
    },
};

pub fn load(env: &Rc<Environment>) {
    env.defun_native(
        "get",
        "Reads the value of a global variable",
        |_, env, args| {
            let (symbol, default) = match args {
                [symbol] => (symbol, &Value::Nil),
                [symbol, default] => (symbol, default),
                _ => todo!(),
            };
            let symbol = IdentifierValue::try_from_value(symbol)?;
            match env.global_value(&symbol) {
                Some(value) => Ok(value.clone()),
                None => Ok(default.clone()),
            }
        },
    );
    env.defun_native(
        "set",
        "Sets the value of a global variable",
        |_, env, args| {
            let [symbol, value] = args else { todo!() };
            let symbol = match symbol {
                Value::Identifier(identifier) => identifier,
                Value::Quote(quoted) if let Value::Identifier(identifier) = quoted.as_ref() => {
                    identifier
                }
                _ => todo!(),
            };
            env.set_global_value(symbol.clone(), value.clone())?;
            Ok(Value::Nil)
        },
    );

    env.defun_native("eval", "Evaluates the given expression", |vm, env, args| {
        let (env, value) = match args {
            [value] => (env.clone(), value),
            [env, value] => (env.as_native()?, value),
            _ => return Err(MachineError::InvalidArgumentCount),
        };
        let outcome =
            match vm.evaluate_value(Default::default(), Some("eval".into()), &env, value.clone()) {
                Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
                Err(error) => Value::list_or_nil([
                    Value::Identifier("err".into()),
                    Value::String(format!("{error}").into()),
                ]),
            };
        Ok(outcome)
    });
    env.defun_native(
        "read-eval",
        "Evaluates the given expression, passed as a string",
        |vm, env, args| {
            let (env, input) = match args {
                [input] => (env.clone(), input),
                [env, input] => (env.as_native()?, input),
                _ => return Err(MachineError::InvalidArgumentCount),
            };
            let input = StringValue::try_from_value(input)?;
            let mut input = String::from(&*input);
            input.push('\n');
            let outcome =
                match vm.evaluate_str(Default::default(), Some("eval".into()), &env, &input) {
                    Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
                    Err(error) => Value::list_or_nil([
                        Value::Identifier("err".into()),
                        Value::String(format!("{error}").into()),
                    ]),
                };

            Ok(outcome)
        },
    );
    env.defun_native("env/create", "Create a new environment", |_, env, args| {
        let parent = match args {
            [] => Some(env.clone()),
            [env] if env.is_nil() => None,
            [env] => Some(env.as_native()?),
            _ => return Err(MachineError::InvalidArgumentCount),
        };
        let environment: Rc<dyn NativeObject> = Rc::new(Environment::new(parent));
        Ok(Value::NativeValue(environment.into()))
    });
    env.defun_native(
        "env/load-prelude",
        "Adds prelude definitions to the environment",
        |_, _, args| {
            let [env] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let env = env.as_native::<Environment>()?;
            super::load(&env);
            Ok(Value::Nil)
        },
    );
    env.defun_native(
        "read",
        "Reads a form from standard input",
        |vm, env, _args| {
            let mut reader = InteractiveReader::new("> ", ">> ");
            let value = read::read(&mut reader, vm, env);
            let value = match value {
                Ok(Some(value)) => {
                    Value::list_or_nil([Value::Identifier("ok".into()), value]).quote()
                }
                Ok(None) => Value::Nil,
                Err(error) => Value::list_or_nil([
                    Value::Identifier("err".into()),
                    Value::String(format!("{error}").into()),
                ])
                .quote(),
            };
            Ok(value)
        },
    );
    env.defun_native(
        "unquote",
        "For quoted values, strips the quote and returns the inner value",
        |_, _, args| {
            let [x] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let unquoted = x.unquote()?;
            Ok(unquoted)
        },
    );
    env.defun_native(
        "print",
        "Prints the arguments to the standard output",
        |_, _, args| {
            for (i, arg) in args.iter().enumerate() {
                if i != 0 {
                    print!(" ");
                }
                if let Value::String(string) = arg {
                    print!("{}", &**string);
                } else {
                    print!("{arg}");
                }
            }
            println!();
            Ok(Value::Nil)
        },
    );
    env.defun_native(
        "eprint",
        "Prints the arguments to the standard error output",
        |_, _, args| {
            for (i, arg) in args.iter().enumerate() {
                if i != 0 {
                    eprint!(" ");
                }
                if let Value::String(string) = arg {
                    eprint!("{}", &**string);
                } else {
                    eprint!("{arg}");
                }
            }
            eprintln!();
            Ok(Value::Nil)
        },
    );
    env.defun_native("error", "Raises an error condition", |_, _, args| {
        let mut message = String::new();
        for (i, arg) in args.iter().enumerate() {
            if i != 0 {
                message.push(' ');
            }
            if let Value::String(string) = arg {
                message.push_str(&format!("{}", *string));
            } else {
                message.push_str(&format!("{arg}"));
            }
        }
        Err(MachineError::Raised(message))
    });
}
