use std::{path::PathBuf, time::Duration};

use image::{Delay, DynamicImage, Frame, ImageBuffer, Luma, LumaA, Primitive, Rgb, Rgba};

use crate::app::op_queue::Output;

pub mod extensions;
pub mod matrix;

#[macro_export]
macro_rules! min {
    ($x: expr) => ($x);
    ($x: expr, $($z: expr),+) => {{
        let y = min!($($z),*);
        if $x < y {
            $x
        } else {
            y
        }
    }}
}

#[macro_export]
macro_rules! max {
    ($x: expr) => ($x);
    ($x: expr, $($z: expr),+) => {{
        let y = max!($($z),*);
        if $x > y {
            $x
        } else {
            y
        }
    }}
}

#[derive(Clone, Debug)]
pub struct Image {
    pub image: DynamicImage,
    pub delay: Duration,
}

impl Image {
    pub fn new(image: DynamicImage) -> Self {
        Image {
            image,
            delay: Duration::default(),
        }
    }

    pub fn with_delay(image: DynamicImage, delay: Duration) -> Self {
        Image { image, delay }
    }

    pub fn buffer(&self) -> &DynamicImage {
        &self.image
    }

    pub fn buffer_mut(&mut self) -> &mut DynamicImage {
        &mut self.image
    }
}

impl From<ImageBuffer<Rgba<u8>, Vec<u8>>> for Image {
    fn from(buffer: ImageBuffer<Rgba<u8>, Vec<u8>>) -> Self {
        Image {
            image: DynamicImage::ImageRgba8(buffer),
            delay: Duration::default(),
        }
    }
}

impl From<Frame> for Image {
    fn from(frame: Frame) -> Self {
        let (num, deno) = frame.delay().numer_denom_ms();
        let delay = Duration::from_millis((num / deno) as u64);
        let buffer = frame.into_buffer();
        Image {
            image: DynamicImage::ImageRgba8(buffer),
            delay,
        }
    }
}

impl From<Image> for Frame {
    fn from(image: Image) -> Frame {
        let duration = image.delay;
        let frame = image.image.to_rgba8();
        Frame::from_parts(frame, 0, 0, Delay::from_saturating_duration(duration))
    }
}

pub enum UserEvent {
    ErrorMessage(String),
    QueueLoad(PathBuf),
    QueueSave(PathBuf),
    QueueDelete(PathBuf),
    Output(Option<Output>),
    LoadBytes(Vec<u8>),
    RepaintRequest(egui::RequestRepaintInfo),
    Wake,
    Exit,
}

#[derive(Debug, Clone)]
pub struct ImageData {
    pub frames: Vec<Image>,
    pub metadata: Vec<(String, String)>,
}

impl ImageData {
    pub fn new(frames: Vec<Image>, metadata: Vec<(String, String)>) -> Self {
        Self { frames, metadata }
    }
}

impl From<Vec<Image>> for ImageData {
    fn from(frames: Vec<Image>) -> Self {
        Self {
            frames,
            metadata: Vec::new(),
        }
    }
}

pub fn p2(v: impl Into<mint::Point2<f32>>) -> mint::Point2<f32> {
    v.into()
}

pub fn v2(v: impl Into<mint::Vector2<f32>>) -> mint::Vector2<f32> {
    v.into()
}

#[allow(dead_code)]
pub trait HasAlpha {
    fn has_alpha(&self) -> bool;
}

impl HasAlpha for DynamicImage {
    fn has_alpha(&self) -> bool {
        match self {
            DynamicImage::ImageLuma8(b) => b.has_alpha(),

            DynamicImage::ImageLumaA8(b) => b.has_alpha(),

            DynamicImage::ImageRgb8(b) => b.has_alpha(),

            DynamicImage::ImageRgba8(b) => b.has_alpha(),

            DynamicImage::ImageLuma16(b) => b.has_alpha(),

            DynamicImage::ImageLumaA16(b) => b.has_alpha(),

            DynamicImage::ImageRgb16(b) => b.has_alpha(),

            DynamicImage::ImageRgba16(b) => b.has_alpha(),

            DynamicImage::ImageRgb32F(b) => b.has_alpha(),

            DynamicImage::ImageRgba32F(b) => b.has_alpha(),

            _ => panic!("Unknown color space name. This is a bug."),
        }
    }
}

impl<T: Primitive> HasAlpha for ImageBuffer<Luma<T>, Vec<T>> {
    fn has_alpha(&self) -> bool {
        false
    }
}

impl<T: Primitive> HasAlpha for ImageBuffer<LumaA<T>, Vec<T>> {
    fn has_alpha(&self) -> bool {
        true
    }
}

// TODO switch to generics when the image crate fixes its shit.

impl HasAlpha for ImageBuffer<Rgb<u8>, Vec<u8>> {
    fn has_alpha(&self) -> bool {
        false
    }
}

impl HasAlpha for ImageBuffer<Rgb<u16>, Vec<u16>> {
    fn has_alpha(&self) -> bool {
        false
    }
}

impl HasAlpha for ImageBuffer<Rgb<f32>, Vec<f32>> {
    fn has_alpha(&self) -> bool {
        false
    }
}

impl HasAlpha for ImageBuffer<Rgba<u8>, Vec<u8>> {
    fn has_alpha(&self) -> bool {
        true
    }
}

impl HasAlpha for ImageBuffer<Rgba<u16>, Vec<u16>> {
    fn has_alpha(&self) -> bool {
        true
    }
}

impl HasAlpha for ImageBuffer<Rgba<f32>, Vec<f32>> {
    fn has_alpha(&self) -> bool {
        true
    }
}
