use std::{
    mem,
    process::Command,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
    thread,
    time::Duration,
};

use cgmath::{EuclideanSpace, Point2, Vector2};
use egui::{Button, CursorIcon, Event, Modifiers, RichText, Style, TopBottomPanel};
use image::{ColorType, DynamicImage, imageops::FilterType};
use num_traits::Zero;
use winit::{
    event::WindowEvent, event_loop::EventLoopProxy, keyboard::ModifiersState, window::Fullscreen,
};

use crate::{
    WgpuState, min,
    util::{UserEvent, p2},
};

pub mod image_view;
use image_view::ImageView;

pub mod image_list;

mod clipboard;

mod color;
mod help;
mod menu_bar;
mod metadata;

pub mod op_queue;
use op_queue::{Op, OpQueue, Output};

pub mod load_image;

mod save_image;

mod undo_stack;

mod cache;

mod resize;
use resize::Resize;

pub mod preferences;

pub mod dialog_manager;

use self::{
    dialog_manager::{DialogManager, DialogProxy},
    image_view::{crop_renderer, image_renderer},
    preferences::PREFERENCES,
    undo_stack::UndoFrame,
};

enum ResizeMode {
    Original,
    BestFit,
    LargestFit,
}

const TOP_BAR_SIZE: f32 = 22.0;
const BOTTOM_BAR_SIZE: f32 = 20.0;

pub struct App {
    exit: Arc<AtomicBool>,
    pub delay: Duration,
    pub image_renderer: image_renderer::Renderer,
    pub crop_renderer: crop_renderer::Renderer,
    pub image_view: Option<Box<ImageView>>,
    pub modifiers: ModifiersState,
    dialog_manager: DialogManager,
    color_type: ColorType,
    size: Vector2<f32>,
    top_bar_size: f32,
    bottom_bar_size: f32,
    proxy: EventLoopProxy<UserEvent>,
    mouse_position: Vector2<f32>,
    current_filename: String,
    op_queue: OpQueue,
    resize: Resize,
    resize_mode: ResizeMode,
    help_visible: bool,
    color_visible: bool,
    color_space_visible: bool,
    metadata_visible: bool,
    preferences_visible: bool,
    enter: bool,
    zen_mode: bool,
}

impl App {
    pub fn view_available(&self) -> bool {
        !self.op_queue.working() && self.image_view.is_some()
    }

