use std::time::Instant;
use cgmath::{Deg, Euler, Quaternion, vec3};
use glyph_brush::GlyphBrush;
use winit::event::Event;

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

use crate::game::entities::{FpsCounter, UiQuad, WorldQuad};
use crate::text::create_brush;
use crate::vulkan::pipelines::vs;
use crate::{config::LogConfig, vulkan};
use crate::input::InputState;
use crate::vulkan::{Game, MeshHandle, TextVertex, Vertex, VulkanRenderer};
use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable};
use crate::vulkan::mesh;

pub mod player;
mod level;
mod entities;

pub struct GameState {
    pub paused: bool,
    pub brush: GlyphBrush<Vec<TextVertex>>,
    pub test_str: String,
}

impl GameState {
    fn new() -> GameState {
        GameState {
            brush: create_brush(),
            paused: false,
            test_str: "".to_string(),
        }
    }
}

pub struct TestGame {
    pub input: InputState,
    pub player: Player,
    pub game_objects: Vec<GameObject>,
    pub log_config: LogConfig,
    pub texture_index_counter: usize,
    pub last_time: f32,
    pub components: Vec<Box<dyn Updatable>>,
    pub game_state: GameState,
    pub ubo: vs::ty::ObjectUniformData,
}

impl Game for TestGame {
    fn get_game_objects(&self) -> &Vec<GameObject> {
        &self.game_objects
    }

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

    fn update(self: &mut Self, renderer: &mut VulkanRenderer) {
        let precise_start = Instant::now();

        // 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 objs = &mut self.game_objects;
        let components = &mut self.components;
        let paused = self.game_state.paused;
        let state = &mut self.game_state;
        
        if !paused {
            components.iter_mut().for_each(|component| {
                component.update(frame_time, &input, state, objs, renderer);
            });
            self.player.update(frame_time, &input, state, objs, 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_just_pressed("test") {
            self.game_state.paused = !self.game_state.paused;
        }

        for char in self.input.typed_characters.iter() {
            match char {
                '\u{8}' => { self.game_state.test_str.pop(); },
                c => { self.game_state.test_str.push(*c); },
            }
        }
        
        // Custom game object stuff
        let light_pos = vec3(2.0, 0.5, 2.0);

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

        self.ubo = vs::ty::ObjectUniformData {
            view: self.player.camera.view.into(),
            projection: self.player.camera.proj.into(),
            ortho_projection: self.player.camera.ortho_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],
        };
        
        let precise_duration = precise_start.elapsed().as_micros();
        renderer.game_data.update_perf_counters[renderer.game_data.performance_counter_index] = precise_duration;
    }

    fn get_ubo(&self) -> &vs::ty::ObjectUniformData {
        &self.ubo
    }
}

impl TestGame {
    pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame {
        TestGame {
            input: InputState::new(toml_path, log_config),
            player: Player::new(3., 30.),
            game_objects: vec![],
            log_config,
            texture_index_counter: 0,
            last_time: 0.0,
            components: vec![],
            game_state: GameState::new(),
            ubo: vs::ty::ObjectUniformData {
                view: [[0.; 4]; 4],
                projection: [[0.; 4]; 4],
                ortho_projection: [[0.; 4]; 4],
                time: 0.,
                light_position: [0.; 3],
                light_directional_rotation: [0.; 3],
                camera_position: [0.; 3],
                _dummy0: [0; 12],
                _dummy1: [0; 4],
                _dummy2: [0; 4],
            },
        }
    }

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

        let fps = FpsCounter::new(self, renderer);
        self.components.push(Box::new(fps));

        let test_quad = UiQuad::new(self, renderer);
        self.components.push(Box::new(test_quad));

        let world_quad = WorldQuad::new(self, renderer);
        let quad_go = &mut self.game_objects[world_quad.game_object];
        quad_go.position = vec3(0.0, 0.01, 0.0);
        quad_go.scale = vec3(10., 10., 10.);
        
        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::<Vertex>(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, Some(gltf_path.to_string()));
            let mesh_handle = MeshHandle {
                index: mesh_id,
                textures: vec![diffuse_id, normal_id],
                original_path: Some(gltf_path.to_string()),
                pipeline_index: 0
            };
            mesh_handles.push(mesh_handle);
        }

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

    pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle {
        let mut obj = GameObject::new(mesh);
        obj.init_descriptor_sets(renderer);
        self.game_objects.push(obj);
        self.game_objects.len() - 1
    }

    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));
}
