use crate::base::BaseFn;
use crate::chunk::Chunk;
use crate::js::*;
use crate::op::Op;
use crate::panic::{Panic, PanicMsg};
use crate::value::{Key, LFn, Value};
use crate::world::Zoo;
use imbl::{HashMap, Vector};
use num_traits::FromPrimitive;
use ordered_float::NotNan;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::mem::swap;
use std::rc::Rc;

const MAX_REDUCTIONS: usize = 1000;

#[derive(Debug, Clone, PartialEq)]
pub struct CallFrame {
    pub function: Value,
    pub arity: u8,
    pub stack_base: usize,
    pub ip: usize,
}

impl CallFrame {
    pub fn chunk(&self) -> &Chunk {
        let Value::Fn(ref function) = self.function else {
            unreachable!()
        };
        function.chunk(self.arity)
    }
}

impl fmt::Display for CallFrame {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let Value::Fn(ref function) = self.function else {
            unreachable!()
        };
        write!(
            f,
            "CallFrame: {}/{} @ {}",
            function.name(),
            self.arity,
            self.ip
        )
    }
}

fn combine_bytes(high: u8, low: u8) -> usize {
    let out = ((high as u16) << 8) + low as u16;
    out as usize
}

const REGISTER_SIZE: usize = 8;

#[derive(Debug, Clone, PartialEq)]
pub struct Creature {
    stack: Vec<Value>,
    call_stack: Vec<CallFrame>,
    frame: CallFrame,
    ip: usize,
    register: [Value; REGISTER_SIZE],
    matches: bool,
    match_depth: u8,
    pub result: Option<Result<Value, Panic>>,
    debug: bool,
    last_code: usize,
    pub pid: &'static str,
    pub mbx: VecDeque<Value>,
    msg_idx: usize,
    reductions: usize,
    zoo: Rc<RefCell<Zoo>>,
    r#yield: bool,
    scrutinee: Option<Value>,
    parents: Vec<&'static str>,
    siblings: Vec<&'static str>,
}

impl std::fmt::Display for Creature {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Creature. {} @{}", self.pid, self.ip)
    }
}

impl Creature {
    pub fn new(chunk: Chunk, zoo: Rc<RefCell<Zoo>>, debug: bool) -> Creature {
        let lfn = LFn::Defined {
            name: "toplevel",
            doc: None,
            chunks: vec![chunk],
            arities: vec![0],
            splat: 0,
            closed: RefCell::new(vec![]),
            patterns: vec![],
        };
        let base_fn = Value::Fn(Rc::new(lfn));
        Creature::spawn(base_fn, zoo, debug)
    }

    pub fn spawn(function: Value, zoo: Rc<RefCell<Zoo>>, debug: bool) -> Creature {
        let base_frame = CallFrame {
            function,
            stack_base: 0,
            ip: 0,
            arity: 0,
        };
        Creature {
            stack: vec![],
            call_stack: Vec::with_capacity(64),
            frame: base_frame,
            ip: 0,
            register: [const { Value::Nothing }; REGISTER_SIZE],
            matches: false,
            match_depth: 0,
            result: None,
            debug,
            last_code: 0,
            pid: "",
            zoo,
            mbx: VecDeque::new(),
            reductions: 0,
            r#yield: false,
            msg_idx: 0,
            scrutinee: None,
            parents: vec![],
            siblings: vec![],
        }
    }

    pub fn reduce(&mut self) {
        self.reductions += 1;
    }

    pub fn reset_reductions(&mut self) {
        self.reductions = 0;
        self.r#yield = false;
    }

    pub fn receive(&mut self, value: Value) {
        self.mbx.push_back(value);
    }

    pub fn chunk(&self) -> &Chunk {
        self.frame.chunk()
    }

    pub fn push(&mut self, value: Value) {
        self.stack.push(value);
    }

    pub fn pop(&mut self) -> Value {
        self.stack.pop().unwrap()
    }

    pub fn peek(&self) -> &Value {
        self.stack.last().unwrap()
    }

    pub fn print_stack(&self) {
        let mut inner = vec![];
        for (i, value) in self.stack.iter().enumerate() {
            if i == self.frame.stack_base {
                inner.push(format!("->{}<-", value.show()))
            } else {
                inner.push(value.show())
            }
        }
        let inner = inner.join("|");
        let register = self
            .register
            .iter()
            .map(|val| val.to_string())
            .collect::<Vec<_>>()
            .join(",");
        let mbx = self
            .mbx
            .iter()
            .map(|val| val.show())
            .collect::<Vec<_>>()
            .join("/");
        console_log!(
            "{:04}: [{inner}] ({register}) {} {{{mbx}}}",
            self.last_code,
            self.pid
        );
    }

    fn print_debug(&self) {
        self.print_stack();
        let mut ip = self.last_code;
        self.chunk().dissasemble_instr(&mut ip);
    }

