package nl.uva.multimedia.image;

class Coordinates {
    public final double x;
    public final double y;

    public Coordinates(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

public class ImageRotation {
    private int[] origImage;
    private int imageWidth;
    private int imageHeight;
    private int centerX;
    private int centerY;

    public ImageRotation(int[] image, int width, int height) {
        this.origImage = image.clone();
        this.imageWidth = width;
        this.imageHeight = height;
        this.centerX = this.imageWidth / 2;
        this.centerY = this.imageHeight / 2;
    }

    /* This helper function bit shifts and performs a binary AND to get the Red (RGB) value. */
    public int pixelRed(int pixel){
        return (pixel >> 16) & 255;
    }
    /* This helper function bit shifts and performs a binary AND to get the Green (RGB) value. */
    public int pixelGreen(int pixel){
        return (pixel >> 8 )& 255;
    }
    /* This helper function bit shifts and performs a binary AND to get the Blue (RGB) value. */
    public int pixelBlue(int pixel){
        return pixel & 255;
    }

    private Coordinates origCoords(int x, int y) {
        return new Coordinates(x, y);
    }

    private Coordinates pixelsToCoords(int w, int h) {
        return new Coordinates(w - centerX, h - centerY);
    }

    // Attempt at a correct BI algoritm
    private int averageColX(int[] img, Coordinates pix0, Coordinates pix1, Coordinates pix2) {
        int col1 = img[(int) (pix1.y * this.imageWidth + pix1.x)];
        int col2 = img[(int) (pix2.y * this.imageWidth + pix2.x)];
        int aRed = (int) (((pix2.x - pix0.x)/(pix2.x - pix1.x)) * (pixelRed(col1))
                         + (((pix0.x - pix1.x)/(pix2.x - pix1.x)) * (pixelRed(col2))));
        int aGreen = (int) (((pix2.x - pix0.x)/(pix2.x - pix1.x)) * (pixelGreen(col1))
                           + (((pix0.x - pix1.x)/(pix2.x - pix1.x)) * (pixelGreen(col2))));
        int aBlue = (int) (((pix2.x - pix0.x)/(pix2.x - pix1.x)) * (pixelBlue(col1))
                          + (((pix0.x - pix1.x)/(pix2.x - pix1.x)) * (pixelBlue(col2))));
        return (255 << 24) + (aRed << 16) + (aGreen << 8) + (aBlue);
    }

    // Attempt at a correct BI algoritm
    private int interpolateM(Coordinates pix) {
        double x = pix.x;
        double y = pix.y;
        int aX1 = averageColX(this.origImage,
                pix,
                new Coordinates(Math.floor(x), Math.ceil(y)),
                new Coordinates(Math.ceil(x), Math.ceil(y)));
        int aX2 = averageColX(this.origImage,
                pix,
                new Coordinates(Math.floor(x), Math.floor(y)),
                new Coordinates(Math.ceil(x), Math.floor(y)));
        int y1 = (int) Math.floor(y);
        int y2 = (int) Math.ceil(y);

        int aYRed = (int) (((y2 - y)/(y2 - y1)) * pixelRed(aX1) + ((y - y1)/(y2 - y1)) * pixelRed(aX2));
        int aYGreen = (int) (((y2 - y)/(y2 - y1)) * pixelGreen(aX1) + ((y - y1)/(y2 - y1)) * pixelGreen(aX2));
        int aYBlue = (int) (((y2 - y)/(y2 - y1)) * pixelBlue(aX1) + ((y - y1)/(y2 - y1)) * pixelBlue(aX2));

        return (255 << 24) + (aYRed << 16) + (aYGreen << 8) + (aYBlue);
    }

    private int averageCol(int[] img, Coordinates pix1, Coordinates pix2) {
        int col1 = img[(int) (pix1.y * this.imageWidth + pix1.x)];
        int col2 = img[(int) (pix2.y * this.imageWidth + pix2.x)];
        int aRed = (pixelRed(col1) + pixelRed(col2)) / 2;
        int aGreen = (pixelGreen(col1) + pixelGreen(col2)) / 2;
        int aBlue = (pixelBlue(col1) + pixelBlue(col2)) / 2;
        return (255 << 24) + (aRed << 16) + (aGreen << 8) + (aBlue);
    }

    // Interpolates a pixel with the given coordinates
    private int interpolate(Coordinates pix) {
        double x = pix.x;
        double y = pix.y;
        int aX = averageCol(this.origImage,
                            new Coordinates(Math.floor(x), Math.ceil(y)),
                            new Coordinates(Math.ceil(x), Math.ceil(y)));
        int aY = averageCol(this.origImage,
                            new Coordinates(Math.floor(x), Math.floor(y)),
                            new Coordinates(Math.ceil(x), Math.floor(y)));
        return (aX + aY) / 2;
    }

    // Rotates the image using nearest neighbour
    public int[] rotateNN(double degrees) {
        double radAng = Math.toRadians(degrees);
        double cos = Math.cos(radAng);
        double sin = Math.sin(radAng);
        int[] rotatedImage = new int[origImage.length];

        for (int x = 0; x < imageWidth; x++) {
            for (int y = 0; y < imageHeight; y++) {
                Coordinates coord = pixelsToCoords(x, y);
                int a = ((int) Math.round(coord.x * cos + coord.y * sin)) + this.centerX;
                int b = ((int) Math.round(coord.y * cos - coord.x * sin)) + this.centerY;

                if (a >= 0 && a < this.imageWidth && b >= 0 && b < this.imageHeight) {
                    rotatedImage[(y * imageWidth + x)] = origImage[(b * imageWidth + a)];
                }
            }
        }
        return rotatedImage;
    }


    // Rotates the image using BI
    public int[] rotateBI(double degrees) {
        double radAng = Math.toRadians(degrees);
        double cos = Math.cos(radAng);
        double sin = Math.sin(radAng);
        int[] rotatedImage = new int[origImage.length];

        for (int x = 0; x < imageWidth; x++) {
            for (int y = 0; y < imageHeight; y++) {
                Coordinates coord = pixelsToCoords(x, y);
                int a = ((int) (coord.x * cos + coord.y * sin)) + this.centerX;
                int b = ((int) (coord.y * cos - coord.x * sin)) + this.centerY;

                if (a >= 0 && a < this.imageWidth && b >= 0 && b < this.imageHeight) {
                    rotatedImage[(y * imageWidth + x)] = interpolate(new Coordinates(a, b));
                }
            }
        }
        return rotatedImage;
    }
}
