use macroquad::prelude::*;
use std::any::Any;

mod drone_cam;
pub use drone_cam::AttachedCamera;

use crate::engine::World;

const MOVE_SPEED: f32 = 0.1;
const LOOK_SPEED: f32 = 0.05;

pub trait CameraController {
    fn update(&mut self, delta: f32, world: &World);

    fn apply(&self);

    fn get_position(&self) -> Vec3;
    fn get_front(&self) -> Vec3;

    fn as_any(&self) -> &dyn Any;
    fn as_mut_any(&mut self) -> &mut dyn Any;
}

impl CameraController for FirstPersonCamera {
    fn update(&mut self, delta: f32, _world: &World) {
        if is_key_pressed(KeyCode::Tab) {
            self.grabbed = !self.grabbed;
            set_cursor_grab(self.grabbed);
            show_mouse(!self.grabbed);
        }

        self.handle_keyboard();
        self.handle_mouse(delta);
    }

    fn apply(&self) {
        set_camera(&Camera3D {
            position: self.position,
            up: self.up,
            target: self.position + self.front,
            ..Default::default()
        });
    }

    fn get_front(&self) -> Vec3 {
        return self.front;
    }
    fn get_position(&self) -> Vec3 {
        return self.position;
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_mut_any(&mut self) -> &mut dyn Any {
        self
    }
}

pub struct FirstPersonCamera {
    pub position: Vec3,
    yaw: f32,
    pitch: f32,

    pub front: Vec3,
    pub right: Vec3,
    pub up: Vec3,
    world_up: Vec3,

    grabbed: bool,
    last_mouse: Vec2,
}

impl FirstPersonCamera {
    pub fn new(position: Vec3) -> Self {
        let yaw: f32 = 0.0;
        let pitch: f32 = -0.25;
        let world_up = vec3(0.0, 1.0, 0.0);

        let front = vec3(
            yaw.cos() * pitch.cos(),
            pitch.sin(),
            yaw.sin() * pitch.cos(),
        )
        .normalize();

        let right = front.cross(world_up).normalize();
        let up = right.cross(front).normalize();

        let grabbed = true;
        set_cursor_grab(grabbed);
        show_mouse(!grabbed);

        Self {
            position,
            yaw,
            pitch,
            front,
            right,
            up,
            world_up,
            grabbed,
            last_mouse: mouse_position().into(),
        }
    }

    pub fn update_vectors(&mut self) {
        self.front = vec3(
            self.yaw.cos() * self.pitch.cos(),
            self.pitch.sin(),
            self.yaw.sin() * self.pitch.cos(),
        )
        .normalize();

        self.right = self.front.cross(self.world_up).normalize();
        self.up = self.right.cross(self.front).normalize();
    }

    pub fn handle_mouse(&mut self, delta: f32) {
        let mouse: Vec2 = mouse_position().into();
        let mouse_delta = mouse - self.last_mouse;
        self.last_mouse = mouse;

        if !self.grabbed {
            return;
        }

        self.yaw += mouse_delta.x * delta * LOOK_SPEED;
        self.pitch -= mouse_delta.y * delta * LOOK_SPEED;

        // clamp pitch
        self.pitch = self.pitch.clamp(-1.5, 1.5);

        self.update_vectors();
    }

    pub fn handle_keyboard(&mut self) {
        if is_key_down(KeyCode::W) {
            self.position += self.front * MOVE_SPEED;
        }
        if is_key_down(KeyCode::S) {
            self.position -= self.front * MOVE_SPEED;
        }
        if is_key_down(KeyCode::A) {
            self.position -= self.right * MOVE_SPEED;
        }
        if is_key_down(KeyCode::D) {
            self.position += self.right * MOVE_SPEED;
        }
        if is_key_down(KeyCode::LeftShift) {
            self.position += Vec3::Y * MOVE_SPEED;
        }
        if is_key_down(KeyCode::LeftControl) {
            self.position -= Vec3::Y * MOVE_SPEED;
        }
    }
}
