
use std::time::SystemTime;

use cgmath::{Deg, Euler, Quaternion, vec3};
use winit::event::Event;

use level::{load_level, save_level};
use player::Player;

use crate::{config::LogConfig, vulkan};
use crate::input::InputState;
use crate::vulkan::{Game, MeshHandle, VulkanRenderer};
use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable};
use crate::vulkan::mesh;
use crate::vulkan::pipelines::vs::ty::ObjectUniformData;

pub mod player;
mod level;

pub struct TestGame {
    pub input: InputState,
    pub player: Player,
    pub game_objects: Vec<GameObjectHandle>,
    pub log_config: LogConfig,
    pub texture_index_counter: usize,
    pub last_time: f32,
    pub components: Vec<Box<dyn Updatable>>,
    pub paused: bool,
}

impl Game for TestGame {
    fn on_window_event(self: &mut Self, event: &Event<()>) {
        self.input.on_window_event(event);
    }

    fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> ObjectUniformData {
        // Input and timing
        self.input.frame_start();
        let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32;
        let frame_time = time - self.last_time;

        // Component update
        let input = &self.input;
        let components = &mut self.components;
        components.iter_mut().for_each(|component| {
            component.update(frame_time, &input, renderer);
        });

        // User interaction
        if self.input.button_just_released("quit") {
            renderer.game_data.shutdown = true;
        }

        if self.input.button_just_pressed("reload_shaders") {
            renderer.recreate_swapchain = true;
        }

        if self.input.button_just_pressed("quicksave") {
            save_level("levels/test.lvl", self, renderer).unwrap();
        }

        if self.input.button_just_pressed("quickload") {
            self.clear_level(renderer);
            load_level("levels/test.lvl", self, renderer).unwrap();
        }

        if self.input.button_down("print_framerate") {
            println!("{:.0} ms / {:.0} FPS", frame_time * 1000.0, 1.0 / frame_time);
            print_quat_as_euler(self.player.camera.rotation);
            println!();
        }

        if self.input.button_just_pressed("test") {
            self.paused = !self.paused;
        }

        // Custom game object stuff
        let light_pos = vec3(2.0, 0.5, 2.0);
        if !self.paused {
            self.player.update(frame_time, &self.input, renderer);
        }
        // self.game_objects[1].get_game_object_mut(renderer).unwrap().rotation = Quaternion::from_angle_y(Deg(time * -20.)).normalize();

        // End frame
        self.last_time = time;
        self.input.frame_end();

        ObjectUniformData {
            view: self.player.camera.view.into(),
            projection: self.player.camera.proj.into(),
            time,
            light_position: light_pos.into(),
            light_directional_rotation: [45.0, 45.0, 0.0],
            camera_position: self.player.camera.position.into(),
            _dummy0: [0; 12],
            _dummy1: [0; 4],
            _dummy2: [0; 4],
        }
    }
}

impl TestGame {
    pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame {
        TestGame {
            input: InputState::new(toml_path, log_config),
            player: Player::new(),
            game_objects: vec![],
            log_config,
            texture_index_counter: 0,
            last_time: 0.0,
            components: vec![],
            paused: false,
        }
    }

    pub fn game_start(self: &mut Self, renderer: &mut VulkanRenderer) {
        load_level("levels/test.lvl", self, renderer).unwrap();
        println!("Game loaded!");
    }

    pub fn offset_texture_id(&mut self, local_tex_id: Option<usize>) -> usize {
        match local_tex_id {
            Some(local_id) => local_id + self.texture_index_counter,
            None => 0,
        }
    }

    pub fn load_gltf(&mut self, renderer: &mut VulkanRenderer, gltf_path: &str) -> Vec<MeshHandle> {
        let mut mesh_handles = Vec::new();

        // Load file
        let (meshes, document) = mesh::load_mesh(gltf_path, self.log_config.mesh_load_info).unwrap();

        for cpu_mesh in meshes.into_iter() {
            // Convert file texture id to game texture id
            let diffuse_id = self.offset_texture_id(cpu_mesh.local_texture_index);
            let normal_id = self.offset_texture_id(cpu_mesh.local_normal_map_index);

            // Upload mesh
            let mesh_id = renderer.upload_mesh(cpu_mesh, gltf_path.to_string());
            let mesh_handle = MeshHandle {
                index: mesh_id,
                diffuse_handle: diffuse_id,
                normal_handle: normal_id,
                original_path: gltf_path.to_string()
            };
            mesh_handles.push(mesh_handle);
        }

        for doc_image in document.images() {
            let texture_start_time = SystemTime::now();
            
            vulkan::dds::upload_texture_from_file(&format!("models/textures/{}.dds", doc_image.name().unwrap()), renderer).unwrap();
            self.texture_index_counter += 1;
            
            if self.log_config.mesh_load_info {
                println!("Uploading texture took {:?}ms", texture_start_time.elapsed().unwrap().as_millis());
            }
        }
        mesh_handles
    }

    pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle {
        let obj = GameObject::new(mesh);
        let obj_handle = renderer.add_game_object(obj, 0);
        self.game_objects.push(obj_handle);
        self.game_objects.last().unwrap().clone()
    }

    pub fn clear_level(&mut self, renderer: &mut VulkanRenderer) {
        self.game_objects.clear();
        self.texture_index_counter = 0;
        renderer.clear_all();
    }
}

pub fn print_quat_as_euler(quat: Quaternion<f32>) {
    let euler = Euler::from(quat);
    print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z));
}