//! This module handles the physics of things and how they interact
//! (and also collisions)

use std::collections::HashMap;
use ulid::Ulid;

/// The angle the physics system assumes the orthogonal view is looking at.
/// This is *not* used by the physics system for anything except
/// converting its internal position to a 2d position understandable by other systems.
// Valid angles are [0.0, pi/2).
// 0.0 means the viewer is looking straight at the ground, so z position has no effect.
// pi/2 means the view is looking *parallel* to the ground, which is not a valid angle for looking at objects
// (the orthogonal approximation only works when approximating an infinitely distant viewer, so looking at angle pi/2
// from the ground means that to see an object, its z must be at *least* infinity, which isn't possible)
pub const VIEW_ANGLE: f32 = std::f32::consts::FRAC_PI_4;

/// An entity that has been marked to be interactable by the physics system
//
// None - the corresponding internal structures have not been made to identify this object
// Some(id) - the internal structures that identify this object are stored under/with `id`
#[derive(Debug)]
pub struct PhysicalObject(Option<Ulid>);

pub struct PhysicsSystem {}

impl PhysicsSystem {
    pub fn new() -> Self {
        Self {}
    }
}

/// A description of the state of physical objects
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PhysicsState {
    objects: HashMap<Ulid, PhysicsObject>,
}

impl PhysicsState {
    /// linearly interpolate between two physics states
    ///
    /// `alpha` must be [0.0, 1.0] for the math to work out
    pub fn lerp(&self, from: &Self, delta: f32) -> Self {
        let both_exist = self
            .objects
            .iter()
            .filter_map(|(id, oobj)| from.objects.get(id).map(|obj| (id, oobj, obj)));
        let only_self = self
            .objects
            .iter()
            .filter(|(id, _)| !from.objects.contains_key(*id));

        let mut new_objects = HashMap::new();
        // Lerp objects that exist in both states
        for (id, oobj, obj) in both_exist {
            new_objects.insert(*id, oobj.lerp(obj, delta));
        }
        // Copy objects that exist in only the current (self) state
        for (id, obj) in only_self {
            new_objects.insert(*id, obj.clone());
        }

        Self {
            objects: new_objects,
        }
    }
}

/// Physical description of a physics object
#[derive(Debug, Clone, PartialEq)]
pub struct PhysicsObject {
    position: glam::Vec3,
}

impl PhysicsObject {
    fn lerp(&self, from: &Self, delta: f32) -> Self {
        let position = self.position * delta + from.position * (1.0 - delta);
        Self { position }
    }

    /// Get the position
    // Maps our internal 3d position into a 2d displayable position
    fn position(&self) -> glam::Vec2 {
        // Physics is a 3d system.
        // to get the display (x, y), use
        // (x, y - z * VIEW_ANGLE.tan())

        glam::Vec2::new(
            self.position.x,
            self.position.y - VIEW_ANGLE.tan() * self.position.z,
        )
    }
}

#[cfg(test)]
mod tests;