package io.gitlab.jfronny.ImgJava.imageProcessing;

import io.gitlab.jfronny.ImgJava.util.MColor;
import io.gitlab.jfronny.ImgJava.util.Picture;

import java.awt.*;

/**
 * Algorithmen zur Änderung der Pixelpositionen eines Pictures
 * z.B. drehen, spiegeln usw.
 *
 * @author Thomas Schaller
 * @version 1.1 (28.11.2019)
 */
public class ImageUtil {
    public enum MirrorMode {
        Vertical,
        Horizontal
    }

    public enum RotateMode {
        Left,
        Right
    }

    /**
     * Horizontally mirrors an image (mutates original instance)
     *
     * @param picture Picture to mirror
     * @return The mirrored image
     */
    public static Picture mirror(Picture picture, MirrorMode mirrorMode) {
        int w = picture.getWidth();
        int h = picture.getHeight();

        Color[][] pixel = picture.getPixelArray();
        Color[][] pixelNeu = new Color[w][h];

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                pixelNeu[x][y] = switch (mirrorMode) {
                    case Vertical -> pixel[x][h - y - 1];
                    case Horizontal -> pixel[w - x - 1][y];
                };
            }
        }

        picture.setPixelArray(pixelNeu);
        return picture;
    }

    /**
     * Rotates an image by 90° (mutates original instance)
     *
     * @param picture Picture to mirror
     * @return The mirrored image
     */
    public static Picture rotate(Picture picture, RotateMode rotateMode) {
        int w = picture.getWidth();
        int h = picture.getHeight();

        Color[][] pixel = picture.getPixelArray();
        Color[][] pixelNeu = new Color[h][w];

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                pixelNeu[y][x] = switch (rotateMode) {
                    case Left -> pixel[w - x - 1][y];
                    case Right -> pixel[x][h - y - 1];
                };
            }
        }

        picture.setPixelArray(pixelNeu);
        return picture;
    }


    public static Picture blurBox(Picture picture, int amount) {
        int w = picture.getWidth();
        int h = picture.getHeight();

        Color[][] pixel;
        Color[][] pixelNeu = picture.getPixelArray();

        for (int i = 0; i < amount; i++) {
            pixel = pixelNeu;
            pixelNeu = new Color[w][h];
            for (int x = 0; x < w; x++) {
                for (int y = 0; y < h; y++) {
                    if (x < 1 || y < 1 || x + 1 >= w || y + 1 >= h) {
                        pixelNeu[x][y] = pixel[x][y];
                        continue;
                    }
                    pixelNeu[x][y] = pixel[x][y];
                    //p p p
                    //p 0 p
                    //p p p
                    MColor sum = new MColor(pixel[x - 1][y + 1])
                            .add(pixel[x + 0][y + 1])
                            .add(pixel[x + 1][y + 1])
                            .add(pixel[x - 1][y + 0])
                            .add(pixel[x + 0][y + 0])
                            .add(pixel[x + 1][y + 0])
                            .add(pixel[x - 1][y - 1])
                            .add(pixel[x + 0][y - 1])
                            .add(pixel[x + 1][y - 1]);

                    pixelNeu[x][y] = sum.div(9).get();
                }
            }
        }

        picture.setPixelArray(pixelNeu);
        return picture;
    }

    public static Picture blurGauss(Picture picture, int radius) {
        int w = picture.getWidth();
        int h = picture.getHeight();

        Color[][] pixel = picture.getPixelArray();
        Color[][] pixelNeu = new Color[w][h];

        double sigma = radius / 2d;

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {

                pixelNeu[x][y] = pixel[x][y];

                MColor sum = new MColor();

                double gSum = 0;

                for (int dx = -radius; dx <= radius; dx++) {
                    for (int dy = -radius; dy <= radius; dy++) {
                        if (x + dx >= 0 && y + dy >= 0 && x + dx < w && y + dy < h) {
                            double g = gauss(dx, dy, sigma);
                            gSum += g;
                            sum.addM(pixel[x + dx][y + dy], g);
                        }
                    }
                }

                pixelNeu[x][y] = sum.div(gSum).get();
            }
        }

        picture.setPixelArray(pixelNeu);
        return picture;
    }

    private static double gauss(double dX, double dY, double stDev) {
        return Math.exp(-((dX * dX + dY * dY) / (2 * stDev * stDev))) / (2 * Math.PI * stDev * stDev);
    }

    public static Picture sharpen(Picture picture, int amount) {
        int w = picture.getWidth();
        int h = picture.getHeight();

        Color[][] pixel;
        Color[][] pixelNeu = picture.getPixelArray();

        for (int i = 0; i < amount; i++) {
            pixel = pixelNeu;
            pixelNeu = new Color[w][h];
            for (int x = 0; x < w; x++) {
                for (int y = 0; y < h; y++) {
                    if (x < 1 || y < 1 || x + 1 >= w || y + 1 >= h) {
                        pixelNeu[x][y] = pixel[x][y];
                        continue;
                    }
                    pixelNeu[x][y] = pixel[x][y];
                    //p p p
                    //p 0 p
                    //p p p
                    MColor sum = new MColor(pixel[x + 0][y + 1]).mult(-1)
                            .addM(pixel[x - 1][y + 0], -1)
                            .addM(pixel[x + 0][y + 0], 5)
                            .addM(pixel[x + 1][y + 0], -1)
                            .addM(pixel[x + 0][y - 1], -1);

                    pixelNeu[x][y] = sum.get();
                }
            }
        }

        picture.setPixelArray(pixelNeu);
        return picture;
    }
}
