use crate::components::{Blocking, Collision, Position, Velocity, World};
use crate::game::MyGame;

use nalgebra::{Isometry2, Vector2};
use specs::{Join, RunNow};

struct Collide;

impl<'a> specs::System<'a> for Collide {
    type SystemData = (
        specs::Entities<'a>,
        specs::ReadStorage<'a, Position>,
        specs::ReadStorage<'a, Velocity>,
        specs::ReadStorage<'a, Blocking>,
        specs::WriteStorage<'a, Collision>,
        specs::WriteExpect<'a, World>,
    );

    fn run(
        &mut self,
        (entities, position, velocity, blocking, mut collisions, mut w): Self::SystemData,
    ) {
        let _: Vec<()> = (&entities, &position, &blocking)
            .join()
            .map(|(e, pos, bl)| {
                let np = if let Some(vel) = velocity.get(e) {
                    pos.moved(vel)
                } else {
                    pos.clone()
                };
                let obj = w.get_mut(bl.handle).unwrap();
                obj.set_position(Isometry2::new(Vector2::new(np.x, np.y), nalgebra::zero()));
            })
            .collect();
        w.update();
        for ev in w.proximity_events() {
            if ev.new_status == ncollide2d::query::Proximity::Intersecting {
                let e = w.collision_object(ev.collider1).unwrap().data();
                collisions.get_mut(*e).map(|r| r.has_collision = true);
                let e = w.collision_object(ev.collider2).unwrap().data();
                collisions.get_mut(*e).map(|r| r.has_collision = true);
            } else {
                let e = w.collision_object(ev.collider1).unwrap().data();
                collisions.get_mut(*e).map(|r| r.has_collision = false);
                let e = w.collision_object(ev.collider2).unwrap().data();
                collisions.get_mut(*e).map(|r| r.has_collision = false);
            }
        }
        w.clear_events();
    }
}

struct Physics;

impl<'a> specs::System<'a> for Physics {
    type SystemData = (
        specs::ReadStorage<'a, Velocity>,
        specs::ReadStorage<'a, Collision>,
        specs::WriteStorage<'a, Position>,
    );

    fn run(&mut self, (velocity, collision, mut position): Self::SystemData) {
        for (vel, col, pos) in (&velocity, &collision, &mut position).join() {
            if !col.has_collision {
                pos.x += vel.dx;
                pos.y += vel.dy;
            }
        }
    }
}

pub fn systems(game: &mut MyGame) {
    Collide.run_now(&game.world);
    Physics.run_now(&game.world);
}
