use std::{
    cell::{Cell, RefCell},
    char,
    collections::HashMap,
    rc::Rc,
    sync::Mutex,
};
#[cfg(not(target_os = "redox"))]
use minifb;
#[cfg(target_os = "redox")]
use minifb_fix as minifb;
use spin_sleep::LoopHelper;
use crate::{prelude::*, render::*, utils::*};
pub fn initialize() {}
fn key_event_helper<A>(key: &mut KeyHelper, adapter: &mut A, window: &minifb::Window)
where
    A: WindowAdapter,
{
    if !key.0 && window.is_key_pressed(key.1, minifb::KeyRepeat::Yes) {
        adapter.key_event(KeyEvent {
            key: key.2,
            state: ButtonState::Down,
            text: String::new(),
        });
        key.0 = true;
    } else {
        key.0 = false;
    }
}
fn unicode_to_key_event(uni_char: u32) -> Option<KeyEvent> {
    let mut text = String::new();
    let key = if let Some(character) = char::from_u32(uni_char) {
        text = character.to_string();
        Key::from(character)
    } else {
        Key::Unknown
    };
    if key == Key::Up || key == Key::Down || key == Key::Left || key == Key::Right {
        return None;
    }
    Some(KeyEvent {
        key,
        state: ButtonState::Down,
        text,
    })
}
struct KeyInputCallBack {
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
}
impl minifb::InputCallback for KeyInputCallBack {
    fn add_char(&mut self, uni_char: u32) {
        if let Some(key_event) = unicode_to_key_event(uni_char) {
            self.key_events.borrow_mut().push(key_event);
        }
    }
}
struct KeyHelper(bool, minifb::Key, Key);
pub struct WindowShell<A>
where
    A: WindowAdapter,
{
    window: minifb::Window,
    render_context_2_d: RenderContext2D,
    adapter: A,
    mouse_pos: (f32, f32),
    button_down: (bool, bool, bool),
    window_size: (usize, usize),
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
    
    key_backspace: KeyHelper,
    key_delete: KeyHelper,
    key_left: KeyHelper,
    key_right: KeyHelper,
    key_up: KeyHelper,
    key_down: KeyHelper,
    key_enter: KeyHelper,
    key_control: KeyHelper,
    key_control_right: KeyHelper,
    key_shift_l: KeyHelper,
    key_shift_r: KeyHelper,
    key_alt: KeyHelper,
    key_alt_r: KeyHelper,
    key_escape: KeyHelper,
    key_home: KeyHelper,
}
impl<A> WindowShell<A>
where
    A: WindowAdapter,
{
    
    pub fn new(
        window: minifb::Window,
        adapter: A,
        key_events: Rc<RefCell<Vec<KeyEvent>>>,
    ) -> WindowShell<A> {
        let size = window.get_size();
        let render_context_2_d = RenderContext2D::new(size.0 as f64, size.1 as f64);
        WindowShell {
            window,
            
            
            
            render_context_2_d,
            adapter,
            mouse_pos: (0.0, 0.0),
            window_size: size,
            button_down: (false, false, false),
            key_events,
            key_backspace: KeyHelper(false, minifb::Key::Backspace, Key::Backspace),
            key_left: KeyHelper(false, minifb::Key::Left, Key::Left),
            key_right: KeyHelper(false, minifb::Key::Right, Key::Right),
            key_up: KeyHelper(false, minifb::Key::Up, Key::Up),
            key_down: KeyHelper(false, minifb::Key::Down, Key::Down),
            key_delete: KeyHelper(false, minifb::Key::Delete, Key::Delete),
            key_enter: KeyHelper(false, minifb::Key::Enter, Key::Enter),
            key_control: KeyHelper(false, minifb::Key::LeftCtrl, Key::Control),
            key_control_right: KeyHelper(false, minifb::Key::RightCtrl, Key::Control),
            key_shift_l: KeyHelper(false, minifb::Key::LeftShift, Key::ShiftL),
            key_shift_r: KeyHelper(false, minifb::Key::RightShift, Key::ShiftR),
            key_alt: KeyHelper(false, minifb::Key::LeftAlt, Key::Alt),
            key_alt_r: KeyHelper(false, minifb::Key::RightAlt, Key::Alt),
            key_escape: KeyHelper(false, minifb::Key::Escape, Key::Escape),
            key_home: KeyHelper(false, minifb::Key::Home, Key::Home),
        }
    }
    
    pub fn adapter(&mut self) -> &mut A {
        &mut self.adapter
    }
    
    pub fn render_context_2_d(&mut self) -> &mut RenderContext2D {
        &mut self.render_context_2_d
    }
    fn drain_events(&mut self) {
        
        if let Some(pos) = self.window.get_mouse_pos(minifb::MouseMode::Discard) {
            if pos != self.mouse_pos {
                self.adapter.mouse(pos.0 as f64, pos.1 as f64);
                self.mouse_pos = pos;
            }
        }
        
        let left_button_down = self.window.get_mouse_down(minifb::MouseButton::Left);
        let middle_button_down = self.window.get_mouse_down(minifb::MouseButton::Middle);
        let right_button_down = self.window.get_mouse_down(minifb::MouseButton::Right);
        if left_button_down != self.button_down.0 {
            if left_button_down {
                self.push_mouse_event(true, MouseButton::Left);
            } else {
                self.push_mouse_event(false, MouseButton::Left);
            }
            self.button_down.0 = left_button_down;
        }
        if middle_button_down != self.button_down.1 {
            if middle_button_down {
                self.push_mouse_event(true, MouseButton::Middle);
            } else {
                self.push_mouse_event(false, MouseButton::Middle);
            }
            self.button_down.1 = middle_button_down;
        }
        if right_button_down != self.button_down.2 {
            if right_button_down {
                self.push_mouse_event(true, MouseButton::Right);
            } else {
                self.push_mouse_event(false, MouseButton::Right);
            }
            self.button_down.2 = right_button_down;
        }
        
        if let Some(delta) = self.window.get_scroll_wheel() {
            self.adapter.scroll(delta.0 as f64, delta.1 as f64);
        }
        
        while let Some(event) = self.key_events.borrow_mut().pop() {
            self.adapter.key_event(event);
        }
        key_event_helper(&mut self.key_backspace, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_delete, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_left, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_right, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_up, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_down, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_enter, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_control, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_control_right, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_shift_l, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_shift_r, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_alt, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_alt_r, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_escape, &mut self.adapter, &self.window);
        key_event_helper(&mut self.key_home, &mut self.adapter, &self.window);
        
        if self.window_size != self.window.get_size() {
            self.window_size = self.window.get_size();
            self.render_context_2_d
                .resize(self.window_size.0 as f64, self.window_size.1 as f64);
            self.adapter
                .resize(self.window_size.0 as f64, self.window_size.1 as f64);
        }
    }
    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };
        self.adapter.mouse_event(MouseEvent {
            x: self.mouse_pos.0 as f64,
            y: self.mouse_pos.1 as f64,
            button,
            state,
        });
    }
    pub fn flip(&mut self) -> bool {
        if let Some(data) = self.render_context_2_d.data() {
            self.window.update_with_buffer(data).unwrap();
            CONSOLE.time_end("render");
            return true;
        }
        false
    }
}
impl<A> Drop for WindowShell<A>
where
    A: WindowAdapter,
{
    fn drop(&mut self) {}
}
pub struct ShellRunner<A>
where
    A: WindowAdapter,
{
    pub window_shell: Rc<RefCell<WindowShell<A>>>,
    pub update: Rc<Cell<bool>>,
    pub running: Rc<Cell<bool>>,
    pub updater: Box<dyn Updater>,
}
impl<A> ShellRunner<A>
where
    A: WindowAdapter,
{
    pub fn run(&mut self) {
        let mut loop_helper = LoopHelper::builder()
            .report_interval_s(0.5)
            .build_with_target_rate(60.0);
        let mut _current_fps = None;
        loop {
            if !self.running.get() || !self.window_shell.borrow().window.is_open() {
                break;
            }
            let _delta = loop_helper.loop_start();
            if let Some(fps) = loop_helper.report_rate() {
                _current_fps = Some(fps);
                
            }
            
            self.updater.update();
            if self.update.get() {
                self.update.set(false);
            }
            if !self.window_shell.borrow_mut().flip() {
                self.window_shell.borrow_mut().window.update();
            }
            self.window_shell.borrow_mut().drain_events();
            loop_helper.loop_sleep();
            
        }
    }
}
pub struct WindowBuilder<A>
where
    A: WindowAdapter,
{
    title: String,
    resizeable: bool,
    bounds: Rectangle,
    adapter: A,
}
impl<A> WindowBuilder<A>
where
    A: WindowAdapter,
{
    
    pub fn new(adapter: A) -> Self {
        WindowBuilder {
            adapter,
            title: String::default(),
            resizeable: false,
            bounds: Rectangle::default(),
        }
    }
    
    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }
    
    pub fn resizeable(mut self, resizeable: bool) -> Self {
        self.resizeable = resizeable;
        self
    }
    
    pub fn bounds(mut self, bounds: impl Into<Rectangle>) -> Self {
        self.bounds = bounds.into();
        self
    }
    
    pub fn build(self) -> WindowShell<A> {
        let window_options = minifb::WindowOptions {
            resize: self.resizeable,
            ..Default::default()
        };
        let mut window = minifb::Window::new(
            self.title.as_str(),
            self.bounds.width as usize,
            self.bounds.height as usize,
            window_options,
        )
        .unwrap_or_else(|e| {
            panic!("{}", e);
        });
        let key_events = Rc::new(RefCell::new(vec![]));
        window.set_input_callback(Box::new(KeyInputCallBack {
            key_events: key_events.clone(),
        }));
        window.set_position(self.bounds.x as isize, self.bounds.y as isize);
        WindowShell::new(window, self.adapter, key_events)
    }
}
use std::time::Instant;
lazy_static! {
    pub static ref CONSOLE: Console = Console {
        instants: Mutex::new(HashMap::new())
    };
}
pub struct Console {
    instants: Mutex<HashMap<String, Instant>>,
}
impl Console {
    pub fn time(&self, name: impl Into<String>) {
        self.instants
            .lock()
            .unwrap()
            .insert(name.into(), Instant::now());
    }
    pub fn time_end(&self, name: impl Into<String>) {
        if let Some((k, v)) = self.instants.lock().unwrap().remove_entry(&name.into()) {
            println!("{} {}ms - timer ended", k, v.elapsed().as_millis());
        }
    }
    pub fn log(&self, message: impl Into<String>) {
        println!("{}", message.into());
    }
}