use crate::parser::types::{FunctionValue, Value, ComposeMode};
use crate::parser::interpreter::Interpreter;
use std::sync::{Arc, Mutex};

pub fn handle_composition(
    interp: &mut Interpreter,
    f_vec: Vec<Box<FunctionValue>>,
    mode: ComposeMode,
    args: Vec<Value>,
) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!("Composition => expected 1 argument, got {}", args.len()));
    }
    let mut current_val = args.into_iter().next().unwrap();
    match mode {
        ComposeMode::Compose => {
            for fv_box in f_vec.iter().rev() {
                current_val = super::apply::apply_n_ary_function_value(interp, fv_box.clone(), vec![current_val])?;
            }
        }
        ComposeMode::Pipe => {
            for fv_box in f_vec.iter() {
                current_val = super::apply::apply_n_ary_function_value(interp, fv_box.clone(), vec![current_val])?;
            }
        }
    }
    Ok(current_val)
}

pub fn handle_composition_partial(
    _interp: &mut Interpreter,
    mode: ComposeMode,
    mut slots: Vec<Option<Box<FunctionValue>>>,
    args: Vec<Value>,
) -> Result<Value, String> {
    for val in args {
        let mut assigned = false;
        for slot in slots.iter_mut() {
            if slot.is_none() {
                match val {
                    Value::Function(fb) => {
                        *slot = Some(fb);
                        assigned = true;
                        break;
                    }
                    Value::Placeholder => {
                        assigned = true;
                        break;
                    }
                    other => {
                        return Err(format!(
                            "CompositionPartial => slot expects function, got {:?}",
                            other
                        ));
                    }
                }
            }
            if assigned {
                break;
            }
        }
        if !assigned {
            return Err("CompositionPartial => no open slot or unexpected argument".to_string());
        }
    }
    if slots.iter().all(|mb| mb.is_some()) {
        let final_fvec: Vec<Box<FunctionValue>> =
            slots.into_iter().map(|op| op.unwrap()).collect();
        Ok(Value::Function(Box::new(FunctionValue::Composition(final_fvec, mode))))
    } else {
        Ok(Value::Function(Box::new(FunctionValue::CompositionPartial { mode, slots })))
    }
}

pub fn handle_curried_prop(
    _interp: &mut Interpreter,
    k: String,
    args: Vec<Value>,
) -> Result<Value, String> {
    if args.is_empty() {
        return Ok(Value::Function(Box::new(FunctionValue::CurriedProp(k))));
    }
    if args.len() > 1 {
        return Err(format!("CurriedProp => expected 0 or 1 arg, got {}", args.len()));
    }
    let obj_val = args.into_iter().next().unwrap();
    match obj_val {
        Value::Placeholder => Ok(Value::Function(Box::new(FunctionValue::CurriedProp(k)))),
        Value::KeyedArray(map) => {
            if let Some(v) = map.get(&k) {
                Ok(v.clone())
            } else {
                Err(format!("CurriedProp => no key '{}' in keyed array", k))
            }
        }
        other => Err(format!(
            "CurriedProp => final object must be keyed array, got {:?}",
            other
        )),
    }
}

pub fn handle_curried_assoc(
    _interp: &mut Interpreter,
    k: String,
    v: Value,
    args: Vec<Value>,
) -> Result<Value, String> {
    if args.is_empty() {
        return Ok(Value::Function(Box::new(FunctionValue::CurriedAssoc(k, v))));
    }
    if args.len() > 1 {
        return Err(format!("CurriedAssoc => expected 1 argument, got {}", args.len()));
    }
    let obj_val = args.into_iter().next().unwrap();
    match obj_val {
        Value::KeyedArray(map) => {
            let mut new_map = map.clone();
            new_map.insert(k, v);
            Ok(Value::KeyedArray(new_map))
        }
        other => Err(format!(
            "CurriedAssoc => final object must be keyed array, got {:?}",
            other
        )),
    }
}

pub fn handle_rust_closure(
    interp: &mut Interpreter,
    desc: String,
    closure_arc: Arc<Mutex<dyn for<'x> Fn(&'x mut Interpreter, Vec<Value>) -> Result<Value, String> + Send + Sync + 'static>>,
    arity: usize,
    args: Vec<Value>,
) -> Result<Value, String> {
    if arity != 0 && args.len() != arity {
        return Err(format!(
            "RustClosure({}): expected {} arg(s), got {}",
            desc, arity, args.len()
        ));
    }
    let lock = closure_arc
        .lock()
        .map_err(|_| format!("Cannot lock RustClosure for '{}'", desc))?;
    lock(interp, args)
}
