//   Copyright 2015 Colin Sherratt
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.


use std::collections::HashMap;
use {Signal, ArmedSignal, Pulse, Waiting, Barrier, Signals};

pub struct Inner {
    pub ready: Vec<usize>,
    pub trigger: Option<Pulse>,
}

pub struct Handle(pub Arc<Mutex<Inner>>);

/// A `Select` listens to 1 or more signals. It will wait until
/// any signal becomes available before Pulsing. `Select` will then
/// return the `Signal` that has been `Pulsed`. `Select` has no defined
/// ordering of events for `Signal`s when there are more then one `Signals`
/// pending.
pub struct Select {
    inner: Arc<Mutex<Inner>>,
    signals: HashMap<usize, ArmedSignal>,
}

impl Select {
    /// Create a new empty `Select`
    pub fn new() -> Select {
        Select {
            inner: Arc::new(Mutex::new(Inner {
                ready: Vec::new(),
                trigger: None,
            })),
            signals: HashMap::new(),
        }
    }

    /// Add a signal to the `Select`, a unique id that is associated
    /// With the signal is returned. This can be used to remove the
    /// signal from the `Select` or to lookup the `Pulse` when it fires.
    pub fn add(&mut self, pulse: Signal) -> usize {
        let id = pulse.id();
        let p = pulse.arm(Waiting::select(Handle(self.inner.clone())));
        self.signals.insert(id, p);
        id
    }

    /// Remove a `Signal1 from the `Select` using it's unique id.
    pub fn remove(&mut self, id: usize) -> Option<Signal> {
        self.signals
            .remove(&id)
            .map(|x| x.disarm())
    }

    /// Convert all the signals present in the `Select` into a `Barrier`
    pub fn into_barrier(self) -> Barrier {
        let vec: Vec<Signal> = self.signals
                                   .into_iter()
                                   .map(|(_, p)| p.disarm())
                                   .collect();

        Barrier::new(&vec)
    }

    /// This is a non-blocking attempt to get a `Signal` from a `Select`
    /// this will return a `Some(Signal)` if there is a pending `Signal`
    /// in the select. Otherwise it will return `None`
    pub fn try_next(&mut self) -> Option<Signal> {
        let mut guard = self.inner.lock().unwrap();
        if let Some(x) = guard.ready.pop() {
            return Some(self.signals.remove(&x).map(|x| x.disarm()).unwrap());
        }
        None
    }

    /// Get the number of Signals being watched
    pub fn len(&self) -> usize {
        self.signals.len()
    }
}

impl Iterator for Select {
    type Item = Signal;

    fn next(&mut self) -> Option<Signal> {
        loop {
            if self.signals.len() == 0 {
                return None;
            }

            let pulse = {
                let mut guard = self.inner.lock().unwrap();
                while let Some(x) = guard.ready.pop() {
                    if let Some(x) = self.signals.remove(&x) {
                        return Some(x.disarm());
                    }
                }
                let (pulse, t) = Signal::new();
                guard.trigger = Some(t);
                pulse
            };
            pulse.wait().unwrap();
        }
    }
}

impl Signals for Select {
    fn signal(&self) -> Signal {
        let (pulse, t) = Signal::new();
        let mut guard = self.inner.lock().unwrap();
        if guard.ready.len() == 0 {
            guard.trigger = Some(t);
        } else {
            t.pulse();
        }
        pulse
    }
}

/// `SelectMap` is a wrapper around a `Select` rather then use
/// a unique id to find out what signal has been asserts, `SelectMap`
/// will return an supplied object.
pub struct SelectMap<T> {
    select: Select,
    items: HashMap<usize, T>,
}

impl<T> SelectMap<T> {
    /// Create a new empty `SelectMap`
    pub fn new() -> SelectMap<T> {
        SelectMap {
            select: Select::new(),
            items: HashMap::new(),
        }
    }

    /// Add a `Signal` and an associated value into the `SelectMap`
    pub fn add(&mut self, signal: Signal, value: T) {
        let id = self.select.add(signal);
        self.items.insert(id, value);
    }

    /// This is a non-blocking attempt to get a `Signal` from a `SelectMap`
    /// this will return a `Some((Signal, T))` if there is a pending `Signal`
    /// in the select. Otherwise it will return `None`
    pub fn try_next(&mut self) -> Option<(Signal, T)> {
        self.select.try_next().map(|x| {
            let id = x.id();
            (x, self.items.remove(&id).unwrap())
        })
    }

    /// Get the number of items in the `SelectMap`
    pub fn len(&self) -> usize {
        self.items.len()
    }
}

impl<T> Iterator for SelectMap<T> {
    type Item = (Signal, T);

    fn next(&mut self) -> Option<(Signal, T)> {
        self.select.next().map(|x| {
            let id = x.id();
            (x, self.items.remove(&id).unwrap())
        })
    }
}

impl<T> Signals for SelectMap<T> {
    fn signal(&self) -> Signal {
        self.select.signal()
    }
}
use std::sync::{Arc, Mutex};