    pub fn handle_output(&mut self, wgpu: &WgpuState, output: Output) {
        let stack = self.op_queue.undo_stack_mut();
        match output {
            Output::ImageLoaded(image_data, path) => {
                stack.clear();
                self.current_filename = if let Some(path) = &path {
                    self.op_queue.image_list.change_dir(path);
                    path.file_name().unwrap().to_str().unwrap().to_string()
                } else {
                    String::new()
                };

                self.color_type = image_data.frames[0].buffer().color();

                let view = Box::new(ImageView::new(wgpu, image_data, path));
                self.resize
                    .set_size(Vector2::new(view.size.x as u32, view.size.y as u32));
                self.image_view = Some(view);

                if self.current_filename.is_empty() {
                    wgpu.window.set_title("Simp");
                } else {
                    wgpu.window.set_title(&self.current_filename.to_string());
                }

                self.set_bar_size(wgpu);
                self.largest_fit();
            }
            Output::FlipHorizontal => {
                let view = self.image_view.as_mut().unwrap();
                if view.rotation() % 2 != 0 {
                    view.flip_vertical();
                    stack.push(UndoFrame::FlipVertical);
                } else {
                    view.flip_horizontal();
                    stack.push(UndoFrame::FlipHorizontal);
                }
            }
            Output::FlipVertical => {
                let view = self.image_view.as_mut().unwrap();
                if view.rotation() % 2 != 0 {
                    view.flip_horizontal();
                    stack.push(UndoFrame::FlipHorizontal);
                } else {
                    view.flip_vertical();
                    stack.push(UndoFrame::FlipVertical);
                }
            }
            Output::Rotate(dir) => {
                self.image_view.as_mut().unwrap().rotate(dir);
                stack.push(UndoFrame::Rotate(dir));
            }
            Output::Resize(mut frames) => {
                if let Some(ref mut view) = self.image_view {
                    view.swap_frames(wgpu, &mut frames);
                    stack.push(UndoFrame::Resize(frames));
                }
            }
            Output::ColorSpace(mut frames) => {
                if let Some(ref mut view) = self.image_view {
                    let new = frames[0].buffer().color();
                    let old = view.image_data.read().unwrap().frames[0].buffer().color();
                    if new != old {
                        view.swap_frames(wgpu, &mut frames);
                        stack.push(UndoFrame::ColorSpace(frames));
                    }
                }
            }
            Output::Color(mut frames) => {
                if let Some(ref mut view) = self.image_view {
                    view.swap_frames(wgpu, &mut frames);
                    stack.push(UndoFrame::Color(frames));
                    view.hue = 0.0;
                    view.contrast = 0.0;
                    view.saturation = 0.0;
                    view.brightness = 0.0;
                    view.grayscale = false;
                    view.invert = false;
                    wgpu.window.request_redraw();
                }
            }
            Output::Crop(mut frames, rotation) => {
                if let Some(ref mut view) = self.image_view {
                    view.set_rotation(0);
                    view.swap_frames(wgpu, &mut frames);
                    stack.push(UndoFrame::Crop { frames, rotation })
                }
            }
            Output::Undo => {
                let frame = stack.undo();
                if let Some(frame) = frame {
                    match frame {
                        UndoFrame::Rotate(rot) => {
                            self.image_view.as_mut().unwrap().rotate(-*rot);
                        }
                        UndoFrame::FlipHorizontal => {
                            self.image_view.as_mut().unwrap().flip_horizontal();
                        }
                        UndoFrame::FlipVertical => {
                            self.image_view.as_mut().unwrap().flip_vertical();
                        }
                        UndoFrame::Crop { frames, rotation } => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                            view.swap_rotation(rotation);
                        }
                        UndoFrame::Resize(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                        }
                        UndoFrame::Color(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                        }
                        UndoFrame::ColorSpace(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                            self.color_type =
                                view.image_data.read().unwrap().frames[0].buffer().color();
                        }
                    }
                }
            }
            Output::Redo => {
                let frame = stack.redo();
                if let Some(frame) = frame {
                    match frame {
                        UndoFrame::Rotate(rot) => {
                            self.image_view.as_mut().unwrap().rotate(*rot);
                        }
                        UndoFrame::FlipHorizontal => {
                            self.image_view.as_mut().unwrap().flip_horizontal();
                        }
                        UndoFrame::FlipVertical => {
                            self.image_view.as_mut().unwrap().flip_vertical();
                        }
                        UndoFrame::Crop { frames, rotation } => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                            view.swap_rotation(rotation);
                        }
                        UndoFrame::Resize(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                        }
                        UndoFrame::Color(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                        }
                        UndoFrame::ColorSpace(frames) => {
                            let view = self.image_view.as_mut().unwrap();
                            view.swap_frames(wgpu, frames);
                            self.color_type =
                                view.image_data.read().unwrap().frames[0].buffer().color();
                        }
                    }
                }
            }
            Output::Close => {
                self.image_view = None;
                stack.clear();
                self.op_queue.image_list.clear();
                self.op_queue.cache.clear();
                wgpu.window.set_title("Simp");
            }
            Output::Saved => {
                stack.set_saved();
            }
            // indicates that the operation is done with no output
            Output::Done => (),
        }
    }

    pub fn handle_user_event(&mut self, wgpu: &WgpuState, event: &mut UserEvent) {
        match event {
            UserEvent::LoadBytes(bytes) => {
                self.queue(Op::LoadBytes(mem::take(bytes)));
            }
            UserEvent::QueueLoad(path) => {
                self.queue(Op::LoadPath(path.to_path_buf(), false));
            }
            UserEvent::QueueSave(path) => {
                self.queue(Op::Save(path.to_path_buf()));
            }
            UserEvent::QueueDelete(path) => {
                self.queue(Op::Delete(path.to_path_buf()));
            }
            UserEvent::ErrorMessage(error) => {
                let error = error.clone();
                self.dialog_manager
                    .get_proxy()
                    .spawn_dialog("Error", move |ui, enter| {
                        ui.label(&error);
                        ui.with_layout(egui::Layout::left_to_right(egui::Align::LEFT), |ui| {
                            if ui.button("Ok").clicked() {
                                Some(())
                            } else if *enter {
                                *enter = false;
                                Some(())
                            } else {
                                None
                            }
                        })
                        .inner
                    });
            }
            UserEvent::Exit => {
                let exit = self.exit.clone();
                let dialog_proxy = self.dialog_manager.get_proxy();
                if !self.op_queue.undo_stack().is_edited() {
                    exit.store(true, Ordering::Relaxed);
                    return;
                }
                thread::spawn(move || {
                    let close = dialog_proxy
                        .spawn_dialog("Unsaved changes", move |ui, enter| {
                            ui.label(
                            "You have unsaved changes are you sure you want to close this image?",
                        );
                            ui.with_layout(egui::Layout::left_to_right(egui::Align::LEFT), |ui| {
                                if ui.button("Ok").clicked() {
                                    return Some(true);
                                }

                                if ui.button("Cancel").clicked() {
                                    return Some(false);
                                }

                                if *enter {
                                    *enter = false;
                                    return Some(true);
                                }

                                None
                            })
                            .inner
                        })
                        .wait()
                        .unwrap_or(false);
                    if close {
                        exit.store(true, Ordering::Relaxed);
                    }
                });
            }
            UserEvent::Output(output) => {
                if let Some(output) = output.take() {
                    self.op_queue.set_working(false);
                    self.handle_output(wgpu, output);
                }
            }
            UserEvent::RepaintRequest(request_repaint_info) => {
                self.delay = self.delay.min(request_repaint_info.delay);
            }
            UserEvent::Wake => (),
        };
    }

    pub fn handle_window_event(&mut self, _wgpu: &WgpuState, event: &WindowEvent) {
        match event {
            WindowEvent::CursorMoved { position, .. } => {
                self.mouse_position.x = position.x as f32;
                self.mouse_position.y = position.y as f32;
            }
            WindowEvent::DroppedFile(path) => {
                self.op_queue.cache.clear();
                self.queue(Op::LoadPath(path.to_path_buf(), true));
            }
            _ => (),
        };
    }

    pub fn set_bar_size(&mut self, wgpu: &WgpuState) {
        if wgpu.window.fullscreen().is_none() && !self.zen_mode {
            self.top_bar_size = TOP_BAR_SIZE * wgpu.scale_factor as f32;
            self.bottom_bar_size = BOTTOM_BAR_SIZE * wgpu.scale_factor as f32;
            if let Some(ref mut view) = self.image_view
                && view.len_frames() > 1
            {
                self.bottom_bar_size += BOTTOM_BAR_SIZE * wgpu.scale_factor as f32;
            }
        } else {
            self.top_bar_size = 0.0;
            self.bottom_bar_size = 0.0;
        }
    }

    pub fn handle_ui(&mut self, wgpu: &WgpuState, ctx: &egui::Context) {
        self.set_bar_size(wgpu);
        if self.op_queue.working() {
            ctx.set_cursor_icon(CursorIcon::Progress);
        }

        self.resize_ui(ctx);
        self.preferences_ui(ctx);
        self.help_ui(ctx);
        self.color_ui(ctx);
        self.color_space_ui(ctx);
        self.metadata_ui(ctx);
        self.crop_ui(ctx);

        self.main_area(wgpu, ctx);

        if wgpu.window.fullscreen().is_none() && !self.zen_mode {
            self.menu_bar(wgpu, ctx);
            self.bottom_bar(ctx);
            self.gif_player_bar(ctx);
        }

        self.dialog_manager.update(ctx, self.size, &mut self.enter);
    }

    pub fn handle_input(&mut self, wgpu: &WgpuState, ui: &egui::Ui, focused: bool) {
        ui.input_mut(|input| {
            use egui::{Key::*, KeyboardShortcut};

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Delete,
            }) && focused
                && let Some(ref view) = self.image_view
                && let Some(ref path) = view.path
            {
                delete(
                    path.clone(),
                    self.dialog_manager.get_proxy(),
                    self.proxy.clone(),
                );
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: I,
            }) {
                self.zen_mode = !self.zen_mode;
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: H,
            }) {
                self.help_visible = true
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: O,
            }) {
                load_image::open(self.proxy.clone(), wgpu, false)
            }
            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: S,
            }) {
                save_image::open(self.current_filename.clone(), self.proxy.clone(), wgpu)
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: Q,
            }) {
                let _ = self.proxy.send_event(UserEvent::Exit);
            }
            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: N,
            }) {
                new_window()
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: L,
            }) {
                self.largest_fit();
            }
            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: B,
            }) {
                self.best_fit();
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Q,
            }) && self.image_view.is_some()
                && focused
            {
                self.queue(Op::Rotate(-1))
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: E,
            }) && self.image_view.is_some()
                && focused
            {
                self.queue(Op::Rotate(1))
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: F5,
            }) && let Some(image) = self.image_view.as_ref()
                && let Some(path) = &image.path
            {
                let buf = path.to_path_buf();
                self.queue(Op::LoadPath(buf, false));
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: Z,
            }) {
                self.queue(Op::Undo);
            }
            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: Y,
            }) {
                self.queue(Op::Redo);
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: R,
            }) {
                self.resize.visible = true;
            }

            if (input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: ArrowRight,
            }) || input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: D,
            }) || input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: H,
            })) && self.view_available()
                && !self.image_view.as_ref().unwrap().cropping()
                && !self.color_visible
                && !self.color_space_visible
                && !self.metadata_visible
                && !self.resize.visible
                && focused
            {
                self.queue(Op::Next);
            }

            if (input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: ArrowLeft,
            }) || input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: A,
            }) || input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: L,
            })) && self.view_available()
                && !self.image_view.as_ref().unwrap().cropping()
                && !self.color_visible
                && !self.color_space_visible
                && !self.metadata_visible
                && !self.resize.visible
                && focused
            {
                self.queue(Op::Prev);
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: W,
            }) {
                self.queue(Op::Close);
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: F11,
            }) || input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::CTRL,
                logical_key: F,
            }) {
                let fullscreen = wgpu.window.fullscreen();
                if fullscreen.is_some() {
                    wgpu.window.set_fullscreen(None);
                } else {
                    wgpu.window
                        .set_fullscreen(Some(Fullscreen::Borderless(None)));
                }
                self.largest_fit();
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Escape,
            }) {
                if self.view_available() && self.image_view.as_ref().unwrap().cropping() {
                    self.image_view.as_mut().unwrap().cancel_crop();
                }
                self.help_visible = false;
                self.color_visible = false;
                self.metadata_visible = false;
                self.resize.visible = false;
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Enter,
            }) {
                self.enter = true;
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Plus,
            }) && focused
            {
                self.zoom(1.0, self.size / 2.0);
            }

            if input.consume_shortcut(&KeyboardShortcut {
                modifiers: Modifiers::NONE,
                logical_key: Minus,
            }) && focused
            {
                self.zoom(-1.0, self.size / 2.0);
            }

            self.zoom(
                input.raw_scroll_delta.y / 40.0 * PREFERENCES.lock().unwrap().zoom_speed,
                self.mouse_position,
            );

            if focused {
                for event in &input.events {
                    match event {
                        Event::Copy => {
                            if self.view_available() {
                                self.queue(Op::Copy);
                            }
                        }
                        Event::Cut => {
                            if self.view_available() {
                                self.image_view.as_mut().unwrap().start_crop()
                            }
                        }
                        _ => (),
                    }
                }
                input
                    .events
                    .retain(|e| !matches!(e, Event::Copy | Event::Cut));
            }

            let nums = [Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9];
            for (i, num) in nums.into_iter().enumerate() {
                if input.consume_shortcut(&KeyboardShortcut {
                    modifiers: Modifiers::CTRL,
                    logical_key: num,
                }) && focused
                    && let Some(ref mut view) = self.image_view
                {
                    view.scale = (i + 1) as f32;
                }
            }
        });
    }

    pub fn main_area(&mut self, wgpu: &WgpuState, ctx: &egui::Context) {
        let frame = egui::Frame::dark_canvas(&Style::default()).multiply_with_opacity(0.0);
        egui::CentralPanel::default().frame(frame).show(ctx, |ui| {
            if self.image_view.is_none() {
                ui.centered_and_justified(|ui| {
                    ui.label(
                        RichText::new("Open File: Ctrl + O\n\nPaste: Ctrl + V\n\nHelp: Ctrl + H")
                            .size(20.0),
                    );
                });
            }

            if let Some(ref mut view) = self.image_view {
                view.handle_drag(ui);
            }

            {
                let size = ui.available_size();
                let r = ui.allocate_response(size, egui::Sense::click());
                if r.clicked() {
                    r.request_focus();
                }
                self.handle_input(wgpu, ui, r.has_focus());
            }
        });
    }

    fn gif_player_bar(&mut self, ctx: &egui::Context) {
        let Some(ref mut view) = self.image_view else {
            return;
        };

        if view.len_frames() < 2 {
            return;
        }

        TopBottomPanel::bottom("gif_player")
            .exact_height(BOTTOM_BAR_SIZE)
            .show(ctx, |ui| {
                ui.with_layout(egui::Layout::left_to_right(egui::Align::LEFT), |ui| {
                    let icon = if view.playing { "⏸" } else { "▶" };
                    ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
                        if ui
                            .small_button(egui::RichText::new(icon).size(11.0))
                            .clicked()
                        {
                            view.playing = !view.playing;
                        }
                    });

                    ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
                        let before = view.index;
                        let frames = view.len_frames() - 1;
                        ui.add(egui::Slider::new(&mut view.index, 0..=frames));
                        if view.index != before {
                            view.playing = false;
                        }
                    });
                });
            });
    }

    fn bottom_bar(&mut self, ctx: &egui::Context) {
        TopBottomPanel::bottom("bottom")
            .exact_height(BOTTOM_BAR_SIZE)
            .show(ctx, |ui| {
                ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
                    if self.image_view.is_some() {
                        ui.add_enabled_ui(
                            self.view_available()
                                && !self.image_view.as_ref().unwrap().cropping()
                                && !self.color_visible
                                && !self.color_space_visible
                                && !self.metadata_visible
                                && !self.resize.visible,
                            |ui| {
                                if ui.small_button("⬅").clicked() {
                                    self.queue(Op::Prev);
                                }
                                if ui.small_button("➡").clicked() {
                                    self.queue(Op::Next);
                                }
                            },
                        );
                    }

                    if let Some(image) = self.image_view.as_mut() {
                        ui.label(self.current_filename.to_string());
                        ui.label(format!("{} x {}", image.size.x, image.size.y));
                        ui.label(format!("Zoom: {}%", (image.scale * 100.0).round()));

                        let g = image.image_data.read();
                        let buf = g.as_ref().unwrap().frames[0].buffer();
                        let mut color_space = color_type_to_str(buf.color()).to_string();

                        {
                            let pos = (((self.mouse_position
                                - image.position
                                - (image.rotated_size() / 2.0) * image.scale)
                                + image.rotated_size() * image.scale)
                                / image.scale)
                                .map(|v| v.floor() as i64);

                            if pos.x >= 0
                                && pos.y >= 0
                                && pos.x < image.rotated_size().x as i64
                                && pos.y < image.rotated_size().y as i64
                            {
                                let guard = image.image_data.read().unwrap();
                                let frame = &guard.frames[image.index];
                                let buffer = frame.buffer();

                                let mut pos = pos.map(|v| v as u32);
                                match image.rotation() {
                                    0 => (),
                                    1 => {
                                        mem::swap(&mut pos.x, &mut pos.y);
                                        pos.y = image.size.y as u32 - pos.y - 1;
                                    }
                                    2 => {
                                        pos.x = image.size.x as u32 - pos.x - 1;
                                        pos.y = image.size.y as u32 - pos.y - 1;
                                    }
                                    3 => {
                                        mem::swap(&mut pos.x, &mut pos.y);
                                        pos.x = image.size.x as u32 - pos.x - 1;
                                    }
                                    _ => panic!("rotated more then 360 degrees"),
                                }

                                if image.horizontal_flip {
                                    pos.x = image.size.x as u32 - pos.x - 1;
                                }

                                if image.vertical_flip {
                                    pos.y = image.size.y as u32 - pos.y - 1;
                                }

                                fn p2s<P>(p: P) -> String
                                where
                                    P: image::Pixel,
                                    <P as image::Pixel>::Subpixel: ToString,
                                {
                                    let channels = p.channels();
                                    let mut out = String::new();

                                    for i in 0..channels.len() {
                                        out.push_str(&channels[i].to_string());
                                        if i < channels.len() - 1 {
                                            out.push_str(", ");
                                        }
                                    }

                                    out
                                }

                                #[rustfmt::skip]
                            let color_str = match buffer {
                                DynamicImage::ImageLuma8(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageLumaA8(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgb8(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgba8(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageLuma16(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageLumaA16(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgb16(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgba16(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgb32F(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                DynamicImage::ImageRgba32F(b) => p2s(*b.get_pixel(pos.x, pos.y)),
                                _ => panic!("Unknown color space name. This is a bug."),
                            };
                                color_space = format!("{color_space}: {color_str}");
                            }
                            if ui.label(color_space).clicked() {
                                self.color_space_visible = true;
                            }
                        }
                    }
                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                        if self.op_queue.working() {
                            ui.add(egui::widgets::Spinner::new().size(14.0));
                        }
                    });
                });
            });
    }

    pub fn update(&mut self, _wgpu: &WgpuState) -> (bool, Duration) {
        self.delay = Duration::MAX;
        if let Some(ref mut image) = self.image_view {
            self.delay = self.delay.min(image.animate());
        }

        if PREFERENCES.lock().unwrap().auto_center
            && let Some(ref mut image) = self.image_view
        {
            let image_size = image.real_size();
            let mut window_size = self.size;
            window_size.y = window_size.y - self.top_bar_size - self.bottom_bar_size;

            const MARGIN: f32 = 50.0;

            if image_size.x < window_size.x {
                image.position.x = self.size.x / 2.0;
            } else {
                if image.position.x - image_size.x / 2.0 > 0.0 + MARGIN {
                    image.position.x = image_size.x / 2.0 + MARGIN;
                }

                if image.position.x + image_size.x / 2.0 < window_size.x - MARGIN {
                    image.position.x = window_size.x - image_size.x / 2.0 - MARGIN;
                }
            }

            if image_size.y < window_size.y {
                image.position.y = self.size.y / 2.0;
            } else {
                if image.position.y - image_size.y / 2.0 > self.top_bar_size + MARGIN {
                    image.position.y = image_size.y / 2.0 + self.top_bar_size + MARGIN;
                }

                if image.position.y + image_size.y / 2.0
                    < window_size.y + self.top_bar_size - MARGIN
                {
                    image.position.y =
                        (window_size.y - image_size.y / 2.0) + self.top_bar_size - MARGIN;
                }
            }
        }

        self.enter = false;
        (self.exit.load(Ordering::Relaxed), self.delay)
    }

    pub fn resize_ui(&mut self, ctx: &egui::Context) {
        if self.resize.visible {
            let mut open = self.image_view.is_some();
            let mut resized = false;
            egui::Window::new("Resize")
                .id(egui::Id::new("resize window"))
                .collapsible(false)
                .resizable(false)
                .pivot(egui::Align2::CENTER_CENTER)
                .default_pos(p2(Point2::from_vec(self.size / 2.0)))
                .open(&mut open)
                .show(ctx, |ui| {
                    egui::Grid::new("resize grid").show(ui, |ui| {
                        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                            ui.label("Width: ");
                        });
                        let w_focus = ui.text_edit_singleline(&mut self.resize.width).has_focus();
                        ui.end_row();
                        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                            ui.label("Height: ");
                        });
                        let h_focus = ui.text_edit_singleline(&mut self.resize.height).has_focus();
                        ui.end_row();

                        self.resize.width.retain(|c| c.is_ascii_digit());
                        self.resize.height.retain(|c| c.is_ascii_digit());

                        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                            ui.label("Maintain aspect ratio:");
                        });
                        ui.checkbox(&mut self.resize.maintain_aspect_ratio, "");
                        ui.end_row();

                        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                            ui.label("Resample: ");
                        });
                        let selected = &mut self.resize.resample;
                        egui::ComboBox::new("filter", "")
                            .selected_text(filter_name(selected))
                            .show_ui(ui, |ui| {
                                ui.selectable_value(
                                    selected,
                                    FilterType::Nearest,
                                    filter_name(&FilterType::Nearest),
                                );
                                ui.selectable_value(
                                    selected,
                                    FilterType::Triangle,
                                    filter_name(&FilterType::Triangle),
                                );
                                ui.selectable_value(
                                    selected,
                                    FilterType::CatmullRom,
                                    filter_name(&FilterType::CatmullRom),
                                );
                                ui.selectable_value(
                                    selected,
                                    FilterType::Gaussian,
                                    filter_name(&FilterType::Gaussian),
                                );
                                ui.selectable_value(
                                    selected,
                                    FilterType::Lanczos3,
                                    filter_name(&FilterType::Lanczos3),
                                );
                            });
                        ui.end_row();
                        ui.end_row();

                        let width = self.resize.width.parse::<u32>();
                        let height = self.resize.height.parse::<u32>();

                        if self.resize.maintain_aspect_ratio
                            && w_focus
                            && let Ok(width) = width
                        {
                            let size = self.image_view.as_ref().unwrap().size;
                            let ratio = width as f32 / size.x;
                            self.resize.height = ((ratio * size.y) as u32).to_string();
                        }

                        if self.resize.maintain_aspect_ratio
                            && h_focus
                            && let Ok(height) = height
                        {
                            let size = self.image_view.as_ref().unwrap().size;
                            let ratio = height as f32 / size.y;
                            self.resize.width = ((ratio * size.x) as u32).to_string();
                        }

                        ui.with_layout(
                            egui::Layout::top_down_justified(egui::Align::Center),
                            |ui| {
                                if ui.add(Button::new("Cancel")).clicked() {
                                    resized = true;
                                }
                            },
                        );

                        ui.with_layout(
                            egui::Layout::top_down_justified(egui::Align::Center),
                            |ui| {
                                if ui
                                    .add_enabled(
                                        width.is_ok() && height.is_ok() && self.view_available(),
                                        Button::new("Resize"),
                                    )
                                    .clicked()
                                    || self.enter
                                {
                                    let width = width.unwrap();
                                    let height = height.unwrap();
                                    self.queue(Op::Resize(
                                        Vector2::new(width, height),
                                        self.resize.resample,
                                    ));
                                    resized = true;
                                    self.enter = false;
                                }
                            },
                        );
                    });
                });
            self.resize.visible = open && !resized;
        }
    }

    fn crop_ui(&mut self, ctx: &egui::Context) {
        let mut crop = None;
        if let Some(ref mut view) = self.image_view {
            let mut cancel = false;
            let size = view.rotated_size();
            if let Some(rect) = view.crop.rect.as_mut() {
                egui::Window::new("Crop")
                    .id(egui::Id::new("crop window"))
                    .collapsible(false)
                    .resizable(false)
                    .pivot(egui::Align2::CENTER_CENTER)
                    .default_pos(p2(Point2::from_vec(self.size / 2.0)))
                    .show(ctx, |ui| {
                        egui::Grid::new("crop grid").show(ui, |ui| {
                            ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                                ui.label("X: ");
                            });
                            ui.text_edit_singleline(&mut view.crop.x);
                            ui.end_row();
                            ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                                ui.label("Y: ");
                            });
                            ui.text_edit_singleline(&mut view.crop.y);
                            ui.end_row();

                            ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                                ui.label("Width: ");
                            });
                            ui.text_edit_singleline(&mut view.crop.width);
                            ui.end_row();
                            ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
                                ui.label("Height: ");
                            });
                            ui.text_edit_singleline(&mut view.crop.height);
                            ui.end_row();
                            ui.end_row();

                            rect.position.x =
                                min!(view.crop.x.parse::<u32>().unwrap_or(0) as f32, size.x - 1.0);
                            rect.position.y =
                                min!(view.crop.y.parse::<u32>().unwrap_or(0) as f32, size.y - 1.0);

                            rect.size.x = (view.crop.width.parse::<u32>().unwrap_or(size.x as u32)
                                as f32)
                                .clamp(1.0, size.x - rect.x());
                            rect.size.y = (view.crop.height.parse::<u32>().unwrap_or(size.y as u32)
                                as f32)
                                .clamp(1.0, size.y - rect.y());

                            if view.crop.x.parse::<u32>().is_ok() {
                                view.crop.x = rect.x().to_string();
                            }

                            if view.crop.y.parse::<u32>().is_ok() {
                                view.crop.y = rect.y().to_string();
                            }

                            if view.crop.width.parse::<u32>().is_ok() {
                                view.crop.width = rect.width().to_string();
                            }

                            if view.crop.height.parse::<u32>().is_ok() {
                                view.crop.height = rect.height().to_string();
                            }

                            view.crop.x.retain(|c| c.is_ascii_digit());
                            view.crop.y.retain(|c| c.is_ascii_digit());
                            view.crop.width.retain(|c| c.is_ascii_digit());
                            view.crop.height.retain(|c| c.is_ascii_digit());

                            ui.with_layout(
                                egui::Layout::top_down_justified(egui::Align::Center),
                                |ui| {
                                    if ui
                                        .add(
                                            Button::new("Cancel")
                                                .wrap_mode(egui::TextWrapMode::Extend),
                                        )
                                        .clicked()
                                    {
                                        cancel = true;
                                    }
                                },
                            );

                            ui.with_layout(
                                egui::Layout::top_down_justified(egui::Align::Center),
                                |ui| {
                                    if ui
                                        .add(
                                            Button::new("Crop")
                                                .wrap_mode(egui::TextWrapMode::Extend),
                                        )
                                        .clicked()
                                        || self.enter
                                    {
                                        crop = Some(*rect);
                                        cancel = true;
                                        self.enter = false;
                                    }
                                },
                            );
                        });
                    });
            }
            if cancel {
                view.cancel_crop();
            }
        }
        if let Some(rect) = crop {
            self.queue(Op::Crop(rect));
        }
    }

    pub fn queue(&mut self, op: Op) {
        self.op_queue
            .queue(op, self.image_view.as_ref().map(|v| v.as_ref()))
    }

    fn zoom(&mut self, zoom: f32, mouse_position: Vector2<f32>) {
        if let Some(ref mut image) = self.image_view {
            let old_scale = image.scale;
            image.scale += image.scale * zoom / 10.0;

            let new_size = image.scaled();
            if (new_size.x < 1.0 || new_size.y < 1.0)
                && old_scale >= image.scale
                && image.scale < 1.0
            {
                image.scale = min!(old_scale, 1.0);
            } else {
                let mouse_to_center = image.position - mouse_position;
                image.position -= mouse_to_center * (old_scale - image.scale) / old_scale;
            }
            self.resize_mode = ResizeMode::Original;
        }
    }

    pub fn best_fit(&mut self) {
        if let Some(ref mut view) = self.image_view {
            let size = view.rotated_size();
            let scaling = min!(
                self.size.x / size.x,
                (self.size.y - self.top_bar_size - self.bottom_bar_size) / size.y
            );
            view.scale = min!(scaling, 1.0);
            view.position.x = self.size.x / 2.0;
            view.position.y = view.real_size().y / 2.0 + self.top_bar_size;
            self.resize_mode = ResizeMode::BestFit;
        }
    }

    pub fn largest_fit(&mut self) {
        if let Some(ref mut view) = self.image_view {
            let size = view.rotated_size();
            let scaling = min!(
                self.size.x / size.x,
                (self.size.y - self.top_bar_size - self.bottom_bar_size) / size.y
            );
            view.scale = scaling;
            view.position.x = self.size.x / 2.0;
            view.position.y = view.real_size().y / 2.0 + self.top_bar_size;
            self.resize_mode = ResizeMode::LargestFit;
        }
    }

    pub fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
        self.size.x = size.width as f32;
        self.size.y = size.height as f32;

        match self.resize_mode {
            ResizeMode::Original => {}
            ResizeMode::LargestFit => {
                self.largest_fit();
            }
            ResizeMode::BestFit => {
                self.best_fit();
            }
        }
    }

    pub fn handle_paste(&mut self) {
        if !self.op_queue.working() {
            self.queue(Op::Paste);
        }
    }

    pub fn new(
        wgpu: &WgpuState,
        proxy: EventLoopProxy<UserEvent>,
        size: [f32; 2],
        zen_mode: bool,
        no_cache: bool,
    ) -> Self {
        let dialog_manager = DialogManager::new(proxy.clone());
        App {
            exit: Arc::new(AtomicBool::new(false)),
            delay: Duration::MAX,
            modifiers: ModifiersState::empty(),
            image_renderer: image_renderer::Renderer::new(wgpu),
            crop_renderer: crop_renderer::Renderer::new(wgpu),
            image_view: None,
            op_queue: OpQueue::new(proxy.clone(), dialog_manager.get_proxy(), no_cache),
            dialog_manager,
            color_type: ColorType::Rgba8,
            size: Vector2::from(size),
            top_bar_size: TOP_BAR_SIZE,
            bottom_bar_size: BOTTOM_BAR_SIZE,
            proxy,
            mouse_position: Vector2::zero(),
            current_filename: String::new(),
            resize: Resize::default(),
            help_visible: false,
            color_visible: false,
            color_space_visible: false,
            metadata_visible: false,
            preferences_visible: false,
            resize_mode: ResizeMode::Original,
            enter: false,
            zen_mode,
        }
    }
}

