use pyxel_platform::key::GAMEPAD1_BUTTON_BACK;
use pyxel_platform::Event;

use crate::image::{Color, Image, SharedImage};
use crate::key::{
    Key, GAMEPAD1_BUTTON_A, GAMEPAD1_BUTTON_B, GAMEPAD1_BUTTON_DPAD_DOWN,
    GAMEPAD1_BUTTON_DPAD_LEFT, GAMEPAD1_BUTTON_DPAD_RIGHT, GAMEPAD1_BUTTON_DPAD_UP,
    GAMEPAD1_BUTTON_X, GAMEPAD1_BUTTON_Y, KEY_0, KEY_1, KEY_2, KEY_3, KEY_8, KEY_9, KEY_ALT, KEY_R,
    KEY_RETURN, KEY_SHIFT,
};
use crate::profiler::Profiler;
use crate::pyxel::Pyxel;
use crate::settings::{MAX_FRAME_DELAY_MS, NUM_MEASURE_FRAMES, NUM_SCREEN_TYPES};
use crate::utils;
use crate::window_watcher::WindowWatcher;

pub trait PyxelCallback {
    fn update(&mut self, pyxel: &mut Pyxel);
    fn draw(&mut self, pyxel: &mut Pyxel);
}

pub struct System {
    fps: u32,
    frame_ms: f32,
    quit_key: Key,
    paused: bool,
    fps_profiler: Profiler,
    update_profiler: Profiler,
    draw_profiler: Profiler,
    perf_monitor_enabled: bool,
    integer_scale_enabled: bool,
    window_watcher: WindowWatcher,
    pub screen_x: i32,
    pub screen_y: i32,
    pub screen_scale: f32,
    pub screen_mode: u32,
}

impl System {
    pub fn new(fps: u32, quit_key: Key) -> Self {
        Self {
            fps,
            frame_ms: 1000.0 / fps as f32,
            quit_key,
            paused: false,
            fps_profiler: Profiler::new(NUM_MEASURE_FRAMES),
            update_profiler: Profiler::new(NUM_MEASURE_FRAMES),
            draw_profiler: Profiler::new(NUM_MEASURE_FRAMES),
            perf_monitor_enabled: false,
            integer_scale_enabled: false,
            window_watcher: WindowWatcher::new(),
            screen_x: 0,
            screen_y: 0,
            screen_scale: 0.0,
            screen_mode: 0,
        }
    }
}

impl Pyxel {
    pub fn run<T: PyxelCallback>(&mut self, mut callback: T) {
        pyxel_platform::run_frame_loop(self.system.fps, move |delta_ms| {
            let ticks = pyxel_platform::ticks();
            self.system.fps_profiler.end(ticks);
            self.system.fps_profiler.start(ticks);

            let update_count = if delta_ms > MAX_FRAME_DELAY_MS as f32 {
                1
            } else {
                (delta_ms / self.system.frame_ms) as u32
            };

            for _ in 1..update_count {
                self.update_frame(Some(&mut callback));
                self.frame_count += 1;
            }

            self.update_frame(Some(&mut callback));
            self.draw_frame(Some(&mut callback));
            self.frame_count += 1;
        });
    }

    pub fn show(&mut self) {
        struct App {
            image: SharedImage,
        }

        impl PyxelCallback for App {
            fn update(&mut self, _pyxel: &mut Pyxel) {}
            fn draw(&mut self, pyxel: &mut Pyxel) {
                pyxel.screen.lock().blt(
                    0.0,
                    0.0,
                    self.image.clone(),
                    0.0,
                    0.0,
                    pyxel.width as f32,
                    pyxel.height as f32,
                    None,
                    None,
                    None,
                );
            }
        }

        let image = Image::new(self.width, self.height);
        image.lock().blt(
            0.0,
            0.0,
            self.screen.clone(),
            0.0,
            0.0,
            self.width as f32,
            self.height as f32,
            None,
            None,
            None,
        );

        self.run(App { image });
    }

    pub fn flip(&mut self) {
        self.system.update_profiler.end(pyxel_platform::ticks());

        self.draw_frame(None);
        self.frame_count += 1;

        pyxel_platform::step_frame(self.system.fps);

        let ticks = pyxel_platform::ticks();
        self.system.fps_profiler.end(ticks);
        self.system.fps_profiler.start(ticks);

        self.update_frame(None);
    }

    pub fn quit(&self) {
        pyxel_platform::quit();
    }

    pub fn reset(&mut self) {
        #[cfg(not(target_os = "emscripten"))]
        if let Some(mut reset_func) = crate::pyxel::RESET_FUNC.lock().take() {
            reset_func();
        }

        #[cfg(target_os = "emscripten")]
        {
            extern "C" {
                fn emscripten_run_script(script: *const std::os::raw::c_char);
            }

            unsafe {
                let script = std::ffi::CString::new("resetPyxel();").unwrap();
                emscripten_run_script(script.as_ptr());
            }
        }
    }

    pub fn title(&self, title: &str) {
        pyxel_platform::set_window_title(title);
    }

