package de.duehl.basics.pictures;

/*
 * Copyright 2024 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageInputStream;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse stellt Hilfsmethoden rund um Bilder zur Verfügung.
 *
 * @version 1.01     2024-06-26
 * @author Christian Dühl
 */

public class PictureHelper {

    private static final List<String> PICTURE_EXTENSIONS = CollectionsHelper.buildListFrom(
            ".png", ".gif", ".bmp", ".jpg", ".jpeg"); // TODO mehr

    /** Ermittelt alle Bilder im angegebenen Verzeichnis. */
    public static List<String> determinePicturesInDirectory(String directory) {
        List<String> allFilenames = FileHelper.findAllFilesInMainDirectoryNio2(directory);

        List<String> pictureFilenames = new ArrayList<>();
        for (String filename : allFilenames) {
            String loweredFilename = Text.toLowerCase(filename);
            if (CollectionsHelper.inputEndsWithAnyListElement(loweredFilename, PICTURE_EXTENSIONS)) {
                pictureFilenames.add(filename);
            }
        }

        return pictureFilenames;
    }

    /** Liest das Bild aus der angegebenen Datei ein. */
    public static BufferedImage loadImage(String filename) {
        try {
            return ImageIO.read(new File(filename));
        }
        catch (IOException exception) {
            throw new RuntimeException(
                    "Beim Laden des Bildes " + filename + " trat ein Fehler auf.", exception);
        }
    }

    /** Speichert das übergebene Bild unter dem angegebenen Dateinamen mit 300 dpi ab. */
    public static void storeImage(String filename, BufferedImage image) {
        try {
            tryToStoreImage(filename, image);
        }
        catch (IOException exception) {
            throw new RuntimeException("Fehler beim Speichern des Bildes " + filename,
                    exception);
        }
    }

    private static void tryToStoreImage(String filename, BufferedImage image) throws IOException {
        String formatName = determinePictureFormatName(filename);

        File outputfile = new File(filename);
        ImageIO.write(image, formatName, outputfile);

        /*
         * Eleganter geht es so, wenn man PDFbox im Einsatz hat:
         * import org.apache.pdfbox.tools.imageio.ImageIOUtil;
         * ImageIOTools.writeImage(image, filename, 300);
         */

    }

    /** Ermittelt den Formatnamen aus dem Dateinamen eines Bildes. */
    public static String determinePictureFormatName(String filename) {
        String formatName = FileHelper.determineExtension(filename);
        if (FileHelper.NO_EXTENSION_FOUND.equals(formatName)) {
            throw new RuntimeException("Keine Extension am Dateinamen erkannt.\n"
                    + "\t" + "filename = " + filename + "\n");
        }
        formatName = formatName.substring(1); // führenden Punkt entfernen
        formatName = Text.toLowerCase(formatName);
        if (formatName.equals("jpeg")) {
            formatName = "jpg";
        }
        return formatName;
    }

    /** Kopiert das übergebene Bild. */
    public static BufferedImage deepImageCopy(BufferedImage image) {
        ColorModel cm = image.getColorModel();
        boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
        WritableRaster raster = image.copyData(image.getRaster().createCompatibleWritableRaster());
        return new BufferedImage(cm, raster, isAlphaPremultiplied, null);

        // Siehe https://stackoverflow.com/questions/3514158/how-do-you-clone-a-bufferedimage
        // Antwort von user1050755 am 12.11.2014
    }

    /**
     * Erzeugt aus dem übergebenen Bild ein um den Winkel in Grad gedrehtes Bild.
     *
     * @param image
     *            Das Bild das gedreht werden soll.
     * @param degree
     *            Der Winkel in Grad.
     * @return Das gedrehte Bild.
     */
    public static BufferedImage rotateImageByDegrees(BufferedImage image, double degree) {
        double rad = Math.toRadians(degree);
        return rotateImageByRad(image, rad);
    }

    /**
     * Erzeugt aus dem übergebenen Bild ein um den Winkel in Grad gedrehtes Bild.
     *
     * @param image
     *            Das Bild das gedreht werden soll.
     * @param rad
     *            Der Winkel im Bogenmaß.
     * @return Das gedrehte Bild.
     */
    public static BufferedImage rotateImageByRad(BufferedImage image, double rad) {
        double sin = Math.abs(Math.sin(rad)), cos = Math.abs(Math.cos(rad));
        int w = image.getWidth();
        int h = image.getHeight();
        int newWidth = (int) Math.floor(w * cos + h * sin);
        int newHeight = (int) Math.floor(h * cos + w * sin);

        BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = rotated.createGraphics();
        AffineTransform at = new AffineTransform();
        at.translate((newWidth - w) / 2, (newHeight - h) / 2);

        int x = w / 2;
        int y = h / 2;

        at.rotate(rad, x, y);
        g2d.setTransform(at);
        g2d.drawImage(image, 0, 0, null);
        //g2d.setColor(Color.RED);
        g2d.drawRect(0, 0, newWidth - 1, newHeight - 1);
        g2d.dispose();

        return rotated;
    }

