use super::*;

pub struct ParallaxPlugin;

impl Plugin for ParallaxPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (
                move_parallax_items.run_if(any_component_changed::<Transform>),
                wrap_parallax_items.run_if(any_component_changed::<ViewVisibility>),
            ),
        );
    }
}

///
/// ParallaxDepth describes how far from the camera something is
///
/// A parallax depth of 1 means it moves 1:1 with camera movement;
/// If the camera moves 1 pixel to the left, the background moves 1px to the right
///
/// A parallax depth of 2 means the movement is 1:2
/// Camera moves 2px, the element moves 1px
///
#[derive(Component)]
pub struct ParallaxDepth(pub f32);

fn move_parallax_items(
    mut q: Query<(&mut Transform, &ParallaxDepth), Without<Camera2d>>,
    camera: Single<&Transform, (With<Camera2d>, Changed<Transform>)>,
    mut prev_camera_pos: Local<Vec2>,
) {
    // Unpack the camera data
    let cam_t = *camera;

    // Calculate how far the camera moved since the last update
    let delta = cam_t.translation.truncate() - *prev_camera_pos;
    debug!("Cam Delta: {:?}", delta);

    // For each object
    q.iter_mut().for_each(|(mut t, pd)| {
        // Update ParallaxPosition
        let depth_movement = delta / pd.0;

        debug!("Depth: {:?} | Move: {:?}", pd.0, depth_movement);

        // Update actual position
        t.translation.x -= depth_movement.x;
        t.translation.y -= depth_movement.y;
    });

    *prev_camera_pos = cam_t.translation.truncate();
}

fn wrap_parallax_items(
    mut items: Query<
        (&mut Transform, &GlobalTransform, &ViewVisibility, &Mesh2d),
        (
            With<ParallaxDepth>,
            Without<Camera2d>,
            Changed<ViewVisibility>,
        ),
    >,
    camera: Single<(&Camera, &GlobalTransform), With<Camera2d>>,
    window: Single<&Window>,
    meshes: Res<Assets<Mesh>>,
) {
    if !items.is_empty() {
        let (cam, cam_gt) = *camera;

        // get the window size in world space
        let window_size_in_world_space = {
            let top_left = cam.viewport_to_world_2d(cam_gt, Vec2::ZERO).unwrap();
            let bottom_right = cam.viewport_to_world_2d(cam_gt, window.size()).unwrap();

            Vec2::abs(Vec2::new(
                bottom_right.x - top_left.x,
                bottom_right.y - top_left.y,
            ))
        };

        // for each item in the paralax items
        items.iter_mut().for_each(|(mut t, gt, v, Mesh2d(m))| {
            if !v.get() {
                debug!("Item is not visible");
                // Get the total size (window + scale)
                let half_extents = {
                    let Aabb { half_extents, .. } = meshes.get(m).unwrap().compute_aabb().unwrap();
                    half_extents.truncate()
                };
                let object_size = t.scale.truncate() * 2.0;
                let total_size = window_size_in_world_space + (object_size * half_extents);
                debug!("Sizes:\n\twindow {window_size_in_world_space}\n\tobject size: {object_size}\n\tAabb Half extents: {half_extents}\n\tTotal size: {total_size}");

                // Double check that item is out of bounds
                let bounds_check = Vec2::abs(cam_gt.translation().truncate() - gt.translation().truncate());
                let out_of_bounds = (total_size / 2.0) - bounds_check;
                debug!("Bounds check {bounds_check} | Out of bounds: {out_of_bounds:?}");

                debug!("Starting position: {:?}", t.translation);
                if out_of_bounds.x < 0.0 {
                    debug!("Object is actually out of bounds horizontally");
                    // determine if should move to the left or right relative to camera
                    let move_right = cam_gt.translation().x > gt.translation().x;

                    // move left or right
                    if move_right {
                        t.translation.x += total_size.x;
                    } else {
                        t.translation.x -= total_size.x;
                    }
                }
                if out_of_bounds.y < 0.0 {
                    debug!("Object is actually out of bounds vertically");
                    let move_up = cam_gt.translation().y > gt.translation().y;

                    // move up or down
                    if move_up {
                        t.translation.y += total_size.y;
                    } else {
                        t.translation.y -= total_size.y;
                    }
                }
                debug!("Moved to {:?}", t.translation);
            }
        });
    }
}
