use std::{
    mem,
    path::PathBuf,
    sync::{Arc, RwLock},
    thread,
    time::{Duration, Instant},
};

use cgmath::{Deg, Matrix4, Ortho, Vector2, Vector3, Vector4};
use image::GenericImageView;
use num_traits::Zero;
use winit::event_loop::EventLoopProxy;

use self::mosaic::Mosaic;
use super::op_queue::{Output, UserEventLoopProxyExt};
use crate::{
    WgpuState, max,
    rect::Rect,
    util::{Image, ImageData, UserEvent, matrix::OPENGL_TO_WGPU_MATRIX},
};

pub mod crop_renderer;
pub mod image_renderer;
pub mod mosaic;

mod crop;
use crop::Crop;

mod texture;

pub struct ImageView {
    pub mosaic: Vec<Mosaic>,
    pub size: Vector2<f32>,
    pub position: Vector2<f32>,
    pub scale: f32,
    rotation: i32,
    pub path: Option<PathBuf>,
    pub image_data: Arc<RwLock<Arc<ImageData>>>,
    pub last_frame: Instant,
    pub index: usize,
    pub horizontal_flip: bool,
    pub vertical_flip: bool,
    pub hue: f32,
    pub contrast: f32,
    pub brightness: f32,
    pub saturation: f32,
    pub grayscale: bool,
    pub invert: bool,
    pub crop: Crop,
    pub playing: bool,
}

impl ImageView {
    pub fn new(wgpu: &WgpuState, image_data: Arc<ImageData>, path: Option<PathBuf>) -> Self {
        let frames = &image_data.frames;
        let image = frames[0].buffer();
        let (width, height) = image.dimensions();
        let mosaic = Mosaic::from_images(wgpu, image_data.clone());

        Self {
            mosaic,
            size: Vector2::new(width as f32, height as f32),
            position: Vector2::zero(),
            scale: 1.0,
            rotation: 0,
            image_data: Arc::new(RwLock::new(image_data)),
            last_frame: Instant::now(),
            index: 0,
            horizontal_flip: false,
            vertical_flip: false,
            crop: Crop::new(),
            path,
            hue: 0.0,
            contrast: 0.0,
            brightness: 0.0,
            saturation: 0.0,
            grayscale: false,
            invert: false,
            playing: true,
        }
    }

    pub fn get_rotation_mat(&self) -> Matrix4<f32> {
        let rotation = Matrix4::from_angle_z(Deg((self.rotation * 90) as f32));
        let pre_rotation =
            Matrix4::from_translation(Vector3::new(self.size.x / 2.0, self.size.y / 2.0, 0.0));
        let post_rotation =
            Matrix4::from_translation(Vector3::new(-self.size.x / 2.0, -self.size.y / 2.0, 0.0));
        (pre_rotation * rotation) * post_rotation
    }

    pub fn get_flip_mat(&self) -> Matrix4<f32> {
        let hori = if self.horizontal_flip { -1.0 } else { 1.0 };

        let vert = if self.vertical_flip { -1.0 } else { 1.0 };

        let flip = Matrix4::from_nonuniform_scale(hori, vert, 1.0);
        let pre_rotation =
            Matrix4::from_translation(Vector3::new(self.size.x / 2.0, self.size.y / 2.0, 0.0));
        let post_rotation =
            Matrix4::from_translation(Vector3::new(-self.size.x / 2.0, -self.size.y / 2.0, 0.0));
        (pre_rotation * flip) * post_rotation
    }

    pub fn get_uniform(&self, size: Vector2<f32>) -> image_renderer::Uniform {
        let ortho: Matrix4<f32> = Ortho {
            left: 0.0,
            right: size.x,
            bottom: size.y,
            top: 0.0,
            near: 0.0,
            far: 1.0,
        }
        .into();

        let position = self.position - self.scaled() / 2.0;
        let scale = Matrix4::from_scale(self.scale);
        let translation = Matrix4::from_translation(Vector3::new(position.x, position.y, 0.0));
        let flip = self.get_flip_mat();
        let rotation = self.get_rotation_mat();
        let matrix = OPENGL_TO_WGPU_MATRIX * ortho * translation * scale * flip * rotation;

        image_renderer::Uniform {
            matrix,
            size: Vector2::new(size.x, size.y),
            hue: self.hue,
            contrast: self.contrast,
            brightness: self.brightness,
            saturation: self.saturation,
            grayscale: self.grayscale as u32,
            invert: self.invert as u32,
        }
    }

    pub fn scaled(&self) -> Vector2<f32> {
        self.size * self.scale
    }

