use specs::world::WorldExt;
use specs::{Component, NullStorage, VecStorage};

pub type World = ncollide2d::world::CollisionWorld<f32, specs::Entity>;

/// Register all the components with the world.
pub fn register(world: &mut specs::World) {
    world.register::<Position>();
    world.register::<Velocity>();
    world.register::<Sprite>();
    world.register::<Background>();
    world.register::<Foreground>();
    world.register::<Decoration>();
    world.register::<Controlled>();
    world.register::<Collision>();
    world.register::<Blocking>();
}

/// The `Position` component represents (in world coordinates) a thing
/// that has a position in the world, measured from the top-left of
/// the thing.
#[derive(Component, Debug, Clone)]
#[storage(VecStorage)]
pub struct Position {
    pub x: f32,
    pub y: f32,
}

impl Position {
    /// Convert a `Position` to a screen point
    pub fn to_point(&self) -> mint::Point2<f32> {
        mint::Point2 {
            x: self.x * 3.0,
            y: self.y * 3.0,
        }
    }

    pub fn to_grid(&self) -> (i32, i32) {
        ((self.x / 24.0) as i32, (self.y / 24.0) as i32)
    }

    pub fn moved(&self, vel: &Velocity) -> Position {
        Position {
            x: self.x + vel.dx,
            y: self.y + vel.dy,
        }
    }
}

/// The `Velocity` componenent is present on any entity that moves
/// through the world, and represents its rate of change per
/// time-unit.
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Velocity {
    pub dx: f32,
    pub dy: f32,
}

/// The `Sprite` components represents the current display location of
/// a sprite in the spritesheet.
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Sprite {
    pub u: u8,
    pub v: u8,
}

impl Sprite {
    /// Convert a `Sprite` into the rectangle that specifies the
    /// sprite location on the spritesheet
    pub fn to_rect(&self) -> ggez::graphics::Rect {
        ggez::graphics::Rect {
            x: (1.0 / 32.0) * self.u as f32,
            y: (1.0 / 32.0) * self.v as f32,
            w: 1.0 / 32.0,
            h: 1.0 / 32.0,
        }
    }
}

/// A drawing-phase component: represents tiles that appear in the
/// background of everything.
#[derive(Component, Default, Debug)]
#[storage(NullStorage)]
pub struct Background;

/// A drawing-phase component: represents tiles which appear in the
/// foreground, possibly entities.
#[derive(Component, Default, Debug)]
#[storage(NullStorage)]
pub struct Foreground;

/// A drawing-phase component: represents tiles which appear on top of
/// everything else, such as the tops of trees or roofs of houses.
#[derive(Component, Default, Debug)]
#[storage(NullStorage)]
pub struct Decoration;

/// A component that represents entities which are controlled by the
/// keyboard.
#[derive(Component, Default, Debug)]
#[storage(NullStorage)]
pub struct Controlled;

/// A component that represents entities which can collide with other
/// things.
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Collision {
    pub has_collision: bool,
}

/// A component which represents things which have a collision shape
/// in the world
#[derive(Component)]
#[storage(VecStorage)]
pub struct Blocking {
    pub handle: ncollide2d::pipeline::CollisionObjectSlabHandle,
}

impl Blocking {
    /// create a `Blocking` component for an entity given a specified shape
    pub fn new_shape<S>(e: specs::Entity, w: &mut World, volume: S) -> Blocking
    where
        S: ncollide2d::shape::Shape<f32>,
    {
        let (handle, _) = w.add(
            nalgebra::geometry::Isometry::identity(),
            ncollide2d::shape::ShapeHandle::new(volume),
            ncollide2d::pipeline::CollisionGroups::new(),
            ncollide2d::pipeline::object::GeometricQueryType::Proximity(0.0),
            e,
        );
        Blocking { handle }
    }

    /// create an 11pxx11px box for an entity
    pub fn new_box(e: specs::Entity, w: &mut World) -> Blocking {
        Blocking::new_shape(
            e,
            w,
            ncollide2d::shape::Cuboid::new(nalgebra::Vector2::new(11.0, 11.0)),
        )
    }

    /// create a 11px ball for an entity
    pub fn new_ball(e: specs::Entity, w: &mut World) -> Blocking {
        Blocking::new_shape(e, w, ncollide2d::shape::Ball::new(11.0))
    }
}
