use std::{
    error, fmt, fs,
    path::{Path, PathBuf},
    thread,
};

use image::metadata::Orientation;
use rexif::ExifTag;
use winit::event_loop::EventLoopProxy;

use crate::{
    WgpuState,
    image_io::load::*,
    util::{ImageData, UserEvent, extensions::*},
};

#[derive(Debug)]
pub enum LoadError {
    Io(std::io::Error),
    Decoding(String),
}

impl fmt::Display for LoadError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            LoadError::Io(ref e) => e.fmt(f),
            LoadError::Decoding(ref source) => {
                write!(f, "error decoding image: {:?}", source)
            }
        }
    }
}

impl error::Error for LoadError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            LoadError::Io(ref e) => Some(e),
            LoadError::Decoding(_) => None,
        }
    }
}

impl From<std::io::Error> for LoadError {
    fn from(err: std::io::Error) -> LoadError {
        LoadError::Io(err)
    }
}

pub fn open(proxy: EventLoopProxy<UserEvent>, wgpu: &WgpuState, folder: bool) {
    let ext: Vec<_> = EXTENSIONS.iter().copied().collect();
    let raw: Vec<_> = RAW.iter().copied().collect();
    let dialog = rfd::FileDialog::new()
        .set_parent(&wgpu.window)
        .add_filter("All", &ext)
        .add_filter("png", &["png", "apng"])
        .add_filter("svg", &["svg"])
        .add_filter("jpeg", &["jpg", "jpeg", "jpe", "jif", "jfif"])
        .add_filter("Photoshop", &["psd"])
        .add_filter("Animated", &["apng", "gif", "webp"])
        .add_filter("Raw", &raw);
    thread::spawn(move || {
        let pick = if folder {
            dialog.pick_folder()
        } else {
            dialog.pick_file()
        };
        if let Some(file) = pick {
            let _ = proxy.send_event(UserEvent::QueueLoad(file));
        }
    });
}

pub fn load_uncached(path: impl AsRef<Path>) -> Result<ImageData, LoadError> {
    let bytes = fs::read(path.as_ref())?;
    load_from_bytes(&bytes, Some(path.as_ref().to_path_buf()))
}

pub fn load_from_bytes(bytes: &[u8], path_buf: Option<PathBuf>) -> Result<ImageData, LoadError> {
    let source = path_buf
        .as_ref()
        .map(|path| path.to_string_lossy().into())
        .unwrap_or_else(|| String::from("from stdin"));
    let extension = path_buf
        .unwrap_or_default()
        .extension()
        .unwrap_or_default()
        .to_string_lossy()
        .to_lowercase();

    let mut loaders = [
        load_raw,
        load_svg,
        load_psd,
        load_raster,
        load_un_detectable_raster,
        load_jxl,
        load_heif,
    ];

    if HEIF.contains(&extension.as_str()) {
        loaders.swap(0, 6);
    } else if JXL.contains(&extension.as_str()) {
        loaders.swap(0, 5);
    } else if RASTER.contains(&extension.as_str()) {
        loaders.swap(0, 3);
    } else if VECTOR.contains(&extension.as_str()) {
        loaders.swap(0, 1);
    } else if PHOTOSHOP.contains(&extension.as_str()) {
        loaders.swap(0, 2);
    } else if UNDETECTABLE_RASTER.contains(&extension.as_str()) {
        loaders.swap(0, 4);
    }

    let mut orientation = None;
    let mut metadata = Vec::new();
    if let Ok(exif) = rexif::parse_buffer_quiet(bytes).0 {
        for entry in exif.entries {
            if ExifTag::UnknownToMe != entry.tag {
                let text = entry.value_more_readable;
                metadata.push((entry.tag.to_string(), text.to_string()));
            }
            if ExifTag::Orientation == entry.tag {
                let raw = match entry.value {
                    rexif::TagValue::U16(vec) => vec.last().copied(),
                    _ => None,
                };
                if let Some(raw) = raw {
                    orientation = Orientation::from_exif(raw.min(255) as u8);
                }
            }
        }
    }

    for loader in loaders {
        if let Some(mut image) = loader(bytes) {
            if let Some(orientation) = orientation {
                for image in image.iter_mut() {
                    image.buffer_mut().apply_orientation(orientation);
                }
            }
            return Ok(ImageData::new(image, metadata));
        }
    }

    Err(LoadError::Decoding(source))
}
