#![allow(clippy::large_enum_variant)]

mod animated;
mod editor;

use std::io::{Cursor, Read};
use std::sync::Mutex;
use std::sync::mpsc::{Receiver, Sender, channel};

pub use editor::ImgEditor;
use glycin_utils::image_rs::Handler;
use glycin_utils::*;
use gufo_common::cicp::Cicp;
use image::{AnimationDecoder, ImageDecoder, ImageResult, Limits, codecs};

type Reader = Cursor<Vec<u8>>;
type FrameReceiver = Receiver<Result<(Frame<LocalMemory>, bool), ProcessError>>;
type FrameSender = Sender<Result<(Frame<LocalMemory>, bool), ProcessError>>;

#[cfg(feature = "builtin")]
#[derive(Debug, Clone)]
pub struct BuiltinImageRs;

#[cfg(feature = "builtin")]
impl Builtin for BuiltinImageRs {
    fn config(&self) -> &'static str {
        include_str!("../glycin-image-rs.conf")
    }

    fn name(&self) -> &'static str {
        "image-rs"
    }
}

#[derive(Default)]
pub struct ImgDecoder {
    pub format: Mutex<Option<ImageRsFormat<Reader>>>,
    pub thread: Mutex<Option<(std::thread::JoinHandle<()>, FrameReceiver)>>,
    pub cicp: Mutex<Option<Cicp>>,
}

impl LoaderImplementation for ImgDecoder {
    fn load<B: ByteData, R: Read>(
        mut stream: R,
        mime_type: String,
        _details: InitializationDetails,
    ) -> Result<(Self, ImageDetails<B>), ProcessError> {
        image_extras::register();

        let mut buf = Vec::new();
        stream.read_to_end(&mut buf).internal_error()?;
        let data = Cursor::new(buf);

        let mut format = ImageRsFormat::create(data.clone(), &mime_type)?;
        if let Err(err) = format.set_no_limits() {
            eprint!("Failed to unset decoder limits: {err}");
        }
        let mut image_info = format.info();

        // TODO: Unnecessary clone of data
        let metadata = gufo::RawMetadata::for_guessed(data.into_inner());

        let data = match metadata {
            Ok((metadata, data)) => {
                image_info.metadata_exif = metadata
                    .exif
                    .first()
                    .map(|x| B::try_from_slice(x))
                    .transpose()
                    .expected_error()?;

                image_info.metadata_xmp = metadata
                    .xmp
                    .first()
                    .map(|x| B::try_from_slice(x))
                    .transpose()
                    .expected_error()?;

                image_info.metadata_key_value = Some(metadata.key_value);

                data
            }
            Err(err) => err.into_inner(),
        };

        let loader_impelementation = Self::default();

        let gufo_image = gufo::Image::new(data);
        let data = Cursor::new(match gufo_image {
            Ok(gufo_image) => {
                *loader_impelementation.cicp.lock().unwrap() = gufo_image.cicp();
                gufo_image.into_inner()
            }
            Err(err) => err.into_inner(),
        });

        if format.decoder.is_animated() {
            let (send, recv) = channel();
            let thread =
                std::thread::spawn(move || animated::worker(format, data, mime_type, send));
            *loader_impelementation.thread.lock().unwrap() = Some((thread, recv));
        } else {
            *loader_impelementation.format.lock().unwrap() = Some(format);
        }

        Ok((loader_impelementation, image_info))
    }

    fn specific_frame<B: ByteData>(
        &mut self,
        frame_request: FrameRequest,
    ) -> Result<Frame<B>, ProcessError> {
        let mut frame = if let Some(decoder) = std::mem::take(&mut *self.format.lock().unwrap()) {
            decoder.frame().expected_error()?
        } else if let Some((ref thread, ref recv)) = *self.thread.lock().unwrap() {
            thread.thread().unpark();
            let (frame, looped) = recv.recv().internal_error()??;
            if !frame_request.loop_animation && matches!(frame.details.n_frame, Some(0)) && looped {
                return Err(ProcessError::NoMoreFrames);
            }
            frame
        } else {
            return Err(ProcessError::NoMoreFrames);
        };

        frame.details.color_cicp = self.cicp.lock().unwrap().map(|x| {
            [
                x.color_primaries.into(),
                x.transfer_characteristics.into(),
                x.matrix_coefficients.into(),
                x.video_full_range_flag.into(),
            ]
        });

        frame.into_other().expected_error()
    }
}

