package mara.mybox.image.file;

import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import mara.mybox.image.tools.BufferedImageTools;
import mara.mybox.image.data.ImageColor;
import static mara.mybox.image.tools.ImageConvertTools.pixelSizeMm2dpi;
import mara.mybox.image.data.ImageFileInformation;
import mara.mybox.image.data.ImageInformation;
import mara.mybox.image.tools.ScaleTools;
import mara.mybox.color.ColorBase;
import mara.mybox.dev.MyBoxLog;
import mara.mybox.fxml.FxTask;
import mara.mybox.tools.FileNameTools;
import mara.mybox.tools.FileTools;
import static mara.mybox.value.AppVariables.ImageHints;
import static mara.mybox.value.Languages.message;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import thridparty.image4j.ICODecoder;

/**
 * @Author Mara
 * @CreateDate 2018-6-4 16:07:27
 * @License Apache License Version 2.0
 */
// https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
public class ImageFileReaders {

    public static ImageReader getReader(ImageInputStream iis, String format) {
        try {
            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (readers == null || !readers.hasNext()) {
                return getReader(format);
            }
            return getReader(readers);
        } catch (Exception e) {
            MyBoxLog.debug(e);
            return null;
        }
    }

    public static ImageReader getReader(String format) {
        try {
            Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format.toLowerCase());
            return getReader(readers);
        } catch (Exception e) {
            MyBoxLog.debug(e);
            return null;
        }
    }

    public static ImageReader getReader(Iterator<ImageReader> readers) {
        try {
            if (readers == null || !readers.hasNext()) {
                return null;
            }
            ImageReader reader = null;
            while (readers.hasNext()) {
                reader = readers.next();
                if (!reader.getClass().toString().contains("TIFFImageReader")
                        || reader instanceof com.github.jaiimageio.impl.plugins.tiff.TIFFImageReader) {
                    return reader;
                }
            }
            return reader;
        } catch (Exception e) {
            MyBoxLog.debug(e);
            return null;
        }
    }

    public static BufferedImage readImage(FxTask task, File file) {
        ImageInformation readInfo = new ImageInformation(file);
        return readFrame(task, readInfo);
    }

    public static BufferedImage readImage(FxTask task, File file, int width) {
        ImageInformation imageInfo = new ImageInformation(file);
        imageInfo.setRequiredWidth(width);
        return readFrame(task, imageInfo);
    }

    public static BufferedImage readFrame(FxTask task, File file, int index) {
        ImageInformation imageInfo = new ImageInformation(file);
        imageInfo.setIndex(index);
        return readFrame(task, imageInfo);
    }

    public static BufferedImage readFrame(FxTask task, ImageInformation imageInfo) {
        if (imageInfo == null) {
            return null;
        }
        File file = imageInfo.getFile();
        if (file == null) {
            return null;
        }
        String format = imageInfo.getImageFormat();
        if (task != null) {
            task.setInfo(message("File") + ": " + file);
            task.setInfo(message("FileSize") + ": " + FileTools.showFileSize(file.length()));
            task.setInfo(message("Format") + ": " + format);
        }
        if ("ico".equals(format) || "icon".equals(format)) {
            return readIcon(task, imageInfo);
        }
        BufferedImage bufferedImage = null;
        try (ImageInputStream iis = ImageIO.createImageInputStream(
                new BufferedInputStream(new FileInputStream(file)))) {
            ImageReader reader = getReader(iis, format);
            if (reader == null) {
                return null;
            }
            reader.setInput(iis, true, true);
            if (task != null && !task.isWorking()) {
                return null;
            }
            bufferedImage = readFrame(task, reader, imageInfo);
            reader.dispose();
        } catch (Exception e) {
            imageInfo.setError(e.toString());
        }
        return bufferedImage;
    }

    public static BufferedImage readFrame(FxTask task, ImageReader reader, ImageInformation imageInfo) {
        if (reader == null || imageInfo == null) {
            return null;
        }
        try {
            double infoWidth = imageInfo.getWidth();
            double requiredWidth = imageInfo.getRequiredWidth();
            ImageReadParam param = reader.getDefaultReadParam();
            Rectangle region = imageInfo.getIntRegion();
            int xscale = imageInfo.getXscale();
            if (xscale < 1) {
                xscale = 1;
            }
            int yscale = imageInfo.getYscale();
            if (yscale < 1) {
                yscale = 1;
            }
            if (region != null) {
                param.setSourceRegion(region);
                if (task != null) {
                    task.setInfo(message("Region") + ": " + region.toString());
                }
            } else if (requiredWidth > 0 && infoWidth > requiredWidth && xscale <= 1 && yscale <= 1) {
                xscale = (int) Math.ceil(infoWidth / requiredWidth);
                yscale = xscale;
            }

            if (xscale > 1 || yscale > 1) {
                param.setSourceSubsampling(xscale, yscale, 0, 0);
                if (task != null) {
                    task.setInfo(message("Scale") + ": " + " xscale=" + xscale + " yscale= " + yscale);
                }
            } else if (region == null) {
                ImageInformation.checkMem(task, imageInfo);
                int sampleScale = imageInfo.getSampleScale();
                if (sampleScale > 1) {
                    if (task != null) {
                        task.setInfo("sampleScale: " + sampleScale);
                    }
                    return null;
                }
            }
            BufferedImage bufferedImage;
            try {
                if (task != null) {
                    task.setInfo(message("Reading") + ": " + message("Frame") + " " + imageInfo.getIndex());
                }
                bufferedImage = reader.read(imageInfo.getIndex(), param);
                if (bufferedImage == null) {
                    return null;
                }
                if (task != null && !task.isWorking()) {
                    return null;
                }
                imageInfo.setImageType(bufferedImage.getType());

                if (requiredWidth > 0 && bufferedImage.getWidth() != requiredWidth) {
                    if (task != null) {
                        task.setInfo(message("Scale") + ": " + message("Width") + " " + requiredWidth);
                    }
                    bufferedImage = ScaleTools.scaleImageWidthKeep(bufferedImage, (int) requiredWidth);
                } else if (ImageHints != null) {
                    bufferedImage = BufferedImageTools.applyRenderHints(bufferedImage, ImageHints);
                }
                return bufferedImage;
            } catch (Exception e) {
                if (task != null) {
                    task.setInfo(message("Error") + ": " + e.toString());
                }
                return readBrokenImage(task, e, imageInfo);
            }
        } catch (Exception e) {
            imageInfo.setError(e.toString());
            return null;
        }
    }

    public static ImageInformation makeInfo(FxTask task, File file, int width) {
        ImageInformation readInfo = new ImageInformation(file);
        readInfo.setRequiredWidth(width);
        return makeInfo(task, readInfo, false);
    }

    public static ImageInformation makeInfo(FxTask task, ImageInformation readInfo, boolean onlyInformation) {
        try {
            if (readInfo == null) {
                return null;
            }
            File file = readInfo.getFile();
            if (file == null) {
                return null;
            }
            ImageFileInformation fileInfo = null;
            ImageInformation imageInfo = null;
            int index = readInfo.getIndex();
            int requiredWidth = (int) readInfo.getRequiredWidth();
            if (task != null) {
                task.setInfo(message("File") + ": " + file);
                task.setInfo(message("FileSize") + ": " + FileTools.showFileSize(file.length()));
                task.setInfo(message("Frame") + ": " + (index + 1));
                if (requiredWidth > 0) {
                    task.setInfo(message("LoadWidth") + ": " + requiredWidth);
                }
            }
            String format = readInfo.getImageFormat();
            if ("ico".equals(format) || "icon".equals(format)) {
                if (fileInfo == null) {
                    fileInfo = new ImageFileInformation(file);
                    ImageFileReaders.readImageFileMetaData(task, null, fileInfo);
                }
                if (task != null && !task.isWorking()) {
                    return null;
                }
                if (fileInfo.getImagesInformation() == null) {
                    return null;
                }
                int framesNumber = fileInfo.getImagesInformation().size();
                if (task != null) {
                    task.setInfo(message("FramesNumber") + ": " + framesNumber);
                }
                if (framesNumber > 0 && index < framesNumber) {
                    imageInfo = fileInfo.getImagesInformation().get(index);
                    if (task != null) {
                        task.setInfo(message("Pixels") + ": " + (int) imageInfo.getWidth() + "x" + (int) imageInfo.getHeight());
                    }
                    if (!onlyInformation) {
                        if (task != null) {
                            task.setInfo(message("Reading") + ": " + message("Frame")
                                    + " " + index + "/" + framesNumber);
                        }
                        imageInfo.setRequiredWidth(requiredWidth);
                        BufferedImage bufferedImage = readIcon(task, imageInfo);
                        if (task != null && !task.isWorking()) {
                            return null;
                        }
                        imageInfo.loadBufferedImage(bufferedImage);
                    }
                }
            } else {
                try (ImageInputStream iis = ImageIO.createImageInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                    ImageReader reader = getReader(iis, format);
                    if (reader != null) {
                        reader.setInput(iis, false, false);
                        fileInfo = new ImageFileInformation(file);
                        if (task != null) {
                            task.setInfo(message("Reading") + ": " + message("MetaData"));
                        }
                        ImageFileReaders.readImageFileMetaData(task, reader, fileInfo);
                        if (task != null && !task.isWorking()) {
                            return null;
                        }
                        if (fileInfo.getImagesInformation() == null) {
                            return null;
                        }
                        int framesNumber = fileInfo.getImagesInformation().size();
                        if (task != null) {
                            task.setInfo(message("FramesNumber") + ": " + framesNumber);
                        }
                        if (framesNumber > 0 && index < framesNumber) {
                            imageInfo = fileInfo.getImagesInformation().get(index);
                            if (task != null) {
                                task.setInfo(message("Pixels") + ": " + (int) imageInfo.getWidth() + "x" + (int) imageInfo.getHeight());
                            }
                            if (!onlyInformation) {
                                if (task != null) {
                                    task.setInfo(message("Reading") + ": " + message("Frame")
                                            + " " + index + "/" + framesNumber);
                                }
                                imageInfo.setRequiredWidth(requiredWidth);
                                BufferedImage bufferedImage = readFrame(task, reader, imageInfo);
                                if (task != null && !task.isWorking()) {
                                    return null;
                                }
                                imageInfo.loadBufferedImage(bufferedImage);
                            }
                        }
                        reader.dispose();
                    } else {
                        if (task != null) {
                            task.setError("Fail to get reader");
                        }
                    }
                } catch (Exception e) {
                    if (task != null) {
                        task.setError(e.toString());
                    } else {
                        MyBoxLog.error(e.toString());
                    }
                    return null;
                }
            }
            return imageInfo;
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return null;
        }
    }

    public static BufferedImage readIcon(FxTask task, File srcFile) {
        return readIcon(task, srcFile, 0);
    }

    public static BufferedImage readIcon(FxTask task, File srcFile, int index) {
        try {
            if (srcFile == null || !srcFile.exists()) {
                return null;
            }
            List<BufferedImage> frames = ICODecoder.read(srcFile);
            if (frames == null || frames.isEmpty()) {
                return null;
            }
            return frames.get(index >= 0 && index < frames.size() ? index : 0);
        } catch (Exception e) {
            String ex = e.toString() + ": " + srcFile;
            if (task != null) {
                task.setError(ex);
            } else {
                MyBoxLog.error(ex);
            }
            return null;
        }
    }

    public static BufferedImage readIcon(FxTask task, ImageInformation imageInfo) {
        try {
            BufferedImage bufferedImage = readIcon(task, imageInfo.getFile(), imageInfo.getIndex());
            if (task != null && !task.isWorking()) {
                return null;
            }
            return adjust(task, imageInfo, bufferedImage);
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return null;
        }
    }

    // call this only when region and scale is not handled when create bufferedImage
    public static BufferedImage adjust(FxTask task, ImageInformation imageInfo, BufferedImage bufferedImage) {
        try {
            if (imageInfo == null || bufferedImage == null) {
                return bufferedImage;
            }
            int requiredWidth = (int) imageInfo.getRequiredWidth();
            int bmWidth = bufferedImage.getWidth();
            int xscale = imageInfo.getXscale();
            int yscale = imageInfo.getYscale();
            Rectangle region = imageInfo.getIntRegion();
            if (region == null) {
                if (xscale != 1 || yscale != 1) {
                    bufferedImage = ScaleTools.scaleImageByScale(bufferedImage, xscale, yscale);
                } else if (requiredWidth > 0 && bmWidth != requiredWidth) {
                    bufferedImage = ScaleTools.scaleImageWidthKeep(bufferedImage, requiredWidth);
                }
            } else {
                if (xscale != 1 || yscale != 1) {
                    bufferedImage = mara.mybox.image.data.CropTools.sample(task, bufferedImage,
                            imageInfo.getRegion(), xscale, yscale);
                } else {
                    bufferedImage = mara.mybox.image.data.CropTools.sample(task, bufferedImage,
                            imageInfo.getRegion(), requiredWidth);
                }
            }
            return bufferedImage;
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return null;
        }

    }

    /*
        Broken image
     */
    public static BufferedImage readBrokenImage(FxTask task, Exception e, ImageInformation imageInfo) {
        BufferedImage image = null;
        try {
            File file = imageInfo.getFile();
            if (e == null || file == null) {
                return null;
            }
            String format = FileNameTools.ext(file.getName()).toLowerCase();
            if (task != null) {
                task.setInfo("Reading broken image: " + format);
            }
            switch (format) {
                case "gif":
                    // Read Gif with JDK api normally. When broken, use DhyanB's API.
                    // if (e.toString().contains("java.lang.ArrayIndexOutOfBoundsException: 4096")) {
                    image = ImageGifFile.readBrokenGifFile(task, imageInfo);
//                    if (e.toString().contains("java.lang.ArrayIndexOutOfBoundsException")) {
//                        image = ImageGifFile.readBrokenGifFile(imageInfo);
//                    }
                    break;
                case "jpg":
                case "jpeg":
                    image = ImageJpgFile.readBrokenJpgFile(task, imageInfo);
//                    if (e.toString().contains("Unsupported Image Type")) {
//                        image = ImageJpgFile.readBrokenJpgFile(imageInfo);
//                    }
                    break;
                default:
                    if (task != null) {
                        task.setError(e.toString());
                    } else {
                        MyBoxLog.error(e.toString());
                    }
            }
        } catch (Exception ex) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
        }
        return image;
    }

    /*
        Meta data
     */
    public static ImageFileInformation readImageFileMetaData(FxTask task, String fileName) {
        return readImageFileMetaData(task, new File(fileName));
    }

    public static ImageFileInformation readImageFileMetaData(FxTask task, File file) {
        if (file == null || !file.exists() || !file.isFile()) {
            return null;
        }
        if (task != null) {
            task.setInfo(message("ReadingMedia...") + ": " + file);
        }
        ImageFileInformation fileInfo = new ImageFileInformation(file);
        String format = fileInfo.getImageFormat();
        if ("ico".equals(format) || "icon".equals(format)) {
            fileInfo = ImageFileInformation.readIconFile(task, file);
        } else {
            try (ImageInputStream iis = ImageIO.createImageInputStream(
                    new BufferedInputStream(new FileInputStream(file)))) {
                ImageReader reader = getReader(iis, format);
                if (reader == null) {
                    return null;
                }
                reader.setInput(iis, false, false);
                readImageFileMetaData(task, reader, fileInfo);
                reader.dispose();
            } catch (Exception e) {
                if (task != null) {
                    task.setError(e.toString());
                } else {
                    MyBoxLog.error(e.toString());
                }
            }
        }
        return fileInfo;
    }

    public static boolean readImageFileMetaData(FxTask task,
            ImageReader reader, ImageFileInformation fileInfo) {
        try {
            if (fileInfo == null) {
                return false;
            }
            String targetFormat = fileInfo.getImageFormat();
            File file = fileInfo.getFile();
            List<ImageInformation> imagesInfo = new ArrayList<>();
            if (reader == null) {
                if (task != null) {
                    task.setInfo("fail to get reader");
                }
                fileInfo.setNumberOfImages(1);
                ImageInformation imageInfo = ImageInformation.create(targetFormat, file);
                imageInfo.setImageFileInformation(fileInfo);
                imageInfo.setImageFormat(targetFormat);
                imageInfo.setFile(file);
                imageInfo.setCreateTime(fileInfo.getCreateTime());
                imageInfo.setModifyTime(fileInfo.getModifyTime());
                imageInfo.setFileSize(fileInfo.getFileSize());
                imageInfo.setIndex(0);
                ImageInformation.checkMem(task, imageInfo);
                imagesInfo.add(imageInfo);
                fileInfo.setImagesInformation(imagesInfo);
                fileInfo.setImageInformation(imageInfo);
                return true;
            }
            String format = reader.getFormatName().toLowerCase();
            fileInfo.setImageFormat(format);
            int num = reader.getNumImages(true);
            fileInfo.setNumberOfImages(num);
            if (task != null) {
                task.setInfo("Number Of Images: " + num);
            }

            ImageInformation imageInfo;
            for (int i = 0; i < num; ++i) {
                if (task != null && !task.isWorking()) {
                    return false;
                }
                if (task != null) {
                    task.setInfo(message("Handle") + ": " + i + "/" + num);
                }
                imageInfo = ImageInformation.create(format, file);
                imageInfo.setImageFileInformation(fileInfo);
                imageInfo.setImageFormat(format);
                imageInfo.setFile(file);
                imageInfo.setCreateTime(fileInfo.getCreateTime());
                imageInfo.setModifyTime(fileInfo.getModifyTime());
                imageInfo.setFileSize(fileInfo.getFileSize());
                imageInfo.setWidth(reader.getWidth(i));
                imageInfo.setHeight(reader.getHeight(i));
                imageInfo.setPixelAspectRatio(reader.getAspectRatio(i));
                imageInfo.setIsMultipleFrames(num > 1);

                imageInfo.setIsTiled(reader.isImageTiled(i));
                imageInfo.setIndex(i);

                Iterator<ImageTypeSpecifier> types = reader.getImageTypes(i);
                List<ImageTypeSpecifier> typesValue = new ArrayList<>();
                if (types != null) {
                    while (types.hasNext()) {
                        if (task != null && !task.isWorking()) {
                            return false;
                        }
                        ImageTypeSpecifier t = types.next();
                        typesValue.add(t);
                        if (task != null) {
                            task.setInfo("ImageTypeSpecifier : " + t.getClass());
                        }
                    }
                    ImageTypeSpecifier imageType = reader.getRawImageType(i);
                    ColorModel colorModel = null;
                    if (imageType != null) {
                        imageInfo.setRawImageType(imageType);
                        colorModel = imageType.getColorModel();
                    }
                    if (colorModel == null) {
                        if (!typesValue.isEmpty()) {
                            colorModel = typesValue.get(0).getColorModel();
                        }
                    }
                    if (colorModel != null) {
                        ColorSpace colorSpace = colorModel.getColorSpace();
                        imageInfo.setColorSpace(ColorBase.colorSpaceType(colorSpace.getType()));
                        imageInfo.setColorChannels(colorModel.getNumComponents());
                        imageInfo.setBitDepth(colorModel.getPixelSize());
                    }
                }
                imageInfo.setImageTypeSpecifiers(typesValue);
                try {
                    imageInfo.setPixelAspectRatio(reader.getAspectRatio(i));
                } catch (Exception e) {
                    if (task != null) {
                        task.setError(e.toString());
                    } else {
                        MyBoxLog.error(e.toString());
                    }
                }
                try {
                    imageInfo.setHasThumbnails(reader.hasThumbnails(i));
                    imageInfo.setNumberOfThumbnails(reader.getNumThumbnails(i));
                } catch (Exception e) {
                    if (task != null) {
                        task.setError(e.toString());
                    } else {
                        MyBoxLog.error(e.toString());
                    }
                }
                try {
                    readImageMetaData(task, format, imageInfo, reader.getImageMetadata(i));
                } catch (Exception e) {
                    if (task != null) {
                        task.setError(e.toString());
                    } else {
                        MyBoxLog.error(e.toString());
                    }
                }
                ImageInformation.checkMem(task, imageInfo);
                imagesInfo.add(imageInfo);
            }
            fileInfo.setImagesInformation(imagesInfo);
            if (!imagesInfo.isEmpty()) {
                fileInfo.setImageInformation(imagesInfo.get(0));
            }
            return task == null || task.isWorking();
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return false;
        }
    }

    public static boolean readImageMetaData(FxTask task, String format,
            ImageInformation imageInfo, IIOMetadata iioMetaData) {
        try {
            if (imageInfo == null || iioMetaData == null) {
                return false;
            }
            if (task != null) {
                task.setInfo("read Image Meta Data : " + format);
            }
            StringBuilder metaDataXml = new StringBuilder();
            String[] formatNames = iioMetaData.getMetadataFormatNames();
            Map<String, Map<String, List<Map<String, Object>>>> metaData = new HashMap<>();
            for (String formatName : formatNames) {
                if (task != null && !task.isWorking()) {
                    return false;
                }
                Map<String, List<Map<String, Object>>> formatMetaData = new HashMap<>();
                IIOMetadataNode tree = (IIOMetadataNode) iioMetaData.getAsTree(formatName);
                readImageMetaData(task, formatMetaData, metaDataXml, tree, 2);
                metaData.put(formatName, formatMetaData);
            }
            imageInfo.setMetaData(metaData);
            imageInfo.setMetaDataXml(metaDataXml.toString());

            explainCommonMetaData(metaData, imageInfo);
            switch (format.toLowerCase()) {
                case "png":
                    ImagePngFile.explainPngMetaData(metaData, imageInfo);
                    break;
                case "jpg":
                case "jpeg":
                    ImageJpgFile.explainJpegMetaData(metaData, imageInfo);
                    break;
                case "gif":
                    ImageGifFile.explainGifMetaData(metaData, imageInfo);
                    break;
                case "bmp":
                    ImageBmpFile.explainBmpMetaData(metaData, imageInfo);
                    break;
                case "tif":
                case "tiff":
                    ImageTiffFile.explainTiffMetaData(iioMetaData, imageInfo);
                    break;
                default:

            }
//            MyBoxLog.debug(metaData);
            return true;
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return false;
        }
    }

    public static boolean readImageMetaData(FxTask task, Map<String, List<Map<String, Object>>> formatMetaData,
            StringBuilder metaDataXml, IIOMetadataNode node, int level) {
        try {
            String lineSeparator = System.getProperty("line.separator");
            for (int i = 0; i < level; ++i) {
                metaDataXml.append("    ");
            }
            metaDataXml.append("<").append(node.getNodeName());
            Map<String, Object> nodeAttrs = new HashMap<>();
            NamedNodeMap map = node.getAttributes();
            boolean isTiff = "TIFFField".equals(node.getNodeName());
            if (map != null && map.getLength() > 0) {
                int length = map.getLength();
                for (int i = 0; i < length; ++i) {
                    if (task != null && !task.isWorking()) {
                        return false;
                    }
                    Node attr = map.item(i);
                    String name = attr.getNodeName();
                    String value = attr.getNodeValue();
                    if (!isTiff) {
                        nodeAttrs.put(name, value);
                    }
                    metaDataXml.append(" ").append(name).append("=\"").append(value).append("\"");
                    if (isTiff && "ICC Profile".equals(value)) {
                        metaDataXml.append(" value=\"skip...\"/>").append(lineSeparator);
                        return true;
                    }
                }
            }
            Object userObject = node.getUserObject();
            if (userObject != null) {
                if (!isTiff) {
                    nodeAttrs.put("UserObject", userObject);
                }
                metaDataXml.append(" ").append("UserObject=\"skip...\"");
            }
            if (!isTiff && !nodeAttrs.isEmpty()) {
                List<Map<String, Object>> nodeAttrsList = formatMetaData.get(node.getNodeName());
                if (nodeAttrsList == null) {
                    nodeAttrsList = new ArrayList<>();
                }
                nodeAttrsList.add(nodeAttrs);
                formatMetaData.put(node.getNodeName(), nodeAttrsList);
            }
            IIOMetadataNode child = (IIOMetadataNode) node.getFirstChild();
            if (child == null) {
                metaDataXml.append("/>").append(lineSeparator);
                return true;
            }
            metaDataXml.append(">").append(lineSeparator);
            while (child != null) {
                if (task != null && !task.isWorking()) {
                    return false;
                }
                readImageMetaData(task, formatMetaData, metaDataXml, child, level + 1);
                child = (IIOMetadataNode) child.getNextSibling();
            }
            for (int i = 0; i < level; ++i) {
                metaDataXml.append("    ");
            }
            metaDataXml.append("</").append(node.getNodeName()).append(">").append(lineSeparator);
            return true;
        } catch (Exception e) {
            if (task != null) {
                task.setError(e.toString());
            } else {
                MyBoxLog.error(e.toString());
            }
            return false;
        }
    }

    // https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
    public static void explainCommonMetaData(Map<String, Map<String, List<Map<String, Object>>>> metaData,
            ImageInformation imageInfo) {
        try {
            if (!metaData.containsKey("javax_imageio_1.0")) {
