use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector2, Vector3, vec3, vec4};

use crate::game::player::PlayerMovementMode::{FirstPerson, Flying};

use crate::input::InputState;
use crate::util::intersection_distance;
use crate::vulkan::gameobject::GameObject;
use crate::vulkan::{
    gameobject::Updatable,
    VulkanRenderer
};

use super::GameState;

pub struct Camera {
    pub fov_y: f32,
    pub position: Vector3<f32>,
    pub rotation: Quaternion<f32>,
    pub view: Matrix4<f32>,
    pub proj: Matrix4<f32>,
    pub ortho_proj: Matrix4<f32>,
}

impl Camera {
    pub fn new() -> Camera {
        Camera {
            fov_y: 45.0,
            position: vec3(0.0, 0.0, 0.0),
            rotation: Quaternion::one(),
            view: Matrix4::identity(),
            proj: Matrix4::identity(),
            ortho_proj: Matrix4::identity(),
        }
    }

    pub fn update(&mut self, renderer: &mut VulkanRenderer) {
        // Don't let floating point error accumulate in rotation
        debug_assert!(f32::abs(1.0 - self.rotation.magnitude()) < 0.00001 as f32);
        self.rotation = self.rotation.normalize();

        // Create matrices
        self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(self.position * -1.);

        let width = renderer.game_data.dimensions[0] as f32;
        let height = renderer.game_data.dimensions[1] as f32;

        self.proj = cgmath::perspective(
            Rad::from(Deg(self.fov_y)),
            width / height,
            0.1,
            100.0
        );
        self.proj.y.y *= -1.0;

        self.ortho_proj = cgmath::ortho(0., width, 0., height, -1., 1.);
    }

    pub fn viewport_pos_to_ray_direction(&self, viewport_pos: Vector2<f64>, viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> {
        let normalized_x = 2. * (viewport_pos.x as f32 / viewport_dimensions[0] as f32) - 1.;
        let normalized_y = 2. * (viewport_pos.y as f32 / viewport_dimensions[1] as f32) - 1.;

        let click_start_screen = vec4(normalized_x, normalized_y, -1.0, 1.0);
        let click_end_screen = vec4(normalized_x, normalized_y, 0.0, 1.0);

        let proj_inverse = self.proj.invert()?;
        let view_inverse = self.view.invert()?;

        let mut ray_start_camera = proj_inverse * click_start_screen;
        ray_start_camera /= ray_start_camera.w;

        let mut ray_end_camera = proj_inverse * click_end_screen;
        ray_end_camera /= ray_end_camera.w;

        let mut ray_start_world = view_inverse * ray_start_camera;
        ray_start_world /= ray_start_world.w;

        let mut ray_end_world = view_inverse * ray_end_camera;
        ray_end_world /= ray_end_world.w;

        let ray_dir_world = (ray_end_world - ray_start_world).normalize();

        Some(ray_dir_world.truncate())
    }

    pub fn local_to_world(&self, vec:Vector3<f32>) -> Vector3<f32> {
        self.rotation.invert().rotate_vector(vec)
    }

    pub fn _world_to_local(&self, vec:Vector3<f32>) -> Vector3<f32> {
        self.rotation.rotate_vector(vec)
    }
}

#[derive(PartialEq)]
pub enum PlayerMovementMode {
    FirstPerson,
    Flying,
}

pub struct Player {
    pub camera: Camera,
    pub movement_mode: PlayerMovementMode,
    pub movement_speed: f32,
    pub look_sensitivity: f32,
    pub height: f32,
    pub x_look: f32,
    pub y_look: f32
}

impl Player {
    pub fn new(movement_speed: f32, look_sensitivity: f32) -> Player {
        Player {
            camera: Camera::new(),
            movement_mode: Flying,
            movement_speed,
            look_sensitivity,
            height: 1.0,
            x_look: 0.0,
            y_look: 0.0
        }
    }
}

impl Updatable for Player {
    fn update(&mut self, delta_time: f32, input: &InputState, _game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer) {
        // Edit mode
        if input.button_just_pressed("toggle_edit") {
            if self.movement_mode == FirstPerson {
                self.movement_mode = Flying;
            } else {
                self.movement_mode = FirstPerson;
            }
        }

        if input.button_just_pressed("select") {
            let ray_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap();
            for game_object in game_objects {
                let mesh = &renderer.game_data.meshes[game_object.mesh_index];
                if let Some(_) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) {
                    game_object.is_selected = !game_object.is_selected;
                }
            }
        }

        // Rotation
        self.x_look += input.get_axis_timescaled("look_vertical", delta_time) * self.look_sensitivity;
        self.y_look += input.get_axis_timescaled("look_horizontal", delta_time) * self.look_sensitivity;
        let x_rot = Quaternion::from_angle_x(Deg(self.x_look));
        let y_rot = Quaternion::from_angle_y(Deg(self.y_look));
        self.camera.rotation = x_rot * y_rot;

        // Movement
        let local_input = vec3(input.get_axis_timescaled("move_right", delta_time), 0.0, -input.get_axis_timescaled("move_forward", delta_time)) * self.movement_speed;

        if self.movement_mode == FirstPerson {
            let mut world_input = self.camera.local_to_world(local_input);
            world_input.y = 0.0;
            self.camera.position += world_input;
        } else if self.movement_mode == Flying {
            self.camera.position += self.camera.local_to_world(local_input);
        }

        // Update
        self.camera.update(renderer);
    }
}