    pub fn real_size(&self) -> Vector2<f32> {
        let mut vectors = vec![
            Vector4::new(0.0, 0.0, 0.0, 1.0),
            Vector4::new(0.0, self.size.y, 0.0, 1.0),
            Vector4::new(self.size.x, 0.0, 0.0, 1.0),
            Vector4::new(self.size.x, self.size.y, 0.0, 1.0),
        ];

        let rotation = Matrix4::from_angle_z(Deg((self.rotation * 90) as f32));

        let scale = Matrix4::from_scale(self.scale);

        let matrix = scale * rotation;

        for vector in &mut vectors {
            (*vector) = matrix * (*vector);
        }

        let mut size = Vector2::new(0.0, 0.0);

        for outer in &vectors {
            for inner in &vectors {
                size.x = max!((inner.x - outer.x).abs(), size.x);
                size.y = max!((inner.y - outer.y).abs(), size.y);
            }
        }

        size
    }

    pub fn flip_horizontal(&mut self) {
        self.horizontal_flip = !self.horizontal_flip;
    }

    pub fn flip_vertical(&mut self) {
        self.vertical_flip = !self.vertical_flip;
    }

    pub fn animate(&mut self) -> Duration {
        let guard = self.image_data.read().unwrap();
        let frames = &guard.frames;
        if !self.playing || frames.len() <= 1 {
            return Duration::MAX;
        }

        let now = Instant::now();
        let time_passed = now.duration_since(self.last_frame);
        let delay = frames[self.index].delay;

        if time_passed > delay {
            self.index += 1;
            if self.index >= frames.len() {
                self.index = 0;
            }

            self.last_frame = now;

            delay
        } else {
            delay - time_passed
        }
    }

    pub fn len_frames(&mut self) -> usize {
        let guard = self.image_data.read().unwrap();
        guard.frames.len()
    }

    pub fn crop(&self, cut: Rect, proxy: EventLoopProxy<UserEvent>) {
        let rotation = self.rotation;
        let image_data = self.image_data.clone();
        let old_rotation = self.rotation;
        thread::spawn(move || {
            let mut new_frames = Vec::new();
            let guard = image_data.read().unwrap();
            let frames = &guard.frames;
            for frame in frames {
                let buffer = match rotation {
                    0 => frame.buffer().clone(),
                    1 => frame.buffer().rotate90(),
                    2 => frame.buffer().rotate180(),
                    3 => frame.buffer().rotate270(),
                    _ => unreachable!(),
                };

                let image = buffer.crop_imm(
                    cut.x() as u32,
                    cut.y() as u32,
                    cut.width() as u32,
                    cut.height() as u32,
                );

                new_frames.push(Image::with_delay(image, frame.delay));
            }

            proxy.send_output(Output::Crop(new_frames, old_rotation));
        });
    }

    pub fn swap_frames(&mut self, wgpu: &WgpuState, frames: &mut Vec<Image>) {
        let mut guard = self.image_data.write().unwrap();
        mem::swap(&mut Arc::make_mut(&mut *guard).frames, frames);
        let (width, height) = guard.frames[0].buffer().dimensions();
        drop(guard);
        self.mosaic = Mosaic::from_images(wgpu, self.image_data.read().unwrap().clone());
        self.size = Vector2::new(width as f32, height as f32);
    }

    pub fn rotation(&self) -> i32 {
        self.rotation
    }

    pub fn rotate(&mut self, rot: i32) {
        self.rotation += rot;
        self.rotation %= 4;
        while self.rotation < 0 {
            self.rotation += 4;
        }
    }

    pub fn set_rotation(&mut self, rot: i32) {
        self.rotation = rot % 4;
    }

    pub fn swap_rotation(&mut self, rot: &mut i32) {
        std::mem::swap(&mut self.rotation, rot);
        self.rotation %= 4;
    }

    pub fn rotated_size(&self) -> Vector2<f32> {
        let mut size = self.size;
        if self.rotation % 2 != 0 {
            mem::swap(&mut size.x, &mut size.y);
        }
        size
    }

    pub fn cropping(&self) -> bool {
        self.crop.cropping()
    }

    pub fn start_crop(&mut self) {
        let size = self.rotated_size();
        let position = Vector2::new(0.0, 0.0);
        self.crop.rect = Some(Rect { position, size });
        self.crop.x = position.x.to_string();
        self.crop.y = position.y.to_string();
        self.crop.width = size.x.to_string();
        self.crop.height = size.y.to_string();
    }

    pub fn cancel_crop(&mut self) {
        self.crop.rect = None;
    }

    pub fn handle_drag(&mut self, ui: &mut egui::Ui) {
        let dragging = self
            .crop
            .handle_drag(ui, self.position, self.rotated_size(), self.scale);

        if !dragging {
            #[allow(deprecated)]
            let res = ui.interact_bg(egui::Sense::drag());
            if res.dragged_by(egui::PointerButton::Primary)
                || res.dragged_by(egui::PointerButton::Middle)
            {
                let vec2 = res.drag_delta();
                self.position += Vector2::from((vec2.x, vec2.y));
            }
        }
    }
}