    pub fn icon(&self, data_str: &[&str], scale: u32, transparent: Option<Color>) {
        let colors = self.colors.lock();
        let width = utils::simplify_string(data_str[0]).len() as u32;
        let height = data_str.len() as u32;
        let image = Image::new(width, height);
        let mut image = image.lock();
        image.set(0, 0, data_str);
        let image_data = &image.canvas.data;
        let scaled_width = width * scale;
        let scaled_height = height * scale;
        let mut rgba: Vec<u8> = Vec::with_capacity((scaled_width * scaled_height * 4) as usize);

        for y in 0..height {
            for _sy in 0..scale {
                for x in 0..width {
                    let color = image_data[(width * y + x) as usize];
                    let argb = colors[color as usize];
                    let r = ((argb >> 16) & 0xff) as u8;
                    let g = ((argb >> 8) & 0xff) as u8;
                    let b = (argb & 0xff) as u8;
                    let a = if Some(color) == transparent { 0 } else { 0xff };
                    for _sx in 0..scale {
                        rgba.push(r);
                        rgba.push(g);
                        rgba.push(b);
                        rgba.push(a);
                    }
                }
            }
        }

        pyxel_platform::set_window_icon(scaled_width, scaled_height, &rgba);
    }

    pub fn perf_monitor(&mut self, enabled: bool) {
        self.system.perf_monitor_enabled = enabled;
    }

    pub fn integer_scale(&mut self, enabled: bool) {
        self.system.integer_scale_enabled = enabled;
    }

    pub fn screen_mode(&mut self, screen_mode: u32) {
        self.system.screen_mode = screen_mode;
    }

    pub fn fullscreen(&self, enabled: bool) {
        pyxel_platform::set_fullscreen(enabled);
    }

    fn process_events(&mut self) {
        self.start_input_frame();
        let events = pyxel_platform::poll_events();

        for event in events {
            match event {
                Event::WindowShown => {
                    self.system.paused = false;
                    pyxel_platform::pause_audio(false);
                }
                Event::WindowHidden => {
                    self.system.paused = true;
                    pyxel_platform::pause_audio(true);
                }
                Event::KeyPressed { key } => {
                    self.press_key(key);
                }
                Event::KeyReleased { key } => {
                    self.release_key(key);
                }
                Event::KeyValueChanged { key, value } => {
                    self.change_key_value(key, value);
                }
                Event::TextInput { text } => {
                    self.add_input_text(&text);
                }
                Event::FileDropped { filename } => {
                    self.add_dropped_file(&filename);
                }
                Event::Quit => {
                    pyxel_platform::quit();
                }
            }
        }
    }

    fn check_special_input(&mut self) {
        if self.btnp(self.system.quit_key, None, None) {
            self.reset_key(self.system.quit_key);
            self.quit();
        } else if self.btn(KEY_ALT) {
            if self.btn(KEY_SHIFT) {
                if self.btnp(KEY_0, None, None) {
                    self.reset_key(KEY_0);
                    self.dump_palette();
                } else {
                    for i in 0..=8 {
                        if self.btnp(KEY_1 + i, None, None) {
                            self.reset_key(KEY_1 + i);
                            self.dump_image_bank(i);
                        }
                    }
                }
            } else if self.btnp(KEY_1, None, None) {
                self.reset_key(KEY_1);
                self.screenshot(None);
            } else if self.btnp(KEY_2, None, None) {
                self.reset_key(KEY_2);
                self.reset_screencast();
            } else if self.btnp(KEY_3, None, None) {
                self.reset_key(KEY_3);
                self.screencast(None);
            } else if self.btnp(KEY_8, None, None) {
                self.reset_key(KEY_8);
                self.integer_scale(!self.system.integer_scale_enabled);
            } else if self.btnp(KEY_9, None, None) {
                self.reset_key(KEY_9);
                self.screen_mode((self.system.screen_mode + 1) % NUM_SCREEN_TYPES);
            } else if self.btnp(KEY_0, None, None) {
                self.reset_key(KEY_0);
                self.perf_monitor(!self.system.perf_monitor_enabled);
            } else if self.btnp(KEY_R, None, None) {
                self.reset_key(KEY_RETURN);
                self.reset();
            } else if self.btnp(KEY_RETURN, None, None) {
                self.reset_key(KEY_RETURN);
                self.fullscreen(!pyxel_platform::is_fullscreen());
            }
        } else if self.btn(GAMEPAD1_BUTTON_A)
            && self.btn(GAMEPAD1_BUTTON_B)
            && self.btn(GAMEPAD1_BUTTON_X)
            && self.btn(GAMEPAD1_BUTTON_Y)
        {
            if self.btnp(GAMEPAD1_BUTTON_BACK, None, None) {
                self.reset_key(GAMEPAD1_BUTTON_BACK);
                self.reset();
            } else if self.btnp(GAMEPAD1_BUTTON_DPAD_LEFT, None, None) {
                self.reset_key(GAMEPAD1_BUTTON_DPAD_UP);
                self.integer_scale(!self.system.integer_scale_enabled);
            } else if self.btnp(GAMEPAD1_BUTTON_DPAD_RIGHT, None, None) {
                self.reset_key(GAMEPAD1_BUTTON_DPAD_DOWN);
                self.screen_mode((self.system.screen_mode + 1) % NUM_SCREEN_TYPES);
            } else if self.btnp(GAMEPAD1_BUTTON_DPAD_UP, None, None) {
                self.reset_key(GAMEPAD1_BUTTON_DPAD_LEFT);
                self.perf_monitor(!self.system.perf_monitor_enabled);
            } else if self.btnp(GAMEPAD1_BUTTON_DPAD_DOWN, None, None) {
                self.reset_key(GAMEPAD1_BUTTON_DPAD_RIGHT);
                self.fullscreen(!pyxel_platform::is_fullscreen());
            }
        }
    }

