package com.intellisrc.img

import com.intellisrc.core.Log
import com.intellisrc.etc.Mime
import groovy.transform.CompileStatic
import net.coobird.thumbnailator.Thumbnails
import net.coobird.thumbnailator.geometry.Positions

import javax.imageio.ImageIO
import javax.imageio.ImageReader
import javax.imageio.stream.ImageInputStream
import java.awt.image.BufferedImage

/**
 * @since 18/11/20.
 */
@CompileStatic
class FileImgTools {
    static class Size {
        int width
        int height
    }
    /**
     * Return size of an image based only in image headers (much faster than using ImageIO.read)
     * @param image
     * @return
     */
    static Size getSize(File image) {
        int width = 0
        int height = 0
        String mime = Mime.getType(image)
        if(Mime.isImage(mime)) {
            InputStream is = new FileInputStream(image)
            ImageInputStream imageInputStream = ImageIO.createImageInputStream(is)
            try {
                ImageReader reader = ImageIO.getImageReadersByMIMEType(mime).next()
                reader.setInput(imageInputStream, true)
                width = reader.getWidth(0)
                height = reader.getHeight(0)
                reader.dispose()
            } catch (Exception e) {
                Log.w("Unable to get image size: ", e)
            } finally {
                imageInputStream.close()
                is.close()
            }
        } else {
            Log.w("File [%s] doesn't seems to be an image (based on mime type)", image)
        }
        return new Size(
            width: width,
            height: height
        )
    }

    /**
     * Get Format to convert to
     * @param imgOut
     * @return
     */
    protected static String getFormat(File imgOut) {
        String mime = Mime.getType(imgOut)
        return Mime.isImage(mime) ? mime.replaceAll('image/', "") : 'jpg'
    }

    /**
     * Resize images without cropping
     * @param imgIn
     * @param imgOut
     * @param size
     * @return
     */
    static boolean resize(File imgIn, File imgOut, int size, format = 'auto') {
        String outFormat = format == "auto" ? getFormat(imgOut) : format
        Thumbnails.of(imgIn).size(size, size).outputFormat(outFormat).toFile(imgOut)
        return imgOut.exists()
    }
    /**
     * Resize images centering on the image and cropping extra parts
     * @param imgIn
     * @param imgOut
     * @param size
     * @return
     */
    static boolean resizeCentered(File imgIn, File imgOut, int size, format = 'auto') {
        String outFormat = format == "auto" ? getFormat(imgOut) : format
        Thumbnails.of(imgIn).size(size, size).crop(Positions.CENTER).outputFormat(outFormat).toFile(imgOut)
        return imgOut.exists()
    }
    /**
     * Resize images and crop without centering (keeping 0,0)
     * @param imgIn
     * @param imgOut
     * @param size
     * @return
     */
    static boolean resizeTopLeft(File imgIn, File imgOut, int size, format = 'auto') {
        String outFormat = format == "auto" ? getFormat(imgOut) : format
        Thumbnails.of(imgIn).size(size, size).crop(Positions.TOP_LEFT).outputFormat(outFormat).toFile(imgOut)
        return imgOut.exists()
    }
    /**
     * Resize images and crop based on Width
     * @param imgIn
     * @param imgOut
     * @param size
     * @return
     */
    static boolean resizeWidth(File imgIn, File imgOut, int size, format = 'auto') {
        String outFormat = format == "auto" ? getFormat(imgOut) : format
        Thumbnails.of(imgIn).width(size).outputFormat(outFormat).toFile(imgOut)
        return imgOut.exists()
    }
    /**
     * Resize images and crop based on Height
     * @param imgIn
     * @param imgOut
     * @param size
     * @return
     */
    static boolean resizeHeight(File imgIn, File imgOut, int size, format = 'auto') {
        String outFormat = format == "auto" ? getFormat(imgOut) : format
        Thumbnails.of(imgIn).height(size).outputFormat(outFormat).toFile(imgOut)
        return imgOut.exists()
    }

    /**
     * Rotate image from file and save to file
     * @param imgIn
     * @param imgOut
     * @param rotate
     * @return
     */
    static boolean rotate(File imgIn, File imgOut, int rotate) {
        if(imgOut.exists()) {
            imgOut.delete()
        }
        if(imgIn.exists()) {
            BufferedImage image = Converter.FileToBuffered(imgIn)
            BufferedImage rotated = BuffImgTools.rotate(image, rotate)
            Converter.BufferedToFile(rotated, imgOut)
        }
        return imgOut.exists()
    }

    /**
     * Crop an image and save it in a file
     * @param imgIn
     * @param imgOut
     * @param x
     * @param y
     * @param width
     * @param height
     * @return
     */
    static boolean crop(File imgIn, File imgOut, int x, int y, int width, int height) {
        if(imgOut.exists()) {
            imgOut.delete()
        }
        if(imgIn.exists()) {
            BufferedImage image = Converter.FileToBuffered(imgIn)
            BufferedImage rotated = BuffImgTools.crop(image, x, y, width, height)
            Converter.BufferedToFile(rotated, imgOut)
        }
        return imgOut.exists()
    }

    /**
     * Checks if an image is a valid JPG
     * @param file
     * @return
     */
    static boolean isValidJPG(File file) {
        boolean valid = false
        //verify that it is not corrupted
        final InputStream digestInputStream = file.newInputStream()
        try {
            final ImageInputStream imageInputStream = ImageIO.createImageInputStream(digestInputStream)
            final Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream)
            if (imageReaders.hasNext()) {
                final ImageReader imageReader = imageReaders.next()
                imageReader.setInput(imageInputStream)
                imageReader.read(0)?.flush()
                if (imageReader.formatName == "JPEG") {
                    imageInputStream.seek(imageInputStream.streamPosition - 2)
                    final byte[] lastTwoBytes = new byte[2]
                    imageInputStream.read(lastTwoBytes)
                    if (lastTwoBytes[0] == (byte) 0xff || lastTwoBytes[1] == (byte) 0xd9) {
                        valid = true
                    } else {
                        Log.w("File: ${file.name} is not complete.")
                    }
                }
            } else {
                try {
                    ImageIO.read(file).flush()
                    valid = true //ignore it
                } catch(Exception | Error ignored) {
                    Log.w("Simple image verification failed")
                }
            }
        } catch (Exception e) {
            Log.w("File: ${file.name} thrown an error: %s", e)
        } finally {
            digestInputStream.close()
        }
        return valid
    }
}