pub enum ImageRsDecoder<T: std::io::BufRead + std::io::Seek> {
    Bmp(codecs::bmp::BmpDecoder<T>),
    Dds(codecs::dds::DdsDecoder<T>),
    Farbfeld(codecs::farbfeld::FarbfeldDecoder<T>),
    Gif(codecs::gif::GifDecoder<T>),
    Ico(codecs::ico::IcoDecoder<T>),
    Jpeg(codecs::jpeg::JpegDecoder<T>),
    Jpeg2000(hayro_jpeg2000::integration::Jp2Decoder),
    OpenExr(codecs::openexr::OpenExrDecoder<T>),
    Png(codecs::png::PngDecoder<T>),
    Pnm(codecs::pnm::PnmDecoder<T>),
    Qoi(codecs::qoi::QoiDecoder<T>),
    Tga(codecs::tga::TgaDecoder<T>),
    Tiff(codecs::tiff::TiffDecoder<T>),
    WebP(codecs::webp::WebPDecoder<T>),
    Xbm(image_extras::xbm::XbmDecoder<T>),
    Xpm(image_extras::xpm::XpmDecoder<T>),
}

pub struct ImageRsFormat<T: std::io::BufRead + std::io::Seek> {
    decoder: ImageRsDecoder<T>,
    handler: Handler,
}

