use specs::{World, System, Dispatcher, DispatcherBuilder};


use super::{SpecsBundle, Result};


pub struct SpecsBundler<'world, 'a, 'b> {
    world: &'world mut World,
    dispatcher_builder: DispatcherBuilder<'a, 'b>,
}

impl<'world, 'a, 'b> SpecsBundler<'world, 'a, 'b> {

    #[inline(always)]
    pub fn new(world: &'world mut World, dispatcher_builder: DispatcherBuilder<'a, 'b>) -> Self {
        SpecsBundler {
            world: world,
            dispatcher_builder: dispatcher_builder,
        }
    }

    #[inline]
    pub fn bundle<B>(mut self, args: B::Args) -> Result<Self>
        where B: SpecsBundle<'a, 'b>,
    {
        self.dispatcher_builder = B::build(self.world, self.dispatcher_builder, args)?;
        Ok(self)
    }

    #[inline]
    pub fn add<T>(mut self, system: T, name: &str, dep: &[&str]) -> Self
        where T: for<'c> System<'c> + Send + 'a,
    {
        self.dispatcher_builder = self.dispatcher_builder.add(system, name, dep);
        self
    }
    #[inline]
    pub fn add_thread_local<T>(mut self, system: T) -> Self
        where T: for<'c> System<'c> + 'b,
    {
        self.dispatcher_builder = self.dispatcher_builder.add_thread_local(system);
        self
    }
    #[inline]
    pub fn add_barrier(mut self) -> Self {
        self.dispatcher_builder = self.dispatcher_builder.add_barrier();
        self
    }
    #[inline]
    #[cfg(not(target_os = "emscripten"))]
    pub fn with_pool(mut self, pool: ::std::sync::Arc<::rayon::ThreadPool>) -> Self {
        self.dispatcher_builder = self.dispatcher_builder.with_pool(pool);
        self
    }

    #[inline(always)]
    pub fn dispatcher_builder(self) -> DispatcherBuilder<'a, 'b> {
        self.dispatcher_builder
    }

    #[inline(always)]
    pub fn build(self) -> Dispatcher<'a, 'b> {
        self.dispatcher_builder.build()
    }
}


#[cfg(test)]
mod test {
    use super::*;

    use std::fmt;
    use std::error::Error;


    #[derive(Debug)]
    struct SimpleError;

    impl Error for SimpleError {
        fn description(&self) -> &str { "Simple Error" }
    }

    impl fmt::Display for SimpleError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{:?}", self)
        }
    }


    struct Simple;

    impl<'a, 'b> SpecsBundle<'a, 'b> for Simple {
        type Args = bool;

        fn build(
            _: &mut World,
            dispatcher_builder: DispatcherBuilder<'a, 'b>,
            should_panic: Self::Args
        ) -> Result<DispatcherBuilder<'a, 'b>> {
            if should_panic {
                Err(Box::new(SimpleError))
            } else {
                Ok(dispatcher_builder)
            }
        }
    }

    fn bundle<'a, 'b>(
        world: &mut World,
        dispatcher_builder: DispatcherBuilder<'a, 'b>,
        should_panic: bool
    ) -> Result<Dispatcher<'a, 'b>> {
        let dispatcher_builder = SpecsBundler::new(world, dispatcher_builder)
            .bundle::<Simple>(should_panic)?
            .dispatcher_builder();

        Ok(dispatcher_builder.build())
    }


    #[test]
    fn test_bundler() {
        let mut world = World::new();
        let dispatcher_builder = DispatcherBuilder::new();
        let _dispatcher = bundle(&mut world, dispatcher_builder, false).expect("should not fail");
    }

    #[should_panic]
    #[test]
    fn test_bundler_fail() {
        let mut world = World::new();
        let dispatcher_builder = DispatcherBuilder::new();
        let _dispatcher = bundle(&mut world, dispatcher_builder, true).expect("should fail");
    }
}
