use chumsky::primitive::NoneOf;

use crate::chunk::Chunk;
use crate::value::{Value, Key};
use crate::vm::Creature;
use crate::panic::Panic;
use crate::errors::panic;
use crate::js::*;
use crate::io::{MsgOut, MsgIn, do_io};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::mem::swap;
use std::rc::Rc;

const ANIMALS: [&str; 32] = [
    "tortoise",
    "hare",
    "squirrel",
    "hawk",
    "woodpecker",
    "cardinal",
    "coyote",
    "raccoon",
    "rat",
    "axolotl",
    "cormorant",
    "duck",
    "orca",
    "humbpack",
    "tern",
    "quokka",
    "koala",
    "kangaroo",
    "zebra",
    "hyena",
    "giraffe",
    "hippopotamus",
    "capybara",
    "python",
    "gopher",
    "crab",
    "trout",
    "osprey",
    "lemur",
    "wobbegong",
    "walrus",
    "opossum",
];

#[derive(Debug, Clone, PartialEq)]
enum Status {
    Empty,
    Borrowed,
    Nested(Creature),
}

impl std::fmt::Display for Status {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Status::Empty => write!(f, "empty"),
            Status::Borrowed => write!(f, "borrowed"),
            Status::Nested(creature) => write!(f, "nested {creature}"),
        }
    }
}