impl ImageRsFormat<Reader> {
    fn create(data: Reader, mime_type: &str) -> Result<Self, ProcessError> {
        Ok(match mime_type {
            "image/apng" => Self::new(ImageRsDecoder::Png(
                codecs::png::PngDecoder::new(data).expected_error()?,
            ))
            .format_name("Animated PNG")
            .supports_two_alpha_modes(true)
            .supports_two_grayscale_modes(true)
            .default_bit_depth(8),
            "image/bmp" => Self::new(ImageRsDecoder::Bmp(
                codecs::bmp::BmpDecoder::new(data).expected_error()?,
            ))
            .format_name("BMP")
            .default_bit_depth(8),
            "image/x-dds" => Self::new(ImageRsDecoder::Dds(
                codecs::dds::DdsDecoder::new(data).expected_error()?,
            ))
            .format_name("DDS")
            .supports_two_grayscale_modes(true),
            "image/x-ff" => Self::new(ImageRsDecoder::Farbfeld(
                codecs::farbfeld::FarbfeldDecoder::new(data).expected_error()?,
            ))
            .format_name("Farbfeld")
            .default_bit_depth(16),
            "image/gif" => Self::new(ImageRsDecoder::Gif(
                codecs::gif::GifDecoder::new(data).expected_error()?,
            ))
            .format_name("GIF")
            .default_bit_depth(8),
            "image/x-win-bitmap" | "image/vnd.microsoft.icon" => Self::new(ImageRsDecoder::Ico(
                codecs::ico::IcoDecoder::new(data).expected_error()?,
            ))
            .format_name("ICO"),
            "image/jpeg" => Self::new(ImageRsDecoder::Jpeg(
                codecs::jpeg::JpegDecoder::new(data).expected_error()?,
            ))
            .format_name("JPEG")
            .default_bit_depth(8)
            .supports_two_grayscale_modes(true),
            "image/jp2" | "image/x-jp2-codestream" => Self::new(ImageRsDecoder::Jpeg2000(
                hayro_jpeg2000::integration::Jp2Decoder::new(data).expected_error()?,
            ))
            .format_name("ICO"),
            "image/x-exr" => Self::new(ImageRsDecoder::OpenExr(
                codecs::openexr::OpenExrDecoder::new(data).expected_error()?,
            ))
            .format_name("OpenEXR")
            .default_bit_depth(32)
            .supports_two_grayscale_modes(true),
            "image/png" => Self::new(ImageRsDecoder::Png(
                codecs::png::PngDecoder::new(data).expected_error()?,
            ))
            .format_name("PNG")
            .supports_two_alpha_modes(true)
            .supports_two_grayscale_modes(true)
            .default_bit_depth(8),
            "image/x-portable-bitmap" => Self::new(ImageRsDecoder::Pnm(
                codecs::pnm::PnmDecoder::new(data).expected_error()?,
            ))
            .format_name("PBM")
            .default_bit_depth(1),
            "image/x-portable-graymap" => Self::new(ImageRsDecoder::Pnm(
                codecs::pnm::PnmDecoder::new(data).expected_error()?,
            ))
            .format_name("PGM"),
            "image/x-portable-pixmap" => Self::new(ImageRsDecoder::Pnm(
                codecs::pnm::PnmDecoder::new(data).expected_error()?,
            ))
            .format_name("PPM"),
            "image/x-portable-anymap" => Self::new(ImageRsDecoder::Pnm(
                codecs::pnm::PnmDecoder::new(data).expected_error()?,
            ))
            .format_name("PAM"),
            "image/x-qoi" | "image/qoi" => Self::new(ImageRsDecoder::Qoi(
                codecs::qoi::QoiDecoder::new(data).expected_error()?,
            ))
            .format_name("QOI")
            .default_bit_depth(8)
            .supports_two_alpha_modes(true),
            "image/x-targa" | "image/x-tga" => Self::new(ImageRsDecoder::Tga(
                codecs::tga::TgaDecoder::new(data).expected_error()?,
            ))
            .format_name("TGA")
            .supports_two_grayscale_modes(true),
            "image/tiff" => Self::new(ImageRsDecoder::Tiff(
                codecs::tiff::TiffDecoder::new(data).expected_error()?,
            ))
            .format_name("TIFF")
            .supports_two_alpha_modes(true)
            .supports_two_grayscale_modes(true),
            "image/webp" => Self::new(ImageRsDecoder::WebP(
                codecs::webp::WebPDecoder::new(data).expected_error()?,
            ))
            .format_name("WebP")
            .default_bit_depth(8)
            .supports_two_alpha_modes(true),
            "image/x-xbitmap" => Self::new(ImageRsDecoder::Xbm(
                image_extras::xbm::XbmDecoder::new(data).expected_error()?,
            ))
            .format_name("XBM")
            .default_bit_depth(8)
            .supports_two_alpha_modes(false),
            "image/x-xpixmap" => Self::new(ImageRsDecoder::Xpm(
                image_extras::xpm::XpmDecoder::new(data).expected_error()?,
            ))
            .format_name("XPM")
            .default_bit_depth(8)
            .supports_two_alpha_modes(false),
            mime_type => return Err(ProcessError::UnsupportedImageFormat(mime_type.to_string())),
        })
    }
}

impl<T: std::io::BufRead + std::io::Seek> ImageRsFormat<T> {
    pub fn format_name(mut self, format_name: impl ToString) -> Self {
        self.handler = self.handler.format_name(format_name);
        self
    }

    pub fn supports_two_alpha_modes(mut self, supports_two_alpha_modes: bool) -> Self {
        self.handler = self
            .handler
            .supports_two_alpha_modes(supports_two_alpha_modes);
        self
    }

    pub fn supports_two_grayscale_modes(mut self, supports_two_grayscale_modes: bool) -> Self {
        self.handler = self
            .handler
            .supports_two_grayscale_modes(supports_two_grayscale_modes);
        self
    }

    pub fn default_bit_depth(mut self, default_bit_depth: u8) -> Self {
        self.handler = self.handler.default_bit_depth(default_bit_depth);
        self
    }