    fn update_screen_params(&mut self) {
        let (window_width, window_height) = pyxel_platform::window_size();

        if self.system.integer_scale_enabled {
            self.system.screen_scale = f32::max(
                f32::min(
                    (window_width as f32 / self.width as f32) as i32 as f32,
                    (window_height as f32 / self.height as f32) as i32 as f32,
                ),
                1.0,
            );
        } else {
            self.system.screen_scale = f32::max(
                f32::min(
                    window_width as f32 / self.width as f32,
                    window_height as f32 / self.height as f32,
                ),
                1.0,
            );
        }

        self.system.screen_x =
            (window_width as i32 - (self.width as f32 * self.system.screen_scale) as i32) / 2;
        self.system.screen_y =
            (window_height as i32 - (self.height as f32 * self.system.screen_scale) as i32) / 2;
    }

    fn update_frame(&mut self, callback: Option<&mut dyn PyxelCallback>) {
        self.system.update_profiler.start(pyxel_platform::ticks());

        self.process_events();

        if self.system.paused {
            return;
        }

        self.check_special_input();

        if let Some(callback) = callback {
            callback.update(self);
            self.system.update_profiler.end(pyxel_platform::ticks());
        }
    }

    fn draw_perf_monitor(&self) {
        if !self.system.perf_monitor_enabled {
            return;
        }

        let mut screen = self.screen.lock();
        let clip_rect = screen.canvas.clip_rect;
        let camera_x = screen.canvas.camera_x;
        let camera_y = screen.canvas.camera_y;
        let palette1 = screen.palette[1];
        let palette2 = screen.palette[2];
        let alpha = screen.canvas.alpha;

        screen.clip0();
        screen.camera0();
        screen.pal(1, 1);
        screen.pal(2, 9);
        screen.dither(1.0);

        let fps = format!("{:.*}", 2, self.system.fps_profiler.average_fps());
        screen.text(1.0, 0.0, &fps, 1, None);
        screen.text(0.0, 0.0, &fps, 2, None);

        let update_time = format!("{:.*}", 2, self.system.update_profiler.average_time());
        screen.text(1.0, 6.0, &update_time, 1, None);
        screen.text(0.0, 6.0, &update_time, 2, None);

        let draw_time = format!("{:.*}", 2, self.system.draw_profiler.average_time());
        screen.text(1.0, 12.0, &draw_time, 1, None);
        screen.text(0.0, 12.0, &draw_time, 2, None);

        screen.canvas.clip_rect = clip_rect;
        screen.canvas.camera_x = camera_x;
        screen.canvas.camera_y = camera_y;
        screen.pal(1, palette1);
        screen.pal(2, palette2);
        screen.dither(alpha);
    }

    fn draw_cursor(&self) {
        let x = self.mouse_x;
        let y = self.mouse_y;

        pyxel_platform::set_mouse_visible(
            x < 0 || x >= self.width as i32 || y < 0 || y >= self.height as i32,
        );

        if !self.is_mouse_visible() {
            return;
        }

        let width = self.cursor.lock().width() as i32;
        let height = self.cursor.lock().height() as i32;

        if x <= -width || x >= self.width as i32 || y <= -height || y >= self.height as i32 {
            return;
        }

        let mut screen = self.screen.lock();
        let clip_rect = screen.canvas.clip_rect;
        let camera_x = screen.canvas.camera_x;
        let camera_y = screen.canvas.camera_y;
        let palette = screen.palette;

        screen.clip0();
        screen.camera0();
        screen.blt(
            x as f32,
            y as f32,
            self.cursor.clone(),
            0.0,
            0.0,
            width as f32,
            height as f32,
            Some(0),
            None,
            None,
        );

        screen.canvas.clip_rect = clip_rect;
        screen.canvas.camera_x = camera_x;
        screen.canvas.camera_y = camera_y;
        screen.palette = palette;
    }

    fn draw_frame(&mut self, callback: Option<&mut dyn PyxelCallback>) {
        self.system.draw_profiler.start(pyxel_platform::ticks());

        if self.system.paused {
            return;
        }

        self.update_screen_params();

        if let Some(callback) = callback {
            callback.draw(self);
        }

        self.system.window_watcher.update();
        self.draw_perf_monitor();
        self.draw_cursor();
        self.render_screen();
        self.capture_screen();

        self.system.draw_profiler.end(pyxel_platform::ticks());
    }
}
