use std::rc::Rc;

use crate::{
    error::MachineError,
    vm::{
        Value,
        env::Environment,
        value::{StringValue, convert::TryFromValue},
    },
};

fn classify<F: Fn(char) -> bool>(args: &[Value], predicate: F) -> Result<Value, MachineError> {
    let [string] = args else {
        return Err(MachineError::InvalidArgumentCount);
    };
    let string = StringValue::try_from_value(string)?;
    Ok(string.chars().all(predicate).into())
}

pub fn load(env: &Rc<Environment>) {
    env.defun_native(
        "string/trim",
        "Trims leading and trailing whitespace from a string",
        |_, _, args| {
            let [string] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            Ok(StringValue::from(string.trim()).into())
        },
    );
    env.defun_native(
        "string/pop",
        "Removes the last character from the string",
        |_, _, args| {
            let [string] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let mut string = (*StringValue::try_from_value(string)?).to_owned();
            string.pop();
            Ok(StringValue::from(string).into())
        },
    );
    env.defun_native(
        "string/strip-prefix",
        "Removes the prefix from a string. Returns NIL if string does not start with the prefix",
        |_, _, args| {
            let [string, prefix] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            let prefix = StringValue::try_from_value(prefix)?;
            match string.strip_prefix(&*prefix) {
                Some(string) => Ok(StringValue::from(string).into()),
                None => Ok(Value::Nil),
            }
        },
    );
    env.defun_native(
        "string/strip-suffix",
        "Removes the suffix from a string. Returns NIL if string does not end with the suffix",
        |_, _, args| {
            let [string, suffix] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            let suffix = StringValue::try_from_value(suffix)?;
            match string.strip_suffix(&*suffix) {
                Some(string) => Ok(StringValue::from(string).into()),
                None => Ok(Value::Nil),
            }
        },
    );
    env.defun_native(
        "string/split",
        "Splits a string by given separator",
        |_, _, args| {
            let (separator, string) = match args {
                [string] => (&StringValue::from(" "), string),
                [separator, string] => (&StringValue::try_from_value(separator)?, string),
                _ => return Err(MachineError::InvalidArgumentCount),
            };
            let string = StringValue::try_from_value(string)?;
            let mut items = Value::Nil;
            for item in string.rsplit(&**separator) {
                items = Value::String(item.into()).cons(items);
            }
            Ok(items)
        },
    );
    env.defun_native(
        "string/length",
        "Returns the length of the string",
        |_, _, args| {
            let [string] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            Ok(string.len().into())
        },
    );
    env.defun_native(
        "string/to-upper",
        "Converts a string to UPPERCASE",
        |_, _, args| {
            let [string] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            Ok(Value::String(string.to_uppercase().into()))
        },
    );
    env.defun_native(
        "string/to-lower",
        "Converts a string to lowercase",
        |_, _, args| {
            let [string] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let string = StringValue::try_from_value(string)?;
            Ok(Value::String(string.to_lowercase().into()))
        },
    );

    env.defun_native(
        "string/alpha?",
        "Returns #t if all the characters in the string are alphabetic",
        |_, _, args| classify(args, char::is_alphabetic),
    );
    env.defun_native(
        "string/digit?",
        "Returns #t if all the characters in the string are digits (0-9)",
        |_, _, args| classify(args, |ch| ch.is_ascii_digit()),
    );
    env.defun_native(
        "string/ascii-graphic?",
        "Returns #t if all the characters in the string are ASCII graphic (non-control)",
        |_, _, args| classify(args, |ch| ch.is_ascii_graphic()),
    );
    env.defun_native(
        "string/ascii?",
        "Returns #t if all the characters in the string are ASCII",
        |_, _, args| classify(args, |ch| ch.is_ascii()),
    );
}