    fn new(decoder: ImageRsDecoder<T>) -> Self {
        Self {
            decoder,
            handler: Handler::default(),
        }
    }

    fn info<B: ByteData>(&mut self) -> ImageDetails<B> {
        match self.decoder {
            ImageRsDecoder::Bmp(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Dds(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Farbfeld(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Gif(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Ico(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Jpeg(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Jpeg2000(ref mut d) => self.handler.info(d),
            ImageRsDecoder::OpenExr(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Png(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Pnm(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Qoi(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Tga(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Tiff(ref mut d) => self.handler.info(d),
            ImageRsDecoder::WebP(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Xbm(ref mut d) => self.handler.info(d),
            ImageRsDecoder::Xpm(ref mut d) => self.handler.info(d),
        }
    }

    fn frame<B: ByteData>(self) -> Result<Frame<B>, ProcessError> {
        match self.decoder {
            ImageRsDecoder::Bmp(d) => self.handler.frame(d),
            ImageRsDecoder::Dds(d) => self.handler.frame(d),
            ImageRsDecoder::Farbfeld(d) => self.handler.frame(d),
            ImageRsDecoder::Gif(d) => self.handler.frame(d),
            ImageRsDecoder::Ico(d) => self.handler.frame(d),
            ImageRsDecoder::Jpeg(d) => self.handler.frame(d),
            ImageRsDecoder::Jpeg2000(d) => self.handler.frame(d),
            ImageRsDecoder::OpenExr(d) => self.handler.frame(d),
            ImageRsDecoder::Png(d) => self.handler.frame(d),
            ImageRsDecoder::Pnm(d) => self.handler.frame(d),
            ImageRsDecoder::Qoi(d) => self.handler.frame(d),
            ImageRsDecoder::Tga(d) => self.handler.frame(d),
            ImageRsDecoder::Tiff(d) => self.handler.frame(d),
            ImageRsDecoder::WebP(d) => self.handler.frame(d),
            ImageRsDecoder::Xbm(d) => self.handler.frame(d),
            ImageRsDecoder::Xpm(d) => self.handler.frame(d),
        }
    }

    fn frame_details<B: ByteData>(&mut self) -> Result<FrameDetails<B>, ProcessError> {
        match self.decoder {
            ImageRsDecoder::Bmp(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Dds(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Farbfeld(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Gif(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Ico(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Jpeg(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Jpeg2000(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::OpenExr(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Png(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Pnm(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Qoi(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Tga(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Tiff(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::WebP(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Xbm(ref mut d) => self.handler.frame_details(d),
            ImageRsDecoder::Xpm(ref mut d) => self.handler.frame_details(d),
        }
    }

    fn set_no_limits(&mut self) -> ImageResult<()> {
        let limits = Limits::no_limits();

        match self.decoder {
            ImageRsDecoder::Bmp(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Dds(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Farbfeld(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Gif(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Ico(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Jpeg(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Jpeg2000(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::OpenExr(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Png(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Pnm(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Qoi(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Tga(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Tiff(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::WebP(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Xbm(ref mut d) => d.set_limits(limits),
            ImageRsDecoder::Xpm(ref mut d) => d.set_limits(limits),
        }
    }
}

impl<'a, T: std::io::BufRead + std::io::Seek + 'a> ImageRsDecoder<T> {
    fn into_frames(self) -> Option<image::Frames<'a>> {
        match self {
            Self::Png(d) => Some(d.apng().unwrap().into_frames()),
            Self::Gif(d) => Some(d.into_frames()),
            Self::WebP(d) => Some(d.into_frames()),
            _ => None,
        }
    }

    fn is_animated(&self) -> bool {
        match self {
            Self::Gif(_) => true,
            Self::Png(d) => d.is_apng().unwrap(),
            Self::WebP(d) => d.has_animation(),
            _ => false,
        }
    }
}