    fn panic(&mut self, msg: PanicMsg) {
        // first prep the current frame for parsing
        let mut frame = self.frame.clone();
        frame.ip = self.ip;
        // add it to our cloned stack
        let mut call_stack = self.call_stack.clone();
        call_stack.push(frame);
        //make a panic
        let panic = Panic {
            msg,
            scrutinee: self.scrutinee.clone(),
            call_stack,
        };
        // and gtfo
        self.result = Some(Err(panic));
        self.r#yield = true;
        self.broadcast_panic();
    }

    pub fn panic_with(&mut self, msg: String) {
        self.panic(PanicMsg::Generic(msg));
    }

    fn get_value_at(&mut self, idx: u8) -> Value {
        let idx = idx as usize;
        let idx = idx + self.frame.stack_base;
        self.stack[idx].clone()
    }

    fn get_scrutinee(&mut self) -> Value {
        let idx = self.stack.len() - self.match_depth as usize - 1;
        self.stack[idx].clone()
    }

    fn read(&mut self) -> u8 {
        let code = self.chunk().bytecode[self.ip];
        self.ip += 1;
        code
    }

    fn read2(&mut self) -> usize {
        let high = self.read();
        let low = self.read();
        combine_bytes(high, low)
    }

    fn at_end(&mut self) -> bool {
        self.ip >= self.chunk().bytecode.len()
    }

    fn send_msg(&mut self, pid: Value, msg: Value) {
        let Value::Keyword(pid) = pid else {
            return self.panic_with(format!("Ludus expected pid keyword, and instead got {pid}"));
        };
        if self.pid == pid {
            self.mbx.push_back(msg.clone());
        } else {
            self.zoo.borrow_mut().send_msg(pid, msg);
        }
        self.push(Value::Keyword("ok"));
    }

    fn broadcast_panic(&mut self) {
        console_log!("broadcasting panic in {}", self.pid);
        let Err(panic) = self.result.clone().unwrap() else {
            unreachable!("expected panic in broadcast panic");
        };
        let msg = Rc::new(crate::errors::panic(panic));
        for pid in self.parents.clone() {
            self.send_msg(
                Value::Keyword(pid),
                Value::tuple(vec![
                    Value::Keyword("exit"),
                    Value::Keyword(self.pid),
                    Value::tuple(vec![Value::Keyword("err"), Value::String(msg.clone())]),
                ]),
            )
        }
        for pid in self.siblings.clone() {
            self.zoo.borrow_mut().kill_linked(pid);
        }
    }

    fn broadcast_return(&mut self) {
        // console_log!("broadcasting return from {}", self.pid);
        //TODO: work around this clone?
        for pid in self.parents.clone() {
            let result_tuple = Value::tuple(vec![
                Value::Keyword("ok"),
                self.result.clone().unwrap().unwrap(),
            ]);
            let exit_signal = Value::tuple(vec![
                Value::Keyword("exit"),
                Value::Keyword(self.pid),
                result_tuple,
            ]);
            // console_log!("sending exit signal {exit_signal}");
            self.send_msg(Value::Keyword(pid), exit_signal);
        }
        // returns no longer kill siblings
        // for pid in self.siblings.clone() {
        //     self.zoo.borrow_mut().kill(pid);
        // }
    }

    // TODO: fix these based on what I decide about `link` & `monitor`
    fn monitor(&mut self, other: Value) {
        // console_log!("monitoring {other}");
        let Value::Keyword(other) = other else {
            unreachable!("expected keyword pid in monitor");
        };
        if other != self.pid {
            self.do_unlink(Value::Keyword(other));
            let mut other = self.zoo.borrow_mut().catch(other);
            other.parents.push(self.pid);
            self.zoo.borrow_mut().release(other);
            // console_log!("{} added to parents", self.pid);
        }
        self.push(Value::Keyword("ok"));
    }

    fn delete_from_siblings(&mut self, other: &'static str) {
        let idx = self.siblings.iter().position(|pid| other == *pid);
        if let Some(idx) = idx {
            self.siblings.swap_remove(idx);
        }
    }

    fn unlink(&mut self, other: Value) {
        self.do_unlink(other);
        self.push(Value::Keyword("ok"))
    }

    fn do_unlink(&mut self, other: Value) {
        let Value::Keyword(other) = other else {
            unreachable!("expected keyword pid in unlink")
        };
        if other != self.pid {
            self.delete_from_siblings(other);
            let mut other = self.zoo.borrow_mut().catch(other);
            other.delete_from_siblings(self.pid);
            self.zoo.borrow_mut().release(other);
        }
    }

    fn link(&mut self, other: Value) {
        let Value::Keyword(other) = other else {
            unreachable!("expected keyword pid in link");
        };
        // if we're linking to ourselves, we don't need to do anything
        if other != self.pid {
            self.siblings.push(other);
            let mut other = self.zoo.borrow_mut().catch(other);
            other.siblings.push(self.pid);
            self.zoo.borrow_mut().release(other);
        }
        self.push(Value::Keyword("ok"));
    }

