extern crate number_traits;
extern crate specs_transform;
extern crate specs;
extern crate specs_bundler;


use number_traits::Float;
use specs_transform::{TransformBundle, Child, Transform2D, Transform3D, LocalTransform2D, LocalTransform3D};
use specs::{World, DispatcherBuilder, Join, WriteStorage, System};
use specs_bundler::SpecsBundler;

use std::marker::PhantomData;


pub struct TestSystem<T: 'static + Sync + Send + Copy + Float>(PhantomData<T>);


impl<T: 'static + Sync + Send + Copy + Float> TestSystem<T> {
    #[inline(always)]
    pub fn new() -> Self {
        TestSystem(PhantomData)
    }
}


impl<'a, T: 'static + Sync + Send + Copy + Float> System<'a> for TestSystem<T> {
    type SystemData = (
        WriteStorage<'a, LocalTransform2D<T>>,
        WriteStorage<'a, LocalTransform3D<T>>
    );

    fn run(&mut self, (mut locals_2d, mut locals_3d): Self::SystemData) {
        for local in (&mut locals_2d).join() {
            local.position[0] += T::one();
            local.position[1] += T::one();
        }
        for local in (&mut locals_3d).join() {
            local.position[0] += T::one();
            local.position[1] += T::one();
            local.position[2] += T::one();
        }
    }
}


#[test]
fn test_parent_child_2d() {
    let mut world = World::new();

    let dispatcher_builder = SpecsBundler::new(&mut world, DispatcherBuilder::new())
        .bundle::<TransformBundle<f32>>(()).unwrap()
        .dispatcher_builder();

    let mut dispatcher = dispatcher_builder
        .add(TestSystem::<f32>::new(), "test_system", &[TransformBundle::<f32>::system_name().as_ref()])
        .build();

    let parent = world.create_entity()
        .with(LocalTransform2D::<f32>::new())
        .with(Transform2D::<f32>::new())
        .build();

    let _ = world.create_entity()
        .with(Child::new(parent))
        .with(LocalTransform2D::<f32>::new())
        .with(Transform2D::<f32>::new())
        .build();

    dispatcher.dispatch(&mut world.res); // first frame init
    dispatcher.dispatch(&mut world.res);

    let globals_read = world.read::<Transform2D<f32>>();
    let globals: Vec<&Transform2D<f32>> = (&globals_read).join().collect();

    assert_eq!(&globals[1].0, &[1.0, 0.0, 0.0, 1.0, 2.0, 2.0]);
}

#[test]
fn test_parent_child_3d() {
    let mut world = World::new();

    let dispatcher_builder = SpecsBundler::new(&mut world, DispatcherBuilder::new())
        .bundle::<TransformBundle<f32>>(()).unwrap()
        .dispatcher_builder();

    let mut dispatcher = dispatcher_builder
        .add(TestSystem::<f32>::new(), "test_system", &[TransformBundle::<f32>::system_name().as_ref()])
        .build();

    let parent = world.create_entity()
        .with(LocalTransform3D::<f32>::new())
        .with(Transform3D::<f32>::new())
        .build();

    let _ = world.create_entity()
        .with(Child::new(parent))
        .with(LocalTransform3D::<f32>::new())
        .with(Transform3D::<f32>::new())
        .build();

    dispatcher.dispatch(&mut world.res); // first frame init
    dispatcher.dispatch(&mut world.res);

    let globals_read = world.read::<Transform3D<f32>>();
    let globals: Vec<&Transform3D<f32>> = (&globals_read).join().collect();

    assert_eq!(&globals[1].0, &[
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        2.0, 2.0, 2.0, 1.0,
    ]);
}

#[test]
fn test_3d_parent_2d_child() {
    let mut world = World::new();

    let dispatcher_builder = SpecsBundler::new(&mut world, DispatcherBuilder::new())
        .bundle::<TransformBundle<f32>>(()).unwrap()
        .dispatcher_builder();

    let mut dispatcher = dispatcher_builder
        .add(TestSystem::<f32>::new(), "test_system", &[TransformBundle::<f32>::system_name().as_ref()])
        .build();

    let parent = world.create_entity()
        .with(LocalTransform3D::<f32>::new())
        .with(Transform3D::<f32>::new())
        .build();

    let _ = world.create_entity()
        .with(Child::new(parent))
        .with(LocalTransform2D::<f32>::new())
        .with(Transform2D::<f32>::new())
        .build();

    dispatcher.dispatch(&mut world.res); // first frame init
    dispatcher.dispatch(&mut world.res);

    let globals_read = world.read::<Transform2D<f32>>();
    let globals: Vec<&Transform2D<f32>> = (&globals_read).join().collect();

    assert_eq!(&globals[0].0, &[1.0, 0.0, 0.0, 1.0, 2.0, 2.0]);
}

#[test]
fn test_2d_parent_3d_child() {
    let mut world = World::new();

    let dispatcher_builder = SpecsBundler::new(&mut world, DispatcherBuilder::new())
        .bundle::<TransformBundle<f32>>(()).unwrap()
        .dispatcher_builder();

    let mut dispatcher = dispatcher_builder
        .add(TestSystem::<f32>::new(), "test_system", &[TransformBundle::<f32>::system_name().as_ref()])
        .build();

    let parent = world.create_entity()
        .with(LocalTransform2D::<f32>::new())
        .with(Transform2D::<f32>::new())
        .build();

    let _ = world.create_entity()
        .with(Child::new(parent))
        .with(LocalTransform3D::<f32>::new())
        .with(Transform3D::<f32>::new())
        .build();

    dispatcher.dispatch(&mut world.res); // first frame init
    dispatcher.dispatch(&mut world.res);

    let globals_read = world.read::<Transform3D<f32>>();
    let globals: Vec<&Transform3D<f32>> = (&globals_read).join().collect();

    assert_eq!(&globals[0].0, &[
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        2.0, 2.0, 1.0, 1.0,
    ]);
}
