Documentation
use super::*;

/// `LookAt` struct
///
/// Struct describing a look at camera.
#[derive(Copy, Clone)]
pub struct LookAt {
    /// Position of the camera
    pub pos: Vec3,

    /// Target point of the camera
    pub target: Vec3,

    /// Up vector of the camera
    pub up: Vec3,
}

/// Camera component accessor.
///
/// A camera is represented by an entity with the Camera component.
///
/// It lets you control the local origin and target of the camera and set
/// the perspective mode and fov.
///
/// Call `set_active` or `set_active_for_player` to make the camera the active one.
///
/// An entity with a `Camera` component is special in that the rotation of its
/// `[Transform`] component will be controlled directly by the client of the active player.
/// That is, if you call [`Self::set_active_for_player`] or [`Self::set_active`] the camera will start
/// acting like a first-person camera controlled by mouse movements of the player.
/// If you do not want the player to control the camera rotation,
/// then you need to call `camera_entity.transform().rotation().set(…)` each frame.

pub struct Camera {
    id: Entity,
}

impl std::fmt::Debug for Camera {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Camera")
            .field("entity", &self.id.name())
            .field("local_origin", &self.local_origin().get())
            .field("local_forward_vector", &self.local_forward_vector().get())
            .field("local_up_vector", &self.local_up_vector().get())
            .field("field_of_view", &self.field_of_view().get())
            .field("focal_point", &self.focal_point().get())
            .field("f_stop", &self.f_stop().get())
            .finish()
    }
}

impl Camera {
    /// Creates a camera entity.
    ///
    /// * `name` - Just a name, to keep track of the entity during development.
    pub fn create(name: &str) -> Self {
        let id = World::create_entity(name, ffi::EntityTemplate::Camera);
        Self { id }
    }

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the local position of the camera.
        ///
        /// The default is `Vec3::ZERO`.
        ///
        /// The `Transform` component of the entity will be used to compute
        /// the world-space camera position.
        Camera,
        LocalOrigin,
        Vec3,
        local_origin,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the local forward-vector of the camera.
        ///
        /// The default is `-Vec3::Z`.
        ///
        /// The `Transform` component of the entity will be used to compute
        /// the world-space forward vector.
        Camera,
        LocalForwardVector,
        Vec3,
        local_forward_vector,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the local up vector of the camera.
        ///
        /// The default is `Vec3::Y`.
        ///
        /// The `Transform` component of the entity will be used to compute
        /// the world-space up vector.
        Camera,
        LocalUpVector,
        Vec3,
        local_up_vector,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the field of view of the camera.
        ///
        /// Sets/gets the vertical field of view in degrees.
        /// Defaults to 60 degrees.
        Camera,
        FieldOfView,
        f32,
        field_of_view,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the focal point of the camera.
        ///
        /// Sets/gets the focal point of the camera in normalized distance units. Set this to 0.0 for automatic focusing.
        /// Defaults to 0.0 / automatic focusing.
        ///
        /// note: That this will only have an effect if f-number is also set to >0.
        Camera,
        FocalPoint,
        f32,
        focal_point,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the focal point of the camera.
        ///
        /// Sets/gets the focal point smoothing value of the camera which controls the amount of smoothing to apply to focal point changes.
        /// Where 0.0 is no smoothing and 1.0 is the maximum amount of smoothing.
        /// Defaults to 0.5.
        ///
        /// note: That this will only have an effect if f-number is also set to >0.
        Camera,
        FocalPointSmoothing,
        f32,
        focal_point_smoothing,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the f-number of the camera.
        ///
        /// Sets/gets the scale of the area that is in focus (cone of confusion).
        /// Defaults to 0.0 which means depth of field is disabled
        ///
        /// Note: This parameter will likely change and be replaced with something that is more physical and non-resolution dependent
        Camera,
        FStop,
        f32,
        f_stop,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the screen space 2D offset of the camera.
        ///
        /// Sets/gets an offset in 2D pixel coordinates to shift the viewport by. Useful for centering things
        /// under sidebars without viewports.
        Camera,
        ScreenSpaceOffset,
        Vec2,
        screen_space_offset,
        ValueAccessorReadWriteAnimate
    );

    /// Currently unimplemented
    ///
    /// Sets the projection mode of the camera (Perspective/Orthographic).
    ///
    /// Default is Perspective.
    pub fn set_camera_projection_mode(&self, mode: CameraProjectionMode) {
        use ffi::Camera;
        World::set_entity_value(
            self.id,
            ffi::ComponentType::Camera,
            Camera::ProjectionMode.into(),
            &Value::from_i64(mode as i64),
        );
    }

    /// Accessor for the `Transform` component, if it exists.
    pub fn transform(&self) -> Transform {
        Transform::from_entity(self.id)
    }

    /// Sets the look at camera properties using a struct.
    #[deprecated = "Position the `Transform` component instead, e.g. with `camera.transform().set_world_to_entity(Affine3A::look_at_rh(…))`"] // 2021-11-25
    pub fn set_look_at(&self, look_at: &LookAt) {
        self.local_origin().set(look_at.pos);
        self.local_forward_vector()
            .set((look_at.target - look_at.pos).normalize());
        self.local_up_vector().set(look_at.up);
    }

    /// Makes this the active camera.
    ///
    /// Simplified wrapper for `EntityMessenger::set_active`.
    ///
    /// In a multiplayer environment, `set_active` will activate the camera for the host player.
    /// Use `set_active_for_player` for activating a camera for a specific player.
    pub fn set_active(&self) {
        EntityMessenger::get()
            .global_queue()
            .set_active(self.id, ComponentType::Camera);
    }

    /// Makes this the active camera for a specific player.
    ///
    /// Simplified wrapper for `EntityMessenger::set_active_for_player`.
    ///
    /// * `player_id`: this identifier can be retrieved through the Applet API.
    pub fn set_active_for_player(&self, player_id: PlayerId) {
        EntityMessenger::get().global_queue().set_active_for_player(
            self.id,
            ComponentType::Camera,
            player_id,
        );
    }
}

impl_world_component!(Camera);