    fn handle_msg(&mut self, args: Vec<Value>) {
        // console_log!("message received by {}: {}", self.pid, args[0]);
        let Value::Keyword(msg) = args.first().unwrap() else {
            return self.panic_with("malformed message to Process".to_string());
        };
        match *msg {
            "self" => self.push(Value::Keyword(self.pid)),
            "send" => self.send_msg(args[1].clone(), args[2].clone()),
            "spawn" => {
                let f = args[1].clone();
                let proc = Creature::spawn(f, self.zoo.clone(), self.debug);
                let id = self.zoo.as_ref().borrow_mut().put(proc);
                // console_log!("spawning new process {id}!");
                self.push(Value::Keyword(id));
            }
            "yield" => {
                self.r#yield = true;
                console_log!("yielding from {}", self.pid);
                self.push(Value::Keyword("ok"));
            }
            "alive" => {
                let Value::Keyword(pid) = args[1].clone() else {
                    unreachable!();
                };
                let is_alive = self.zoo.as_ref().borrow().is_alive(pid);
                if is_alive {
                    self.push(Value::True)
                } else {
                    self.push(Value::False)
                }
            }
            "link" => self.link(args[1].clone()),
            "monitor" => self.monitor(args[1].clone()),
            "unlink" => self.unlink(args[1].clone()),
            "flush" => {
                let msgs = self.mbx.iter().cloned().collect::<Vec<_>>();
                let msgs = Vector::from(msgs);
                console_log!(
                    "delivering messages: {}",
                    msgs.iter()
                        .map(|x| x.show())
                        .collect::<Vec<_>>()
                        .join(" | ")
                );
                self.mbx = VecDeque::new();
                console_log!("flushing messages in {}", self.pid);
                self.push(Value::List(Box::new(msgs)));
            }
            "sleep" => {
                console_log!("sleeping {} for {}", self.pid, args[1]);
                let Value::Number(ms) = args[1] else {
                    unreachable!()
                };
                self.zoo
                    .as_ref()
                    .borrow_mut()
                    .sleep(self.pid, f64::from(ms));
                self.r#yield = true;
                self.push(Value::Keyword("ok"));
            }
            msg => panic!("Process does not understand message: {msg}"),
        }
    }