pub fn delete(
    path: std::path::PathBuf,
    dialog_proxy: DialogProxy,
    proxy: EventLoopProxy<UserEvent>,
) {
    dialog_proxy.spawn_dialog("Move to trash", move |ui, enter| {
        ui.label("Are you sure you want to move this to trash?");

        ui.with_layout(egui::Layout::left_to_right(egui::Align::LEFT), |ui| {
            if ui.button("Yes").clicked() {
                let _ = proxy.send_event(UserEvent::QueueDelete(path.clone()));
                return Some(());
            }

            if ui.button("No").clicked() {
                return Some(());
            }

            if *enter {
                *enter = false;
                let _ = proxy.send_event(UserEvent::QueueDelete(path.clone()));
                return Some(());
            }

            None
        })
        .inner
    });
}

fn new_window() {
    let _ = Command::new(std::env::current_exe().unwrap()).spawn();
}

fn filter_name(filter: &FilterType) -> &'static str {
    match filter {
        FilterType::Nearest => "Nearest Neighbor",
        FilterType::Triangle => "Linear Filter",
        FilterType::CatmullRom => "Cubic Filter",
        FilterType::Gaussian => "Gaussian Filter",
        FilterType::Lanczos3 => "Lanczos",
    }
}

pub fn color_type_to_str(color_type: ColorType) -> &'static str {
    match color_type {
        ColorType::L8 => "Luma8",
        ColorType::La8 => "LumaA8",
        ColorType::Rgb8 => "Rgb8",
        ColorType::Rgba8 => "Rgba8",
        ColorType::L16 => "Luma16",
        ColorType::La16 => "LumaA16",
        ColorType::Rgb16 => "Rgb16",
        ColorType::Rgba16 => "Rgba16",
        ColorType::Rgb32F => "Rgb32F",
        ColorType::Rgba32F => "Rgba32F",
        _ => panic!("Unknown color space name. This is a bug."),
    }
}
