use crate::image::{ImageDescriptor, ImageFormat, ImageMipmap, ImageMode};
use core::assets::{
    asset::{Asset, AssetId},
    protocol::{AssetLoadResult, AssetProtocol, AssetVariant, Meta},
    protocols::binary::BinaryAsset,
};
use serde::{Deserialize, Serialize};
use std::str::from_utf8;

fn default_depth() -> usize {
    1
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageAssetSourceRawPixels {
    pub width: usize,
    pub height: usize,
    #[serde(default = "default_depth")]
    pub depth: usize,
    pub bytes: Vec<u8>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ImageAssetSource {
    Color {
        descriptor: ImageDescriptor,
        width: usize,
        height: usize,
        #[serde(default = "default_depth")]
        depth: usize,
        color: [u8; 4],
    },
    RawPixels {
        descriptor: ImageDescriptor,
        bytes_path: String,
    },
    Png {
        descriptor: ImageDescriptor,
        bytes_paths: Vec<String>,
    },
    Jpeg {
        descriptor: ImageDescriptor,
        bytes_paths: Vec<String>,
    },
    Ktx2 {
        descriptor: ImageDescriptor,
        bytes_path: String,
    },
}

impl Default for ImageAssetSource {
    fn default() -> Self {
        Self::Color {
            descriptor: Default::default(),
            width: 1,
            height: 1,
            depth: 1,
            color: [255, 255, 255, 255],
        }
    }
}

#[derive(Debug, Clone)]
pub struct ImageAsset {
    pub descriptor: ImageDescriptor,
    pub width: usize,
    pub height: usize,
    pub depth: usize,
    pub bytes: Vec<u8>,
    pub content_assets: Vec<AssetId>,
}

impl ImageAsset {
    pub fn color(color: [u8; 4], mode: ImageMode) -> Self {
        Self {
            descriptor: ImageDescriptor {
                mode,
                format: ImageFormat::RGBA,
                mipmap: ImageMipmap::None,
            },
            width: 1,
            height: 1,
            depth: 1,
            bytes: color.to_vec(),
            content_assets: vec![],
        }
    }

    pub fn bytes(width: usize, height: usize, bytes: Vec<u8>, mode: ImageMode) -> Self {
        Self {
            descriptor: ImageDescriptor {
                mode,
                format: ImageFormat::RGBA,
                mipmap: ImageMipmap::None,
            },
            width,
            height,
            depth: 1,
            bytes,
            content_assets: vec![],
        }
    }
}

pub struct ImageAssetProtocol;

impl AssetProtocol for ImageAssetProtocol {
    fn name(&self) -> &str {
        "image"
    }

    fn on_load(&mut self, data: Vec<u8>) -> AssetLoadResult {
        let data = from_utf8(&data).unwrap();
        match serde_json::from_str(data).unwrap() {
            ImageAssetSource::Color {
                descriptor,
                width,
                height,
                depth,
                color,
            } => {
                let mut bytes = vec![0; width * height * depth * 4];
                for chunk in bytes.chunks_mut(4) {
                    chunk[0] = color[0];
                    chunk[1] = color[1];
                    chunk[2] = color[2];
                    chunk[3] = color[3];
                }
                AssetLoadResult::Data(Box::new(ImageAsset {
                    descriptor,
                    width,
                    height,
                    depth,
                    bytes,
                    content_assets: vec![],
                }))
            }
            ImageAssetSource::RawPixels {
                descriptor,
                bytes_path,
            } => AssetLoadResult::Yield(
                Some(Box::new((DataType::Raw, descriptor))),
                vec![("".to_owned(), format!("bin://{}", bytes_path))],
            ),
            ImageAssetSource::Png {
                descriptor,
                bytes_paths,
            } => {
                if descriptor.mode == ImageMode::Image2d && bytes_paths.len() > 1 {
                    return AssetLoadResult::Error(
                        "PNG Image2d mode can read data from only one binary asset!".to_owned(),
                    );
                }
                AssetLoadResult::Yield(
                    Some(Box::new((DataType::Png, descriptor))),
                    bytes_paths
                        .into_iter()
                        .map(|path| (path.to_owned(), format!("bin://{}", path)))
                        .collect(),
                )
            }
            ImageAssetSource::Jpeg {
                descriptor,
                bytes_paths,
            } => {
                if descriptor.mode == ImageMode::Image2d && bytes_paths.len() > 1 {
                    return AssetLoadResult::Error(
                        "JPEG Image2d mode can read data from only one binary asset!".to_owned(),
                    );
                }
                AssetLoadResult::Yield(
                    Some(Box::new((DataType::Jpeg, descriptor))),
                    bytes_paths
                        .into_iter()
                        .map(|path| (path.to_owned(), format!("bin://{}", path)))
                        .collect(),
                )
            }
            ImageAssetSource::Ktx2 {
                descriptor,
                bytes_path,
            } => AssetLoadResult::Yield(
                Some(Box::new((DataType::Ktx2, descriptor))),
                vec![("".to_owned(), format!("bin://{}", bytes_path))],
            ),
        }
    }

    fn on_resume(&mut self, meta: Meta, list: &[(&str, &Asset)]) -> AssetLoadResult {
        let (data_type, descriptor) = *meta
            .unwrap()
            .downcast::<(DataType, ImageDescriptor)>()
            .unwrap();
        match data_type {
            DataType::Raw => {
                let asset = match list.first() {
                    Some(asset) => asset.1,
                    None => {
                        return AssetLoadResult::Error("No image binary data loaded".to_owned())
                    }
                };
                if let Some(bytes) = asset.get::<BinaryAsset>() {
                    let ImageAssetSourceRawPixels {
                        width,
                        height,
                        depth,
                        bytes,
                    } = bincode::deserialize(bytes.get()).unwrap();
                    AssetLoadResult::Data(Box::new(ImageAsset {
                        descriptor,
                        width,
                        height,
                        depth,
                        bytes,
                        content_assets: vec![asset.id()],
                    }))
                } else {
                    AssetLoadResult::Error(format!(
                        "Could not read binary asset: {:?}",
                        asset.to_full_path()
                    ))
                }
            }
            DataType::Png => {
                let mut width = 0;
                let mut height = 0;
                let mut bytes = vec![];
                let mut offset = 0;
                let mut content_assets = Vec::with_capacity(list.len());
                for (_, asset) in list {
                    if let Some(data) = asset.get::<BinaryAsset>() {
                        let decoder = png::Decoder::new(data.get());
                        let mut reader = decoder.read_info().unwrap();
                        let info = reader.info();
                        if info.interlaced {
                            return AssetLoadResult::Error(format!(
                                "Trying to load interlaced PNG: {:?}",
                                asset.to_full_path()
                            ));
                        }
                        let w = info.width as usize;
                        let h = info.height as usize;
                        let size = reader.output_buffer_size();
                        let bytesize = size / (w * h);
                        if bytesize != descriptor.format.bytesize() {
                            return AssetLoadResult::Error(format!(
                                "PNG: {:?} image doesn't have expected per-pixel bytesize: {} (provided: {})",
                                asset.to_full_path(),
                                descriptor.format.bytesize(),
                                bytesize,
                            ));
                        }
                        if width == 0 || height == 0 {
                            width = w;
                            height = h;
                            bytes = vec![0; size * list.len()];
                        } else if w != width || h != height {
                            return AssetLoadResult::Error(format!(
                                "PNG: {:?} image doesn't have expected resolution: {} x {}",
                                asset.to_full_path(),
                                width,
                                height,
                            ));
                        }
                        reader
                            .next_frame(&mut bytes[offset..(offset + size)])
                            .unwrap();
                        offset += size;
                        content_assets.push(asset.id());
                    } else {
                        return AssetLoadResult::Error(format!(
                            "Trying to read non-binary asset as PNG: {:?}",
                            asset.to_full_path()
                        ));
                    }
                }
                AssetLoadResult::Data(Box::new(ImageAsset {
                    descriptor,
                    width,
                    height,
                    depth: list.len(),
                    bytes,
                    content_assets,
                }))
            }
            DataType::Jpeg => {
                let mut width = 0;
                let mut height = 0;
                let mut bytes = vec![];
                let mut content_assets = Vec::with_capacity(list.len());
                for (_, asset) in list {
                    if let Some(data) = asset.get::<BinaryAsset>() {
                        let mut decoder = jpeg_decoder::Decoder::new(data.get());
                        decoder.read_info().unwrap();
                        let info = decoder.info().unwrap();
                        let w = info.width as usize;
                        let h = info.height as usize;
                        let data = decoder.decode().unwrap();
                        let size = bytes.len();
                        let bytesize = size / (w * h);
                        if bytesize != descriptor.format.bytesize() {
                            return AssetLoadResult::Error(format!(
                                "JPEG: {:?} image doesn't have expected per-pixel bytesize: {} (provided: {})",
                                asset.to_full_path(),
                                descriptor.format.bytesize(),
                                bytesize,
                            ));
                        }
                        if width == 0 || height == 0 {
                            width = w;
                            height = h;
                            bytes = vec![0; size * list.len()];
                        } else if w != width || h != height {
                            return AssetLoadResult::Error(format!(
                                "JPEG: {:?} image doesn't have expected resolution: {} x {}",
                                asset.to_full_path(),
                                width,
                                height,
                            ));
                        }
                        bytes.extend(data);
                        content_assets.push(asset.id());
                    } else {
                        return AssetLoadResult::Error(format!(
                            "Trying to read non-binary asset as JPEG: {:?}",
                            asset.to_full_path()
                        ));
                    }
                }
                AssetLoadResult::Data(Box::new(ImageAsset {
                    descriptor,
                    width,
                    height,
                    depth: list.len(),
                    bytes,
                    content_assets,
                }))
            }
            DataType::Ktx2 => {
                let asset = match list.first() {
                    Some(asset) => asset.1,
                    None => {
                        return AssetLoadResult::Error("No image binary data loaded".to_owned())
                    }
                };
                if let Some(bytes) = asset.get::<BinaryAsset>() {
                    let reader =
                        ktx2::Reader::new(bytes.get()).expect("Could not create KTX2 decoder");
                    let header = reader.header();
                    let bytes = reader.levels().next().map(|b| b.to_owned());
                    if let Some(bytes) = bytes {
                        AssetLoadResult::Data(Box::new(ImageAsset {
                            descriptor,
                            width: header.pixel_width as _,
                            height: header.pixel_height as _,
                            depth: header.pixel_depth as _,
                            bytes,
                            content_assets: vec![asset.id()],
                        }))
                    } else {
                        AssetLoadResult::Error(format!(
                            "Could not decode KTX2 image: {:?}",
                            asset.to_full_path()
                        ))
                    }
                } else {
                    AssetLoadResult::Error(format!(
                        "Could not read binary asset: {:?}",
                        asset.to_full_path()
                    ))
                }
            }
        }
    }

    fn on_unload(&mut self, asset: &Asset) -> Option<Vec<AssetVariant>> {
        Some(
            asset
                .get::<ImageAsset>()
                .unwrap()
                .content_assets
                .iter()
                .map(|id| AssetVariant::Id(*id))
                .collect(),
        )
    }
}

enum DataType {
    Raw,
    Png,
    Jpeg,
    Ktx2,
}