    /**
     * Gibt die Größe des Bildes zurück.
     *
     * Siehe:
     *     https://stackoverflow.com/questions/672916/how-to-get-image-height-and-width-using-java
     *
     * @param imageFilename
     *            image file
     * @return dimensions of image
     */
    public static Dimension getImageDimension(String imageFilename) {
        File imageFile = new File(imageFilename);
        int pos = imageFile.getName().lastIndexOf(".");
        if (pos == -1) {
            throw new RuntimeException("Der Dateinamen hat keine Erweiterung: " + imageFilename);
        }
        String suffix = imageFile.getName().substring(pos + 1);
        Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
        while (iter.hasNext()) {
            ImageReader reader = iter.next();
            try {
                FileImageInputStream stream = new FileImageInputStream(imageFile);
                reader.setInput(stream);
                int width = reader.getWidth(reader.getMinIndex());
                int height = reader.getHeight(reader.getMinIndex());
                return new Dimension(width, height);
            }
            catch (IOException exception) {
                throw new RuntimeException("Fehler beim Einlesen des Bildes: " + imageFilename,
                        exception);
            }
            finally {
                reader.dispose();
            }
        }

        throw new RuntimeException("Kein bekanntes Bildformat: " + imageFilename);
    }

    /** Gibt zurück, ob das Bild breiter als hoch ist. */
    public static boolean hasLandscapeFormat(String imageFilename) {
        Dimension dimension = getImageDimension(imageFilename);
        double width = dimension.getWidth();
        double height = dimension.getHeight();
        return width > height;
    }

    /**
     * Erzeugt eine Miniaturansicht (Thumbnail) aus dem übergebenen Bild.
     *
     * Siehe https://cooltrickshome.blogspot.com/2017/07/create-image-thumbnails-using-java.html
     *
     * Achtung: Diese Methode beachtet nicht das Seitenverhältnis. Dafür sollte man die Methode
     * createThumbnailWithAspectRatio() nutzen.
     *
     * @param filename
     *            Der Dateiname des Bildes, zu dem ein Miniaturbild erzeugt werden soll.
     * @param thumbnailWidth
     *            Die gewünschte Breite der Miniaturansicht.
     * @param thumbnailHeight
     *            Die gewünschte Höhe der Miniaturansicht.
     */
    public static BufferedImage createThumbnail(String filename, int thumbnailWidth,
            int thumbnailHeight) {
        try {
            return tryToCreateThumbnail(filename, thumbnailWidth, thumbnailHeight);
        }
        catch (IOException exception) {
            throw new RuntimeException(
                    "Es trat beim Erzeugen eines Miniaturbildes eine Ausnmane auf.", exception);
        }
    }

    private static BufferedImage tryToCreateThumbnail(String filename, int thumbnailWidth,
            int thumbnailHeight) throws IOException {
        BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight,
                BufferedImage.TYPE_INT_RGB);
        File imageFile = new File(filename);
        thumbnail.createGraphics().drawImage(ImageIO.read(imageFile).getScaledInstance(
                thumbnailWidth, thumbnailHeight, Image.SCALE_SMOOTH), 0, 0, null);
        return thumbnail;
    }

    /**
     * Erzeugt eine Miniaturansicht (Thumbnail) aus dem übergebenen Bild, wobei das
     * Seitenverhältnis beibehalten wird.
     *
     * @param filename
     *            Der Dateiname des Bildes, zu dem ein Miniaturbild erzeugt werden soll.
     * @param thumbnailMaxWidthAndHeight
     *            Die gewünschte maximale Breite und Höhe der Miniaturansicht.
     */
    public static BufferedImage createThumbnailWithAspectRatio(String filename,
            int thumbnailMaxWidthAndHeight) {
        Dimension originalDimension = getImageDimension(filename);
        int originalWidth = originalDimension.width;
        int originalHeight = originalDimension.height;
        int originalMax = Math.max(originalWidth, originalHeight);
        double shrinkFactor = originalMax / (double) thumbnailMaxWidthAndHeight;
        int thumbnailWidth = (int) (originalWidth / shrinkFactor);
        int thumbnailHeight = (int) (originalHeight / shrinkFactor);
        if (thumbnailWidth > thumbnailMaxWidthAndHeight) {
            thumbnailWidth = thumbnailMaxWidthAndHeight;
        }
        if (thumbnailHeight > thumbnailMaxWidthAndHeight) {
            thumbnailHeight = thumbnailMaxWidthAndHeight;
        }

        return createThumbnail(filename, thumbnailWidth, thumbnailHeight);
    }

}