impl Status {
    pub fn receive(&mut self, msg: Value) {
        match self {
            Status::Nested(creature) => creature.receive(msg),
            Status::Borrowed => println!("sending a message to a borrowed process"),
            Status::Empty => println!("sending a message to a dead process"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Zoo {
    procs: Vec<Status>,
    empty: Vec<usize>,
    ids: HashMap<&'static str, usize>,
    dead: HashSet<&'static str>,
    kill_list: Vec<&'static str>,
    sleeping: HashMap<&'static str, f64>,
    active_idx: usize,
    active_id: &'static str,
    msgs: Vec<String>,
}

impl Zoo {
    pub fn new() -> Zoo {
        Zoo {
            procs: vec![],
            empty: vec![],
            ids: HashMap::new(),
            kill_list: vec![],
            dead: HashSet::new(),
            sleeping: HashMap::new(),
            active_idx: 0,
            active_id: "",
            msgs: vec![],
        }
    }

    fn random_id(&self) -> String {
        let rand_idx = (random() * 32.0) as usize;
        let idx = self.procs.len();
        format!("{}_{idx}", ANIMALS[rand_idx])
    }

    fn new_id(&self) -> &'static str {
        let mut new = self.random_id();
        while self.dead.iter().any(|old| *old == new) {
            new = self.random_id();
        }
        new.leak()
    }

    pub fn put(&mut self, mut proc: Creature) -> &'static str {
        if self.empty.is_empty() {
            let id = self.new_id();
            let idx = self.procs.len();
            proc.pid = id;
            self.procs.push(Status::Nested(proc));
            self.ids.insert(id, idx);
            id
        } else {
            let idx = self.empty.pop().unwrap();
            let rand = (random() * 32.0) as usize;
            let id = format!("{}_{idx}", ANIMALS[rand]).leak();
            proc.pid = id;
            self.ids.insert(id, idx);
            self.procs[idx] = Status::Nested(proc);
            id
        }
    }

    pub fn kill_linked(&mut self, id: &'static str) {
        self.msgs.push(format!("process :{id} terminated by linked process"));
        self.kill_list.push(id);
    }

    pub fn kill(&mut self, id: &'static str) {
        self.kill_list.push(id);
    }

    pub fn sleep(&mut self, id: &'static str, ms: f64) {
        self.sleeping
            .insert(id, now() + ms);
    }

    pub fn is_alive(&self, id: &'static str) -> bool {
        if self.kill_list.contains(&id) {
            return false;
        }
        let idx = self.ids.get(id);
        match idx {
            Some(idx) => match self.procs.get(*idx) {
                Some(proc) => match proc {
                    Status::Empty => false,
                    Status::Borrowed => true,
                    Status::Nested(_) => true,
                },
                None => false,
            },
            None => false,
        }
    }

    pub fn clean_up(&mut self) {
        while let Some(id) = self.kill_list.pop() {
            if let Some(idx) = self.ids.get(id) {
                console_log!("process :{id} terminated");
                self.procs[*idx] = Status::Empty;
                self.empty.push(*idx);
                self.ids.remove(id);
                self.dead.insert(id);
            }
        }

        self.sleeping
            .retain(|_, wakeup_time| now() < *wakeup_time);

        // console_log!(
        //     "currently sleeping processes: {}",
        //     self.sleeping
        //         .keys()
        //         .map(|id| id.to_string())
        //         .collect::<Vec<_>>()
        //         .join(" | ")
        // );
    }

    pub fn catch(&mut self, id: &'static str) -> Creature {
        if let Some(idx) = self.ids.get(id) {
            let mut proc = Status::Borrowed;
            swap(&mut proc, &mut self.procs[*idx]);
            let Status::Nested(proc) = proc else {
                unreachable!("tried to borrow an empty or already-borrowed process {id}");
            };
            proc
        } else {
            unreachable!("tried to borrow a non-existent process {id}");
        }
    }

    pub fn release(&mut self, proc: Creature) {
        let id = proc.pid;
        if let Some(idx) = self.ids.get(id) {
            let mut proc = Status::Nested(proc);
            swap(&mut proc, &mut self.procs[*idx]);
        }
    }

    pub fn is_available(&self) -> bool {
        match &self.procs[self.active_idx] {
            Status::Empty => false,
            Status::Borrowed => false,
            Status::Nested(proc) => !self.sleeping.contains_key(proc.pid),
        }
    }

    pub fn next(&mut self) -> &'static str {
        self.clean_up();
        let starting_idx = self.active_idx;
        self.active_idx = (self.active_idx + 1) % self.procs.len();
        while !self.is_available() {
            // we've gone round the process queue already
            // that means no process is active
            // but we may have processes that are alive and asleep
            // if nothing is active, yield back to the world's event loop
            if self.active_idx == starting_idx {
                return ""
            }
            self.active_idx = (self.active_idx + 1) % self.procs.len();
        }
        match &self.procs[self.active_idx] {
            Status::Empty | Status::Borrowed => unreachable!(),
            Status::Nested(proc) => proc.pid,
        }
    }

    pub fn send_msg(&mut self, id: &'static str, msg: Value) {
        let Some(idx) = self.ids.get(id) else {
            return;
        };
        self.procs[*idx].receive(msg);
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Buffers {
    console: Value,
    commands: Value,
    fetch_out: Value,
    fetch_in: Value,
    input: Value,
    keys_down: Value,
}

impl Buffers {
    pub fn new (prelude: imbl::HashMap<Key, Value>) -> Buffers {
        Buffers {
            console: prelude.get(&Key::Keyword("console")).unwrap().clone(),
            commands: prelude.get(&Key::Keyword("turtle_commands")).unwrap().clone(),
            fetch_out: prelude.get(&Key::Keyword("fetch_outbox")).unwrap().clone(),
            fetch_in: prelude.get(&Key::Keyword("fetch_inbox")).unwrap().clone(),
            input: prelude.get(&Key::Keyword("input")).unwrap().clone(),
            keys_down: prelude.get(&Key::Keyword("keys_down")).unwrap().clone(),
        }
    }

    pub fn console (&self) -> Rc<RefCell<Value>> {
        self.console.as_box()
    }

    pub fn input (&self) -> Rc<RefCell<Value>> {
        self.input.as_box()
    }

    pub fn commands (&self) -> Rc<RefCell<Value>> {
        self.commands.as_box()
    }

    pub fn fetch_out (&self) -> Rc<RefCell<Value>> {
        self.fetch_out.as_box()
    }

    pub fn fetch_in (&self) -> Rc<RefCell<Value>> {
        self.fetch_in.as_box()
    }

    pub fn keys_down (&self) -> Rc<RefCell<Value>> {
        self.keys_down.as_box()
    }

}

#[derive(Debug, Clone, PartialEq)]
pub struct World {
    zoo: Rc<RefCell<Zoo>>,
    active: Option<Creature>,
    main: &'static str,
    pub result: Option<Result<Value, Panic>>,
    buffers: Buffers,
    last_io: f64,
    kill_signal: bool,
}

impl World {
    pub fn new(chunk: Chunk, prelude: imbl::HashMap<Key, Value>, debug: bool) -> World {
        let zoo = Rc::new(RefCell::new(Zoo::new()));
        let main = Creature::new(chunk, zoo.clone(), debug);
        let id = zoo.borrow_mut().put(main);
        let buffers = Buffers::new(prelude);
        
        World {
            zoo,
            active: None,
            main: id,
            result: None,
            buffers,
            last_io: 0.0,
            kill_signal: false,
        }
    }

    fn next(&mut self) {
        let mut active = None;
        swap(&mut active, &mut self.active);
        let mut zoo = self.zoo.borrow_mut();
        if let Some(active) = active {
            zoo.release(active);
        }
        let new_active_id = zoo.next();
        if new_active_id.is_empty() {
            self.active = None;
            return;
        }
        let mut new_active_proc = zoo.catch(new_active_id);
        new_active_proc.reset_reductions();
        let mut new_active_opt = Some(new_active_proc);
        swap(&mut new_active_opt, &mut self.active);
    }

    fn activate_main(&mut self) {
        let main = self.zoo.borrow_mut().catch(self.main);
        self.active = Some(main);
    }

    fn active_id(&mut self) -> Option<&'static str> {
        match &self.active {
            Some(creature) => Some(creature.pid),
            None => None,
        }
    }

    fn kill_active(&mut self) {
        if let Some(pid) = self.active_id() {
            self.zoo.borrow_mut().kill(pid);
        }
    }

    fn active_result(&mut self) -> &Option<Result<Value, Panic>> {
        if self.active.is_none() { return &None; }
        &self.active.as_ref().unwrap().result
    }

    fn flush_buffers(&mut self) -> Vec<MsgOut> {
        let mut outbox = vec![];
        if let Some(console) = self.flush_console() {
            outbox.push(console);
        }
        if let Some(commands) = self.flush_commands() {
            outbox.push(commands);
        }
        if let Some(fetch) = self.make_fetch_happen() {
            outbox.push(fetch);
        }
        if let Some(msgs) = self.flush_zoo() {
            outbox.push(msgs);
        }
        outbox
    }

    fn flush_zoo(&mut self) -> Option<MsgOut> {
        let mut zoo_msgs = vec![];
        let mut zoo = self.zoo.borrow_mut();
        swap(&mut zoo_msgs, &mut zoo.msgs);
        if zoo_msgs.is_empty() {
            None
        } else {
            let inner = zoo_msgs.into_iter().map(Value::from_string).collect::<imbl::Vector<_>>();
            Some(MsgOut::Console(Value::list(inner)))
        }
    }

    fn make_fetch_happen(&self) -> Option<MsgOut> {
        let out = self.buffers.fetch_out();
        let working = RefCell::new(Value::Interned(""));
        out.swap(&working);
        let working = working.borrow();
        if working.as_string().is_empty() {
            None
        } else {
            Some(MsgOut::Fetch(working.clone()))
        }
    }

    fn flush_console(&self) -> Option<MsgOut> {
        let console = self.buffers.console();
        let working_copy = RefCell::new(Value::new_list());
        console.swap(&working_copy);
        let working_value = working_copy.borrow();
        if working_value.as_list().is_empty() {
            None
        } else {
            Some(MsgOut::Console(working_value.clone()))
        }
    }

    fn flush_commands(&self) -> Option<MsgOut> {
        let commands = self.buffers.commands();
        let working_copy = RefCell::new(Value::new_list());
        commands.swap(&working_copy);
        let commands = working_copy.borrow();
        if commands.as_list().is_empty() {
            None
        } else {
            Some(MsgOut::Commands(commands.clone()))
        }
    }

    fn complete_main(&mut self) -> Vec<MsgOut> {
        let mut outbox = self.flush_buffers();
        // TODO: if we have a panic, actually add the panic message to the console
        let result = self.active_result().clone().unwrap();        
        self.result = Some(result.clone());
        match result {
            Ok(value) => outbox.push(MsgOut::Complete(Value::from_string(value.show()))),
            Err(p) => {
                outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::from_string("Ludus panicked!".to_string())))));
                outbox.push(MsgOut::Error(panic(p)))
            }
        }
        outbox
    }

    fn interpret_active(&mut self) {
        self.active.as_mut().unwrap().interpret();
    }

    async fn maybe_do_io(&mut self) {
        if self.last_io + 10.0 < now() {
            let outbox = self.flush_buffers();
            let inbox = do_io(outbox).await;
            self.fill_buffers(inbox);
            self.last_io = now();
        }
    }

    fn fill_input(&mut self, str: String) {
        let value = Value::from_string(str);
        let working = RefCell::new(value);
        let input = self.buffers.input();
        input.swap(&working);
    }

    fn fetch_reply(&mut self, reply: Value) {
        let inbox_rc = self.buffers.fetch_in();
        inbox_rc.replace(reply);
    }

    fn register_keys(&mut self, keys: Value) {
        // console_log!("New keys: {keys}");
        let keys_down_rc = self.buffers.keys_down();
        keys_down_rc.replace(keys);
    }

    fn fill_buffers(&mut self, inbox: Vec<MsgIn>) {
        for msg in inbox {
            match msg {
                MsgIn::Input(str) => self.fill_input(str),
                MsgIn::Kill => self.kill_signal = true,
                MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()),
                MsgIn::Keys(..) => self.register_keys(msg.into_value()),
            }
        }
    }

    async fn ready_io(&mut self) {
        let inbox = do_io(vec![MsgOut::Ready]).await;
        self.fill_buffers(inbox);
        self.last_io = now();
    }

    fn send_ludus_msg(&mut self, msg: String) {
        let console = self.buffers.console();
        let mut console = console.as_ref().borrow_mut();
        let Value::List(ref mut console) = *console else {unreachable!("expect console to be a list")};
        console.push_back(Value::from_string(msg));
    }

    fn report_process_end(&mut self) {
        let result = self.active_result().clone().unwrap();
        let msg = match result {
            Ok(value) => format!("Process {} returned with {}", self.active_id().unwrap(), value.show()),
            Err(panic) => format!("Process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic))
        };
        self.send_ludus_msg(msg);
    }

    pub async fn run(&mut self) {
        self.activate_main();
        self.ready_io().await;
        loop {
            self.maybe_do_io().await;
            if self.kill_signal {
                let mut outbox = self.flush_buffers();
                outbox.push(MsgOut::Error("Ludus stopped by user".to_string()));
                do_io(outbox).await;
                return;
            }
            if self.active.is_some() {
                self.interpret_active();
            }
            if self.active_result().is_some() {
                if self.active_id().unwrap() == self.main {
                    let outbox = self.complete_main();
                    do_io(outbox).await;
                    return;
                } else {
                    self.report_process_end()
                }
                self.kill_active();
            }
            self.next();
        }
    }
}

