use std::sync::Arc;
use svgtypes::Length;
use usvg_tree::{Image, ImageKind, Node, NodeExt, NodeKind, NonZeroRect, Size, Tree, ViewBox};
use crate::svgtree::{AId, SvgNode};
use crate::{converter, OptionLog, Options, TreeParsing};
pub type ImageHrefDataResolverFn =
    Box<dyn Fn(&str, Arc<Vec<u8>>, &Options) -> Option<ImageKind> + Send + Sync>;
pub type ImageHrefStringResolverFn = Box<dyn Fn(&str, &Options) -> Option<ImageKind> + Send + Sync>;
pub struct ImageHrefResolver {
    pub resolve_data: ImageHrefDataResolverFn,
    pub resolve_string: ImageHrefStringResolverFn,
}
impl Default for ImageHrefResolver {
    fn default() -> Self {
        ImageHrefResolver {
            resolve_data: ImageHrefResolver::default_data_resolver(),
            resolve_string: ImageHrefResolver::default_string_resolver(),
        }
    }
}
impl ImageHrefResolver {
    pub fn default_data_resolver() -> ImageHrefDataResolverFn {
        Box::new(
            move |mime: &str, data: Arc<Vec<u8>>, opts: &Options| match mime {
                "image/jpg" | "image/jpeg" => Some(ImageKind::JPEG(data)),
                "image/png" => Some(ImageKind::PNG(data)),
                "image/gif" => Some(ImageKind::GIF(data)),
                "image/svg+xml" => load_sub_svg(&data, opts),
                "text/plain" => match get_image_data_format(&data) {
                    Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(data)),
                    Some(ImageFormat::PNG) => Some(ImageKind::PNG(data)),
                    Some(ImageFormat::GIF) => Some(ImageKind::GIF(data)),
                    _ => load_sub_svg(&data, opts),
                },
                _ => None,
            },
        )
    }
    pub fn default_string_resolver() -> ImageHrefStringResolverFn {
        Box::new(move |href: &str, opts: &Options| {
            let path = opts.get_abs_path(std::path::Path::new(href));
            if path.exists() {
                let data = match std::fs::read(&path) {
                    Ok(data) => data,
                    Err(_) => {
                        log::warn!("Failed to load '{}'. Skipped.", href);
                        return None;
                    }
                };
                match get_image_file_format(&path, &data) {
                    Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(Arc::new(data))),
                    Some(ImageFormat::PNG) => Some(ImageKind::PNG(Arc::new(data))),
                    Some(ImageFormat::GIF) => Some(ImageKind::GIF(Arc::new(data))),
                    Some(ImageFormat::SVG) => load_sub_svg(&data, opts),
                    _ => {
                        log::warn!("'{}' is not a PNG, JPEG, GIF or SVG(Z) image.", href);
                        None
                    }
                }
            } else {
                log::warn!("'{}' is not a path to an image.", href);
                None
            }
        })
    }
}
impl std::fmt::Debug for ImageHrefResolver {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("ImageHrefResolver { .. }")
    }
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum ImageFormat {
    PNG,
    JPEG,
    GIF,
    SVG,
}
pub(crate) fn convert(node: SvgNode, state: &converter::State, parent: &mut Node) -> Option<()> {
    let href = node
        .attribute(AId::Href)
        .log_none(|| log::warn!("Image lacks the 'xlink:href' attribute. Skipped."))?;
    let kind = get_href_data(href, state.opt)?;
    let visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
    let rendering_mode = node
        .find_attribute(AId::ImageRendering)
        .unwrap_or(state.opt.image_rendering);
    let actual_size = match kind {
        ImageKind::JPEG(ref data) | ImageKind::PNG(ref data) | ImageKind::GIF(ref data) => {
            imagesize::blob_size(data)
                .ok()
                .and_then(|size| Size::from_wh(size.width as f32, size.height as f32))
                .log_none(|| log::warn!("Image has an invalid size. Skipped."))?
        }
        ImageKind::SVG(ref svg) => svg.size,
    };
    let rect = NonZeroRect::from_xywh(
        node.convert_user_length(AId::X, state, Length::zero()),
        node.convert_user_length(AId::Y, state, Length::zero()),
        node.convert_user_length(
            AId::Width,
            state,
            Length::new_number(actual_size.width() as f64),
        ),
        node.convert_user_length(
            AId::Height,
            state,
            Length::new_number(actual_size.height() as f64),
        ),
    );
    let rect = rect.log_none(|| log::warn!("Image has an invalid size. Skipped."))?;
    let view_box = ViewBox {
        rect,
        aspect: node.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
    };
    let id = if state.parent_markers.is_empty() {
        node.element_id().to_string()
    } else {
        String::new()
    };
    parent.append_kind(NodeKind::Image(Image {
        id,
        transform: Default::default(),
        visibility,
        view_box,
        rendering_mode,
        kind,
    }));
    Some(())
}
pub(crate) fn get_href_data(href: &str, opt: &Options) -> Option<ImageKind> {
    if let Ok(url) = data_url::DataUrl::process(href) {
        let (data, _) = url.decode_to_vec().ok()?;
        let mime = format!(
            "{}/{}",
            url.mime_type().type_.as_str(),
            url.mime_type().subtype.as_str()
        );
        (opt.image_href_resolver.resolve_data)(&mime, Arc::new(data), opt)
    } else {
        (opt.image_href_resolver.resolve_string)(href, opt)
    }
}
fn get_image_file_format(path: &std::path::Path, data: &[u8]) -> Option<ImageFormat> {
    let ext = path.extension().and_then(|e| e.to_str())?.to_lowercase();
    if ext == "svg" || ext == "svgz" {
        return Some(ImageFormat::SVG);
    }
    get_image_data_format(data)
}
fn get_image_data_format(data: &[u8]) -> Option<ImageFormat> {
    match imagesize::image_type(data).ok()? {
        imagesize::ImageType::Gif => Some(ImageFormat::GIF),
        imagesize::ImageType::Jpeg => Some(ImageFormat::JPEG),
        imagesize::ImageType::Png => Some(ImageFormat::PNG),
        _ => None,
    }
}
pub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option<ImageKind> {
    let mut sub_opt = Options::default();
    sub_opt.resources_dir = None;
    sub_opt.dpi = opt.dpi;
    sub_opt.font_size = opt.font_size;
    sub_opt.languages = opt.languages.clone();
    sub_opt.shape_rendering = opt.shape_rendering;
    sub_opt.text_rendering = opt.text_rendering;
    sub_opt.image_rendering = opt.image_rendering;
    sub_opt.default_size = opt.default_size;
    let tree = match Tree::from_data(data, &sub_opt) {
        Ok(tree) => tree,
        Err(_) => {
            log::warn!("Failed to load subsvg image.");
            return None;
        }
    };
    sanitize_sub_svg(&tree);
    Some(ImageKind::SVG(tree))
}
fn sanitize_sub_svg(tree: &Tree) {
    let mut changed = true;
    while changed {
        changed = false;
        for node in tree.root.descendants() {
            let mut rm = false;
            if let NodeKind::Image(_) = *node.borrow() {
                rm = true;
            };
            if rm {
                node.detach();
                changed = true;
                break;
            }
        }
    }
}