use alloc::arc::{Arc, Weak};
use collections::{BTreeMap, VecDeque};
use spin::{Mutex, Once, RwLock, RwLockReadGuard, RwLockWriteGuard};

use context;
use syscall::data::Event;

type EventList = Weak<Mutex<VecDeque<Event>>>;

type Registry = BTreeMap<(usize, usize), BTreeMap<(usize, usize), EventList>>;

static REGISTRY: Once<RwLock<Registry>> = Once::new();

/// Initialize registry, called if needed
fn init_registry() -> RwLock<Registry> {
    RwLock::new(Registry::new())
}

/// Get the global schemes list, const
fn registry() -> RwLockReadGuard<'static, Registry> {
    REGISTRY.call_once(init_registry).read()
}

/// Get the global schemes list, mutable
pub fn registry_mut() -> RwLockWriteGuard<'static, Registry> {
    REGISTRY.call_once(init_registry).write()
}

pub fn register(fd: usize, scheme_id: usize, id: usize) -> bool {
    let (context_id, events) = {
        let contexts = context::contexts();
        let context_lock = contexts.current().expect("event::register: No context");
        let context = context_lock.read();
        (context.id, Arc::downgrade(&context.events))
    };

    let mut registry = registry_mut();
    let entry = registry.entry((scheme_id, id)).or_insert_with(|| {
        BTreeMap::new()
    });
    if entry.contains_key(&(context_id, fd)) {
        false
    } else {
        entry.insert((context_id, fd), events);
        true
    }
}

pub fn unregister(fd: usize, scheme_id: usize, id: usize) {
    let mut registry = registry_mut();

    let mut remove = false;
    if let Some(entry) = registry.get_mut(&(scheme_id, id)) {
        entry.remove(&(context::context_id(), fd));

        if entry.is_empty() {
            remove = true;
        }
    }

    if remove {
        registry.remove(&(scheme_id, id));
    }
}

pub fn trigger(scheme_id: usize, id: usize, flags: usize, data: usize) {
    let registry = registry();
    if let Some(event_lists) = registry.get(&(scheme_id, id)) {
        for entry in event_lists.iter() {
            if let Some(event_list_lock) = entry.1.upgrade() {
                let mut event_list = event_list_lock.lock();
                event_list.push_back(Event {
                    id: (entry.0).1,
                    flags: flags,
                    data: data
                });
            }
        }
    }
}