use std::mem;

use crate::{
    env::EuEnv,
    fns::EuDef,
    types::EuType,
};

pub const STACK: EuDef = |env| {
    env.push(EuType::Vec(env.stack.clone()));
    Ok(())
};

pub const DUP: EuDef = |env| {
    env.push(env.last()?.clone());
    Ok(())
};

pub const DUPD: EuDef = |env| {
    env.check_nargs(2)?;
    env.stack.insert(
        env.iflip(1).unwrap(),
        env.stack[env.iflip(1).unwrap()].clone(),
    );
    Ok(())
};

pub const OVER: EuDef = |env| {
    env.check_nargs(2)?;
    env.push(env.stack[env.iflip(1).unwrap()].clone());
    Ok(())
};

pub const DDUP: EuDef = |env| {
    env.check_nargs(2)?;
    env.push(env.stack[env.iflip(1).unwrap()].clone());
    env.push(env.stack[env.iflip(1).unwrap()].clone());
    Ok(())
};

pub const EDUP: EuDef = |env| {
    env.check_nargs(3)?;
    for _ in 0..3 {
        env.push(env.stack[env.iflip(2).unwrap()].clone());
    }
    Ok(())
};

pub const PICK: EuDef = |env| {
    let a0 = env.arg("a0")?.try_isize()?;
    env.push(env.stack[env.iflip(a0)?].clone());
    Ok(())
};

pub const POP: EuDef = |env| {
    env.pop()?;
    Ok(())
};

pub const CLR: EuDef = |env| {
    env.stack.clear();
    Ok(())
};

pub const NIP: EuDef = |env| {
    env.check_nargs(2)?;
    env.stack.remove(env.iflip(1).unwrap());
    Ok(())
};

pub const PPOP: EuDef = |env| {
    env.check_nargs(2)?;
    env.stack.truncate(env.iflip(1).unwrap());
    Ok(())
};

pub const QPOP: EuDef = |env| {
    env.check_nargs(3)?;
    env.stack.truncate(env.iflip(2).unwrap());
    Ok(())
};

pub const NIX: EuDef = |env| {
    let a0 = env.arg("a0")?.try_isize()?;
    env.stack.remove(env.iflip(a0)?);
    Ok(())
};

pub const SWAP: EuDef = |env| {
    env.check_nargs(2)?;
    let a = env.iflip(0).unwrap();
    env.stack.make_mut().swap(a, a - 1);
    Ok(())
};

pub const REV: EuDef = |env| {
    env.stack.make_mut().reverse();
    Ok(())
};

pub const SWAPD: EuDef = |env| {
    env.check_nargs(3)?;
    let a = env.iflip(1).unwrap();
    env.stack.make_mut().swap(a, a - 1);
    Ok(())
};

pub const TUCK: EuDef = |env| {
    env.check_nargs(2)?;
    env.stack
        .insert(env.iflip(1).unwrap(), env.stack.last().unwrap().clone());
    Ok(())
};

pub const TRADE: EuDef = |env| {
    let a0 = env.arg("a0")?.try_isize()?;
    let i = env.iflip(a0)?;
    let j = env.iflip(0).unwrap();
    env.stack.make_mut().swap(i, j);
    Ok(())
};

pub const ROT: EuDef = |env| {
    env.check_nargs(3)?;
    let a0 = env.stack.remove(env.iflip(2).unwrap());
    env.push(a0);
    Ok(())
};

pub const UNROT: EuDef = |env| {
    env.check_nargs(3)?;
    let a0 = env.arg("a0")?;
    env.stack.insert(env.iflip(1).unwrap(), a0);
    Ok(())
};

pub const ROLL: EuDef = |env| {
    let a0 = env.arg("a0")?.try_isize()?;
    let t = env.stack.remove(env.iflip(a0)?);
    env.push(t);
    Ok(())
};

pub const UNROLL: EuDef = |env| {
    let a1 = env.arg("a1")?.try_isize()?;
    let i = env.iflip(a1)?;
    let a0 = env.arg("a0")?;
    env.stack.insert(i, a0);
    Ok(())
};

pub const WRAP: EuDef = |env| {
    let a0 = EuType::Vec(mem::take(&mut env.stack));
    env.stack.push(a0);
    Ok(())
};

pub const UNWRAP: EuDef = |env| {
    let a0 = env.pop()?.to_vec()?;
    env.stack.extend(a0);
    Ok(())
};

pub const USURP: EuDef = |env| {
    env.stack = env.pop()?.to_vec()?;
    Ok(())
};

pub const SUB_STACK: EuDef = |env| {
    let a1 = env.arg("a1 (eval)")?.to_expr()?;
    let a0 = env.arg("a0")?.to_vec()?;
    env.push(EuType::Vec(
        EuEnv::apply(a1, &a0, env.scope.clone(), env.ctx)?.stack,
    ));
    Ok(())
};

pub const DIP: EuDef = |env| {
    let a1 = env.arg("a1 (eval)")?.to_expr()?;
    let a0 = env.arg("a0")?;
    env.stack = EuEnv::apply(a1, &env.stack, env.scope.clone(), env.ctx)?.stack;
    env.push(a0);
    Ok(())
};
