use std::prelude::v1::*;
use std::collections::{VecDeque, BTreeMap};
use std::rc::Rc;
use crate::*;
use crate::gc::*;
use crate::slotmap::*;
use crate::runtime::*;
use crate::bytecode::*;
use crate::process::*;
new_key! {
    struct ProcessKey;
}
pub struct IdleAction {
    count: usize,
    thresh: usize,
    action: Box<dyn FnMut()>,
}
impl IdleAction {
    pub fn new(thresh: usize, action: Box<dyn FnMut()>) -> Self {
        Self { count: 0, thresh, action }
    }
    pub fn consume<C: CustomTypes<S>, S: System<C>>(&mut self, res: &ProjectStep<'_, C, S>) {
        match res {
            ProjectStep::Idle | ProjectStep::Yield | ProjectStep::Pause => {
                self.count += 1;
                if self.count >= self.thresh {
                    self.trigger();
                }
            }
            ProjectStep::Normal | ProjectStep::ProcessTerminated { .. } | ProjectStep::Error { .. } | ProjectStep::Watcher { .. } => self.count = 0,
        }
    }
    pub fn trigger(&mut self) {
        self.count = 0;
        self.action.as_mut()();
    }
}
#[derive(Debug)]
pub enum Input {
    Start,
    Stop,
    KeyDown(KeyCode),
    KeyUp(KeyCode),
}
pub enum ProjectStep<'gc, C: CustomTypes<S>, S: System<C>> {
    Idle,
    Yield,
    Normal,
    ProcessTerminated { result: Option<Value<'gc, C, S>>, proc: Process<'gc, C, S> },
    Error { error: ExecError<C, S>, proc: Process<'gc, C, S> },
    Watcher { create: bool, watcher: Watcher<'gc, C, S> },
    Pause,
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
struct Script<'gc, C: CustomTypes<S>, S: System<C>> {
    #[collect(require_static)] event: Rc<(Event, usize)>, entity: Gc<'gc, RefLock<Entity<'gc, C, S>>>,
    #[collect(require_static)] process: Option<ProcessKey>,
                               context_queue: VecDeque<ProcContext<'gc, C, S>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Script<'gc, C, S> {
    fn consume_context(&mut self, state: &mut State<'gc, C, S>) {
        let process = self.process.and_then(|key| Some((key, state.processes.get_mut(key)?)));
        if process.as_ref().map(|x| x.1.is_running()).unwrap_or(false) { return }
        let context = match self.context_queue.pop_front() {
            Some(x) => x,
            None => return,
        };
        match process {
            Some((key, process)) => {
                debug_assert!(!state.process_queue.contains(&key));
                debug_assert_eq!(self.process, Some(key));
                process.initialize(context);
                state.process_queue.push_back(key);
            }
            None => {
                let mut process = Process::new(state.global_context, self.entity, self.event.1);
                process.initialize(context);
                let key = state.processes.insert(process);
                state.process_queue.push_back(key);
                self.process = Some(key);
            }
        }
    }
    fn stop_all(&mut self, state: &mut State<'gc, C, S>) {
        if let Some(process) = self.process.take() {
            state.processes.remove(process);
        }
        self.context_queue.clear();
    }
    fn schedule(&mut self, state: &mut State<'gc, C, S>, context: ProcContext<'gc, C, S>, max_queue: usize) {
        self.context_queue.push_back(context);
        self.consume_context(state);
        if self.context_queue.len() > max_queue {
            self.context_queue.pop_back();
        }
    }
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
struct State<'gc, C: CustomTypes<S>, S: System<C>> {
                               global_context: Gc<'gc, RefLock<GlobalContext<'gc, C, S>>>,
                               processes: SlotMap<ProcessKey, Process<'gc, C, S>>,
    #[collect(require_static)] process_queue: VecDeque<ProcessKey>,
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct Project<'gc, C: CustomTypes<S>, S: System<C>> {
    state: State<'gc, C, S>,
    scripts: Vec<Script<'gc, C, S>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Project<'gc, C, S> {
    pub fn from_init<'a>(mc: &Mutation<'gc>, init_info: &InitInfo, bytecode: Rc<ByteCode>, settings: Settings, system: Rc<S>) -> Self {
        let global_context = GlobalContext::from_init(mc, init_info, bytecode, settings, system);
        let mut project = Self::new(Gc::new(mc, RefLock::new(global_context)));
        for entity_info in init_info.entities.iter() {
            let entity = *project.state.global_context.borrow().entities.get(&entity_info.name).unwrap();
            for (event, pos) in entity_info.scripts.iter() {
                project.add_script(*pos, entity, Some(event.clone()));
            }
        }
        project
    }
    pub fn new(global_context: Gc<'gc, RefLock<GlobalContext<'gc, C, S>>>) -> Self {
        Self {
            state: State {
                global_context,
                processes: Default::default(),
                process_queue: Default::default(),
            },
            scripts: Default::default(),
        }
    }
    pub fn add_script(&mut self, start_pos: usize, entity: Gc<'gc, RefLock<Entity<'gc, C, S>>>, event: Option<Event>) {
        match event {
            Some(event) => self.scripts.push(Script {
                event: Rc::new((event, start_pos)),
                entity,
                process: None,
                context_queue: Default::default(),
            }),
            None => {
                let process = Process::new(self.state.global_context, entity, start_pos);
                let key = self.state.processes.insert(process);
                self.state.process_queue.push_back(key);
            }
        }
    }
    pub fn input(&mut self, input: Input) {
        match input {
            Input::Start => {
                for script in self.scripts.iter_mut() {
                    if let Event::OnFlag = &script.event.0 {
                        script.stop_all(&mut self.state);
                        script.schedule(&mut self.state, Default::default(), 0);
                    }
                }
            }
            Input::Stop => {
                self.state.processes.clear();
                self.state.process_queue.clear();
            }
            Input::KeyDown(input_key) => {
                for script in self.scripts.iter_mut() {
                    if let Event::OnKey { key_filter } = &script.event.0 {
                        if key_filter.map(|x| x == input_key).unwrap_or(true) {
                            script.schedule(&mut self.state, Default::default(), 0);
                        }
                    }
                }
            }
            Input::KeyUp(_) => unimplemented!(),
        }
    }
    pub fn step(&mut self, mc: &Mutation<'gc>) -> ProjectStep<'gc, C, S> {
        let msg = self.state.global_context.borrow().system.receive_message();
        if let Some(IncomingMessage { msg_type, values, reply_key }) = msg {
            let values: BTreeMap<_,_> = values.into_iter().collect();
            for script in self.scripts.iter_mut() {
                if let Event::NetworkMessage { msg_type: script_msg_type, fields } = &script.event.0 {
                    if msg_type == *script_msg_type {
                        let mut locals = SymbolTable::default();
                        for field in fields.iter() {
                            locals.define_or_redefine(field,
                                values.get(field).and_then(|x| Value::from_json(mc, x.clone()).ok())
                                .unwrap_or_else(|| Number::new(0.0).unwrap().into()).into());
                        }
                        script.schedule(&mut self.state, ProcContext { locals, barrier: None, reply_key: reply_key.clone(), local_message: None }, usize::MAX);
                    }
                }
            }
        }
        let (proc_key, proc) = loop {
            match self.state.process_queue.pop_front() {
                None => return ProjectStep::Idle,
                Some(proc_key) => if let Some(proc) = self.state.processes.get_mut(proc_key) { break (proc_key, proc) }
            }
        };
        match proc.step(mc) {
            Ok(x) => match x {
                ProcessStep::Normal => {
                    self.state.process_queue.push_front(proc_key);
                    ProjectStep::Normal
                }
                ProcessStep::Yield => {
                    self.state.process_queue.push_back(proc_key);
                    ProjectStep::Yield
                }
                ProcessStep::Watcher { create, watcher } => {
                    self.state.process_queue.push_front(proc_key);
                    ProjectStep::Watcher { create, watcher }
                }
                ProcessStep::Pause => {
                    self.state.process_queue.push_front(proc_key);
                    ProjectStep::Pause
                }
                ProcessStep::Fork { pos, locals, entity } => {
                    let mut proc = Process::new(self.state.global_context, entity, pos);
                    proc.initialize(ProcContext { locals, barrier: None, reply_key: None, local_message: None });
                    let fork_proc_key = self.state.processes.insert(proc);
                    self.state.process_queue.push_back(fork_proc_key); self.state.process_queue.push_front(proc_key); ProjectStep::Normal
                }
                ProcessStep::CreatedClone { new_entity } => {
                    let root = new_entity.borrow().root.unwrap();
                    let mut new_scripts = vec![];
                    for script in self.scripts.iter() {
                        if Gc::ptr_eq(script.entity, root) {
                            new_scripts.push(Script {
                                event: script.event.clone(),
                                entity: new_entity,
                                process: None,
                                context_queue: Default::default(),
                            });
                        }
                    }
                    for script in new_scripts.iter_mut() {
                        if let Event::OnClone = &script.event.0 {
                            script.schedule(&mut self.state, ProcContext::default(), 0);
                        }
                    }
                    self.scripts.extend(new_scripts);
                    self.state.process_queue.push_front(proc_key); ProjectStep::Normal
                }
                ProcessStep::Broadcast { msg_type, barrier, targets } => {
                    for script in self.scripts.iter_mut() {
                        if let Event::LocalMessage { msg_type: recv_type } = &script.event.0 {
                            if recv_type.as_ref().map(|x| *x == *msg_type).unwrap_or(true) {
                                if let Some(targets) = &targets {
                                    if !targets.iter().any(|&target| Gc::ptr_eq(script.entity, target)) {
                                        continue
                                    }
                                }
                                script.stop_all(&mut self.state);
                                script.schedule(&mut self.state, ProcContext { locals: Default::default(), barrier: barrier.clone(), reply_key: None, local_message: Some(msg_type.clone()) }, 0);
                            }
                        }
                    }
                    self.state.process_queue.push_front(proc_key); ProjectStep::Normal
                }
                ProcessStep::Terminate { result } => ProjectStep::ProcessTerminated { result, proc: self.state.processes.remove(proc_key).unwrap() },
                ProcessStep::Idle => unreachable!(),
            }
            Err(error) => ProjectStep::Error { error, proc: self.state.processes.remove(proc_key).unwrap() },
        }
    }
    pub fn get_global_context(&self) -> Gc<'gc, RefLock<GlobalContext<'gc, C, S>>> {
        self.state.global_context
    }
}