    pub fn interpret(&mut self) {
        // console_log!("starting process {}", self.pid);
        // console_log!(
        //     "mbx: {}",
        //     self.mbx
        //         .iter()
        //         .map(|x| x.show())
        //         .collect::<Vec<_>>()
        //         .join(" | ")
        // );
        loop {
            if self.at_end() {
                let result = self.stack.pop().unwrap();
                self.broadcast_return();
                // console_log!("process :{} has returned {result}", self.pid);
                self.result = Some(Ok(result));
                return;
            }
            if self.r#yield {
                // console_log!("yielding from {}", self.pid);
                return;
            }
            if self.reductions >= MAX_REDUCTIONS {
                // console_log!(
                //     "process {} is yielding after {MAX_REDUCTIONS} reductions",
                //     self.pid
                // );
                return;
            }
            let code = self.read();
            if self.debug {
                self.last_code = self.ip - 1;
                self.print_debug();
            }
            let op = Op::from_u8(code).unwrap();
            use Op::*;
            match op {
                Noop => (),
                Nil => self.push(Value::Nil),
                Nothing => self.push(Value::Nothing),
                True => self.push(Value::True),
                False => self.push(Value::False),
                Msg => {
                    let _ = self.read();
                }
                Constant => {
                    let const_idx = self.read2();
                    let value = self.chunk().constants[const_idx].clone();
                    self.push(value);
                }
                Jump => {
                    let jump_len = self.read2();
                    self.ip += jump_len;
                }
                JumpBack => {
                    let jump_len = self.read2();
                    self.ip -= jump_len + 3;
                }
                JumpIfFalse => {
                    let jump_len = self.read2();
                    let cond = self.pop();
                    match cond {
                        Value::Nil | Value::False => self.ip += jump_len,
                        _ => (),
                    }
                }
                JumpIfTrue => {
                    let jump_len = self.read2();
                    let cond = self.pop();
                    match cond {
                        Value::Nil | Value::False => (),
                        _ => self.ip += jump_len,
                    }
                }
                JumpIfZero => {
                    let jump_len = self.read2();
                    let cond = self.pop();
                    match cond {
                        Value::Number(x) if f64::from(x) <= 0.0 => self.ip += jump_len,
                        Value::Number(..) => (),
                        _ => {
                            return self
                                .panic_with(format!("repeat requires a number, but got {cond}"))
                        }
                    }
                }
                Pop => {
                    self.pop();
                }
                PopN => {
                    let n = self.read() as usize;
                    self.stack.truncate(self.stack.len() - n);
                }
                PushBinding => {
                    let idx = self.read();
                    let value = self.get_value_at(idx);
                    self.push(value);
                }
                PushGlobal => {
                    let key = self.pop();
                    let Value::Keyword(name) = key else {
                        unreachable!("internal Ludus error: expected key for global resolution")
                    };
                    let value = self.chunk().env.get(&Key::Keyword(name)).unwrap();
                    self.push(value.clone());
                }
                Store => {
                    self.register[0] = self.pop();
                }
                StoreN => {
                    let n = self.read() as usize;
                    for i in (0..n).rev() {
                        self.register[i] = self.pop();
                    }
                }
                Stash => {
                    self.register[0] = self.peek().clone();
                }
                Load => {
                    let mut value = Value::Nothing;
                    swap(&mut self.register[0], &mut value);
                    self.push(value);
                }
                LoadN => {
                    let n = self.read() as usize;
                    for i in 0..n {
                        let mut value = Value::Nothing;
                        swap(&mut self.register[i], &mut value);
                        self.push(value);
                    }
                }
                ResetMatch => {
                    self.matches = false;
                    self.match_depth = 0;
                }
                UnconditionalMatch => {
                    self.matches = true;
                }
                MatchType => {
                    let as_type = self.pop();
                    let Value::Keyword(as_type) = as_type else {
                        unreachable!()
                    };
                    let value = self.get_scrutinee();
                    let val_type = value.type_of();
                    self.matches = val_type == as_type;
                }
                MatchNil => {
                    let value = self.get_scrutinee();
                    self.matches = value == Value::Nil;
                }
                MatchTrue => {
                    let value = self.get_scrutinee();
                    self.matches = value == Value::True;
                }
                MatchFalse => {
                    let value = self.get_scrutinee();
                    self.matches = value == Value::False;
                }
                PanicNoMatch => {
                    if !self.matches {
                        self.ip -= 1;
                        return self.panic(PanicMsg::NoMatch);
                    }
                }
                PanicNoLetMatch => {
                    if !self.matches {
                        self.ip -= 2;
                        console_log!("panic at bytecode {}", self.ip);
                        return self.panic(PanicMsg::NoLetMatch);
                    }
                }
                PanicNoFnMatch => {
                    if !self.matches {
                        self.ip -= 2;
                        return self.panic(PanicMsg::NoFnMatch);
                    }
                }
                MatchConstant => {
                    let const_idx = self.read2();
                    let scrutinee = self.get_scrutinee();
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    self.matches = scrutinee == self.chunk().constants[const_idx];
                }
                MatchString => {
                    let pattern_idx = self.read();
                    let scrutinee = self.get_scrutinee();
                    self.matches = match scrutinee {
                        Value::String(str) => self.chunk().string_patterns[pattern_idx as usize]
                            .re
                            .is_match(str.as_str()),
                        Value::Interned(str) => self.chunk().string_patterns[pattern_idx as usize]
                            .re
                            .is_match(str),
                        _ => false,
                    };
                }
                PushStringMatches => {
                    let pattern_idx = self.read();
                    let pattern_len = self.chunk().string_patterns[pattern_idx as usize]
                        .words
                        .len();
                    // let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1;
                    let scrutinee = self.get_scrutinee();
                    let scrutinee = match scrutinee {
                        Value::String(str) => str.as_ref().clone(),
                        Value::Interned(str) => str.to_string(),
                        _ => unreachable!(),
                    };
                    let captures = self.chunk().string_patterns[pattern_idx as usize]
                        .re
                        .captures(scrutinee.as_str())
                        .unwrap();
                    for cap in 0..pattern_len {
                        self.push(Value::String(Rc::new(captures[cap + 1].to_string())))
                    }
                    self.match_depth += pattern_len as u8;
                }
                MatchTuple => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let tuple_len = self.read() as usize;
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::Tuple(members) => self.matches = members.len() == tuple_len,
                        _ => self.matches = false,
                    };
                }
                MatchSplattedTuple => {
                    let patt_len = self.read() as usize;
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::Tuple(members) => self.matches = members.len() >= patt_len,
                        _ => self.matches = false,
                    }
                }
                PushTuple => {
                    let tuple_len = self.read() as usize;
                    let tuple_members = self.stack.split_off(self.stack.len() - tuple_len);
                    let tuple = Value::Tuple(Rc::new(tuple_members));
                    self.push(tuple);
                }
                LoadTuple => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    // let tuple = self.stack[idx].clone();
                    let tuple = self.get_scrutinee();
                    match tuple {
                        Value::Tuple(members) => {
                            for member in members.iter() {
                                self.push(member.clone());
                            }
                        }
                        _ => {
                            return self
                                .panic_with(format!("internal error: expected tuple, got {tuple}"))
                        }
                    };
                }
                LoadSplattedTuple => {
                    let load_len = self.read() as usize;
                    let tuple = self.get_scrutinee();
                    let Value::Tuple(members) = tuple else {
                        return self
                            .panic_with(format!("internal error: expected tuple, got {tuple}"));
                    };
                    for i in 0..load_len - 1 {
                        self.push(members[i].clone());
                    }
                    let mut splatted = Vector::new();
                    for i in load_len - 1..members.len() {
                        splatted.push_back(members[i].clone());
                    }
                    self.push(Value::List(Box::new(splatted)));
                }
                PushList => {
                    self.push(Value::List(Box::new(Vector::new())));
                }
                AppendList => {
                    let value = self.pop();
                    let list = self.pop();
                    let Value::List(mut members) = list else {
                        return self.panic_with(format!(
                            "only lists may be splatted into lists, but got {list}"
                        ));
                    };
                    members.push_back(value);
                    self.push(Value::List(members));
                }
                ConcatList => {
                    let list = self.pop();
                    let target = self.pop();
                    let Value::List(mut target) = target else {
                        unreachable!()
                    };
                    let Value::List(splatted) = list else {
                        return self.panic_with(format!(
                            "only lists may be splatted into lists, but got {list}"
                        ));
                    };
                    target.append(*splatted);
                    self.push(Value::List(target));
                }
                MatchList => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let list_len = self.read() as usize;
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::List(members) => self.matches = members.len() == list_len,
                        _ => self.matches = false,
                    };
                }
                MatchSplattedList => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let patt_len = self.read() as usize;
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::List(members) => self.matches = members.len() >= patt_len,
                        _ => self.matches = false,
                    }
                }
                LoadList => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let list = self.get_scrutinee();
                    match list {
                        Value::List(members) => {
                            for member in members.iter() {
                                self.push(member.clone());
                            }
                        }
                        _ => {
                            return self
                                .panic_with(format!("internal error: expected list, got {list}"))
                        }
                    };
                }
                LoadSplattedList => {
                    let loaded_len = self.read() as usize;
                    let list = self.get_scrutinee();
                    let Value::List(members) = list else {
                        return self
                            .panic_with(format!("internal error: expected list, got {list}"));
                    };
                    for i in 0..loaded_len - 1 {
                        self.push(members[i].clone());
                    }
                    let splatted = Value::List(Box::new(members.skip(loaded_len - 1)));
                    self.push(splatted);
                }
                PushDict => {
                    self.push(Value::Dict(Box::new(HashMap::new())));
                }
                AppendDict => {
                    let value = self.pop();
                    let key = Key::from_value(self.pop());
                    let Value::Dict(mut dict) = self.pop() else {
                        unreachable!()
                    };
                    dict.insert(key, value);
                    self.push(Value::Dict(dict));
                }
                ConcatDict => {
                    let prolly_dict = self.pop();
                    let Value::Dict(splatted) = prolly_dict else {
                        return self.panic_with(format!(
                            "only dicts may be splatted into dicts, got {prolly_dict}"
                        ));
                    };
                    let Value::Dict(target) = self.pop() else {
                        unreachable!()
                    };
                    let union = splatted.union(*target);
                    self.push(Value::Dict(Box::new(union)));
                }
                LoadDictValue => {
                    let dict_idx = self.read();
                    let dict = match self.get_value_at(dict_idx) {
                        Value::Dict(dict) => dict,
                        value => {
                            console_log!(
                                "internal Ludus error in function {}",
                                self.frame.function.as_fn().name()
                            );
                            unreachable!("expected dict, got {value}")
                        }
                    };
                    let key = Key::from_value(self.pop());
                    let value = dict.get(&key).unwrap_or(&Value::Nil);
                    self.push(value.clone());
                }
                MatchDict => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let dict_len = self.read();
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::Dict(members) => self.matches = members.len() == dict_len as usize,
                        _ => self.matches = false,
                    };
                }
                MatchSplattedDict => {
                    // let idx = self.stack.len() - self.match_depth as usize - 1;
                    let patt_len = self.read() as usize;
                    let scrutinee = self.get_scrutinee();
                    match scrutinee {
                        Value::Dict(members) => self.matches = members.len() >= patt_len,
                        _ => self.matches = false,
                    }
                }
                DropDictEntry => {
                    let key_to_drop = Key::from_value(self.pop());
                    let Value::Dict(mut dict) = self.pop() else {
                        unreachable!()
                    };
                    dict.remove(&key_to_drop);
                    self.push(Value::Dict(dict));
                }
                PushBox => {
                    let val = self.pop();
                    self.push(Value::Box(Rc::new(RefCell::new(val))));
                }
                GetKey => {
                    let key = Key::from_value(self.pop());
                    let dict = self.pop();
                    let value = match dict {
                        Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(),
                        _ => Value::Nil,
                    };
                    self.push(value);
                }
                JumpIfNoMatch => {
                    let jump_len = self.read2();
                    // let jump_len = self.chunk().bytecode[self.ip + 1] as usize;
                    if !self.matches {
                        self.ip += jump_len
                    }
                }
                JumpIfMatch => {
                    let jump_len = self.read2();
                    if self.matches {
                        self.ip += jump_len;
                    }
                }
                TypeOf => {
                    let val = self.pop();
                    let type_of = Value::Keyword(val.type_of());
                    self.push(type_of);
                }
                ToInt => {
                    let val = self.pop();
                    if let Value::Number(x) = val {
                        self.push(Value::Number(
                            NotNan::new(f64::from(x) as usize as f64).unwrap(),
                        ));
                    } else {
                        return self.panic_with(format!("repeat requires a number, but got {val}"));
                    }
                }
                Decrement => {
                    let val = self.pop();
                    if let Value::Number(x) = val {
                        self.push(Value::from_f64(f64::from(x) - 1.0));
                    } else {
                        return self
                            .panic_with(format!("you may only decrement a number, but got {val}"));
                    }
                }
                Duplicate => {
                    self.push(self.peek().clone());
                }
                MatchDepth => {
                    self.match_depth = self.read();
                }
                PanicWhenFallthrough => {
                    return self.panic_with(
                        "when form fallthrough: expected one clause to be truthy".to_string(),
                    );
                }
                Eq => {
                    let first = self.pop();
                    let second = self.pop();
                    if first == second {
                        self.push(Value::True)
                    } else {
                        self.push(Value::False)
                    }
                }
                Add => {
                    let first = self.pop();
                    let second = self.pop();
                    if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
                        self.push(Value::Number(x + y))
                    } else {
                        return self.panic_with(format!(
                            "`add` requires two numbers, but got {second}, {first}"
                        ));
                    }
                }
                Sub => {
                    let first = self.pop();
                    let second = self.pop();
                    if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
                        self.push(Value::Number(y - x))
                    } else {
                        return self.panic_with(format!(
                            "`sub` requires two numbers, but got {second}, {first}"
                        ));
                    }
                }
                Mult => {
                    let first = self.pop();
                    let second = self.pop();
                    if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
                        self.push(Value::Number(x * y))
                    } else {
                        return self.panic_with(format!(
                            "`mult` requires two numbers, but got {second}, {first}"
                        ));
                    }
                }
                Div => {
                    let first = self.pop();
                    let second = self.pop();
                    if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
                        if x == 0.0 {
                            return self.panic_with("division by 0".to_string());
                        }
                        self.push(Value::Number(y / x))
                    } else {
                        return self.panic_with(format!(
                            "`div` requires two numbers, but got {second}, {first}"
                        ));
                    }
                }
                Unbox => {
                    let the_box = self.pop();
                    let inner = if let Value::Box(b) = the_box {
                        b.borrow().clone()
                    } else {
                        return self
                            .panic_with(format!("`unbox` requires a box, but got {the_box}"));
                    };
                    self.push(inner);
                }
                BoxStore => {
                    let new_value = self.pop();
                    let the_box = self.pop();
                    if let Value::Box(b) = the_box {
                        b.replace(new_value.clone());
                    } else {
                        return self
                            .panic_with(format!("`store` requires a box, but got {the_box}"));
                    }
                    self.push(new_value);
                }
                Assert => {
                    let value = self.stack.last().unwrap();
                    if let Value::Nil | Value::False = value {
                        return self.panic_with("asserted falsy value".to_string());
                    }
                }
                Get => {
                    let key = self.pop();
                    if !matches!(
                        key,
                        Value::Keyword(_) | Value::String(_) | Value::Interned(_)
                    ) {
                        return self.panic_with(format!(
                            "dict keys must be keywords or strings, but got {key}"
                        ));
                    }
                    let key = Key::from_value(key);
                    let dict = self.pop();
                    let value = match dict {
                        Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(),
                        _ => Value::Nil.clone(),
                    };
                    self.push(value);
                }
                At => {
                    let idx = self.pop();
                    let ordered = self.pop();
                    let value = match (ordered, idx.clone()) {
                        (Value::List(l), Value::Number(i)) => {
                            l.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone()
                        }
                        (Value::Tuple(t), Value::Number(i)) => {
                            t.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone()
                        }
                        (_, Value::Number(_)) => Value::Nil,
                        _ => {
                            return self
                                .panic_with(format!("indexes must be numbers, but got {idx}"))
                        }
                    };
                    self.push(value);
                }
                Not => {
                    let value = self.pop();
                    let negated = match value {
                        Value::Nil | Value::False => Value::True,
                        _ => Value::False,
                    };
                    self.push(negated);
                }
                Panic => {
                    let msg = self.pop().stringify();
                    return self.panic_with(msg);
                }
                EmptyString => {
                    self.push(Value::String(Rc::new("".to_string())));
                }
                //TODO: don't use the schlemiel's algo here
                ConcatStrings => {
                    let second = self.pop();
                    let first = self.pop();
                    let combined = match (first, second) {
                        (Value::String(first), Value::String(second)) => {
                            let mut new = first.as_ref().clone();
                            new.push_str(second.as_str());
                            Value::String(Rc::new(new))
                        }
                        _ => unreachable!(),
                    };
                    self.push(combined);
                }
                Stringify => {
                    let to_stringify = self.pop();
                    let the_string = to_stringify.stringify();
                    let stringified = Value::String(Rc::new(the_string));
                    self.push(stringified);
                }
                Partial => {
                    let arity = self.read();
                    let the_fn = self.pop();
                    let Value::Fn(ref inner) = the_fn else {
                        return self.panic_with(format!(
                            "only functions may be partially applied, but got {the_fn}"
                        ));
                    };
                    let args = self.stack.split_off(self.stack.len() - arity as usize);
                    let partial = crate::value::Partial {
                        args,
                        name: inner.name(),
                        function: the_fn,
                    };
                    self.push(Value::Partial(Rc::new(partial)));
                }
                TailCall => {
                    self.reduce();
                    let arity = self.read();

                    let called = self.pop();

                    if self.debug {
                        console_log!(
                            "=== tail call into {called}/{arity} from {} ===",
                            self.frame.function.as_fn().name()
                        );
                    }

                    match called {
                        Value::Process => {
                            let args = self.stack.split_off(self.stack.len() - arity as usize);
                            self.handle_msg(args);
                        }
                        Value::Fn(_) => {
                            if !called.as_fn().accepts(arity) {
                                return self.panic_with(format!(
                                    "wrong number of arguments to {} passing {arity} args",
                                    called.show()
                                ));
                            }
                            // first put the arguments in the register
                            for i in 0..arity as usize {
                                self.register[arity as usize - i - 1] = self.pop();
                            }

                            // save the arguments as our scrutinee
                            let mut scrutinee = vec![];
                            for i in 0..arity as usize {
                                scrutinee.push(self.register[i].clone())
                            }
                            self.scrutinee = Some(Value::tuple(scrutinee));

                            // then pop everything back to the current stack frame
                            self.stack.truncate(self.frame.stack_base);
                            // then push the arguments back on the stack
                            let mut i = 0;
                            while i < 8 && self.register[i] != Value::Nothing {
                                let mut value = Value::Nothing;
                                swap(&mut self.register[i], &mut value);
                                self.push(value);
                                i += 1;
                            }

                            let splat_arity = called.as_fn().splat_arity();
                            if splat_arity > 0 && arity >= splat_arity {
                                let splatted_args = self.stack.split_off(
                                    self.stack.len() - (arity - splat_arity) as usize - 1,
                                );
                                let gathered_args = Vector::from(splatted_args);
                                self.push(Value::List(Box::new(gathered_args)));
                            }
                            let arity = if splat_arity > 0 {
                                splat_arity.min(arity)
                            } else {
                                arity
                            };

                            let mut frame = CallFrame {
                                function: called,
                                arity,
                                stack_base: self.stack.len() - arity as usize,
                                ip: 0,
                            };

                            swap(&mut self.frame, &mut frame);
                            frame.ip = self.ip;

                            self.ip = 0;
                        }
                        Value::BaseFn(base_fn) => {
                            let value = match (arity, *base_fn) {
                                (0, BaseFn::Nullary(_, f)) => f(),
                                (1, BaseFn::Unary(_, f)) => f(&self.pop()),
                                (2, BaseFn::Binary(_, f)) => {
                                    let y = &self.pop();
                                    let x = &self.pop();
                                    f(x, y)
                                }
                                (3, BaseFn::Ternary(_, f)) => {
                                    let z = &self.pop();
                                    let y = &self.pop();
                                    let x = &self.pop();
                                    f(x, y, z)
                                }
                                _ => {
                                    return self.panic_with(
                                        "internal ludus error: bad base fn call".to_string(),
                                    )
                                }
                            };
                            self.push(value);
                        }
                        Value::Partial(partial) => {
                            let last_arg = self.pop();
                            let args = &partial.args;
                            for arg in args {
                                if *arg == Value::Nothing {
                                    self.push(last_arg.clone());
                                } else {
                                    self.push(arg.clone());
                                }
                            }
                            let the_fn = partial.function.clone();
                            let mut frame = CallFrame {
                                function: the_fn,
                                arity: args.len() as u8,
                                stack_base: self.stack.len() - args.len(),
                                ip: 0,
                            };

                            swap(&mut self.frame, &mut frame);
                            frame.ip = self.ip;

                            self.call_stack.push(frame);
                            self.ip = 0;
                        }
                        Value::Keyword(key) => {
                            let dict = self.pop();
                            if arity != 1 {
                                return self.panic_with(
                                    "called keywords may only take a single argument".to_string(),
                                );
                            }
                            match dict {
                                Value::Dict(d) => self
                                    .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()),
                                _ => self.push(Value::Nil),
                            }
                        }
                        _ => {
                            return self.panic_with(format!("{} is not a function", called.show()))
                        }
                    }
                }
                Call => {
                    self.reduce();
                    let arity = self.read();

                    let called = self.pop();

                    if self.debug {
                        console_log!("=== calling into {called}/{arity} ===");
                    }

                    match called {
                        Value::Process => {
                            let args = self.stack.split_off(self.stack.len() - arity as usize);
                            self.handle_msg(args);
                        }
                        Value::Fn(_) => {
                            if !called.as_fn().accepts(arity) {
                                return self.panic_with(format!(
                                    "wrong number of arguments to {} passing {arity} args",
                                    called.show()
                                ));
                            }

                            let mut scrutinee = vec![];
                            for i in 0..arity {
                                scrutinee.push(
                                    self.stack[self.stack.len() - arity as usize + i as usize]
                                        .clone(),
                                )
                            }
                            self.scrutinee = Some(Value::tuple(scrutinee));

                            let splat_arity = called.as_fn().splat_arity();
                            if splat_arity > 0 && arity >= splat_arity {
                                let splatted_args = self.stack.split_off(
                                    self.stack.len() - (arity - splat_arity) as usize - 1,
                                );
                                let gathered_args = Vector::from(splatted_args);
                                self.push(Value::List(Box::new(gathered_args)));
                            }

                            let arity = if splat_arity > 0 {
                                splat_arity.min(arity)
                            } else {
                                arity
                            };

                            let mut frame = CallFrame {
                                function: called,
                                arity,
                                stack_base: self.stack.len() - arity as usize,
                                ip: 0,
                            };

                            swap(&mut self.frame, &mut frame);
                            frame.ip = self.ip;

                            self.call_stack.push(frame);
                            self.ip = 0;
                        }
                        Value::BaseFn(base_fn) => {
                            let value = match (arity, *base_fn) {
                                (0, BaseFn::Nullary(_, f)) => f(),
                                (1, BaseFn::Unary(_, f)) => f(&self.pop()),
                                (2, BaseFn::Binary(_, f)) => {
                                    let y = &self.pop();
                                    let x = &self.pop();
                                    f(x, y)
                                }
                                (3, BaseFn::Ternary(_, f)) => {
                                    let z = &self.pop();
                                    let y = &self.pop();
                                    let x = &self.pop();
                                    f(x, y, z)
                                }
                                _ => {
                                    return self.panic_with(
                                        "internal ludus error: bad base fn call".to_string(),
                                    )
                                }
                            };
                            self.push(value);
                        }
                        Value::Partial(partial) => {
                            let last_arg = self.pop();
                            let args = &partial.args;
                            for arg in args {
                                if *arg == Value::Nothing {
                                    self.push(last_arg.clone());
                                } else {
                                    self.push(arg.clone());
                                }
                            }
                            let the_fn = partial.function.clone();
                            let mut frame = CallFrame {
                                function: the_fn,
                                arity: args.len() as u8,
                                stack_base: self.stack.len() - args.len(),
                                ip: 0,
                            };

                            swap(&mut self.frame, &mut frame);
                            frame.ip = self.ip;

                            self.call_stack.push(frame);
                            self.ip = 0;
                        }
                        Value::Keyword(key) => {
                            let dict = self.pop();
                            if arity != 1 {
                                return self.panic_with(
                                    "called keywords may only take a single argument".to_string(),
                                );
                            }
                            match dict {
                                Value::Dict(d) => self
                                    .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()),
                                _ => self.push(Value::Nil),
                            }
                        }

                        _ => {
                            return self.panic_with(format!("{} is not a function", called.show()))
                        }
                    }
                }
                Return => {
                    if self.debug {
                        console_log!("== returning from {} ==", self.frame.function.show());
                        let destination = match self.call_stack.last() {
                            Some(frame) => frame.function.as_fn().name(),
                            None => "toplevel",
                        };
                        console_log!("== returning to {destination} ==");
                    }
                    let mut value = Value::Nothing;
                    swap(&mut self.register[0], &mut value);
                    match self.call_stack.pop() {
                        Some(frame) => {
                            self.ip = frame.ip;
                            self.frame = frame;
                            self.push(value);
                        }
                        None => {
                            // console_log!("process {} has returned with {}", self.pid, value);
                            self.result = Some(Ok(value));
                            self.broadcast_return();
                            return;
                        }
                    }
                }
                Print => {
                    console_log!("{}", self.pop().show());
                    self.push(Value::Keyword("ok"));
                }
                SetUpvalue => {
                    let value = self.pop();
                    let Value::Fn(lfn) = self.peek() else {
                        panic!("expected function closing over value, got {}", self.peek());
                    };
                    lfn.close(value);
                }
                GetUpvalue => {
                    let idx = self.read();
                    if let Value::Fn(ref inner) = self.frame.function {
                        self.push(inner.as_ref().upvalue(idx));
                    } else {
                        unreachable!();
                    }
                }
                NextMessage => {
                    self.msg_idx += 1;
                }
                LoadMessage => match self.mbx.get(self.msg_idx) {
                    Some(msg) => self.push(msg.clone()),
                    None => {
                        self.msg_idx = 0;
                        self.r#yield = true;
                        self.ip -= 2;
                    }
                },
                MatchMessage => {
                    self.mbx.remove(self.msg_idx).unwrap();
                }
                ClearMessage => {
                    self.msg_idx = 0;
                }
                SendMethod => {
                    let Value::Tuple(args) = self.pop() else {
                        unreachable!("method args should be a tuple");
                    };
                    let method = self.pop();
                    let target = self.pop();
                    let mut msg = vec![method];
                    for arg in args.as_ref() {
                        msg.push(arg.clone());
                    }
                    self.send_msg(target, Value::tuple(msg));
                }
                LoadScrutinee => {
                    self.scrutinee = Some(self.peek().clone());
                }
                Spawn => {
                    let f = self.pop();
                    let proc = Creature::spawn(f, self.zoo.clone(), self.debug);
                    let id = self.zoo.borrow_mut().put(proc);
                    self.push(Value::Keyword(id));
                }
            }
        }
    }
}
