package math.color;

import math.utils.MathUtil;

import java.awt.*;

public class ColorUtil {
    public static final Color d = new Color(255, 0, 0, 127);
    public static final Color s = new Color(0, 0, 255, 153);
    public static final Color[] apcolors = {new Color(0, 0, 255),
            new Color(0, 255, 0), new Color(0, 255, 255),
            new Color(255, 0, 255), new Color(64, 64, 64),
            new Color(255, 200, 0), new Color(255, 0, 0),
            new Color(192, 192, 192), new Color(255, 175, 175),
            new Color(255, 255, 0)};
    public static final double L_MIN = 350;
    public static final double L_MAX = 700;
    public static final double L_WIDTH = L_MAX - L_MIN;
    public static final int[][] spectrum = {{0, 28, 160}, {0, 40, 200},
            {0, 60, 244}, {0, 124, 248}, {0, 160, 252}, {0, 212, 200},
            {0, 252, 80}, {124, 252, 40}, {252, 252, 0}, {252, 188, 0},
            {252, 124, 0}, {252, 60, 0}, {252, 0, 0}, {204, 0, 0}, {160, 0, 0}};
    public static final Color[] kolorySamochodow = {Color.BLACK, Color.BLUE,
            Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.LIGHT_GRAY,
            Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW};

    private ColorUtil() {
    }

    public static Color lambdaToColor(double lambda) {
        int c4, r, g, b;
        double c5, c6, d1, d2;
        lambda += (1.0 - MathUtil.randomInRange(0, 2000) / 1000.0);
        if (lambda <= L_MIN || lambda >= L_MAX) {
            return Color.BLACK;
        }
        c5 = 0.04 * lambda - 14.0;
        c4 = (int) c5;
        c6 = c5 - c4;
        d1 = spectrum[c4][0];
        d2 = spectrum[c4 + 1][0];
        r = (int) (d1 + c6 * (d2 - d1));
        d1 = spectrum[c4][1];
        d2 = spectrum[c4 + 1][1];
        g = (int) (d1 + c6 * (d2 - d1));
        d1 = spectrum[c4][2];
        d2 = spectrum[c4 + 1][2];
        b = (int) (d1 + c6 * (d2 - d1));
        return new Color(r, g, b);
    }

    /**
     * Zamienia liczbę HEX na RGB
     *
     * @param hexa String - liczba typu 0xFFFFFF podawana jest jako
     *             "FFFFFF" bez 0x
     * @return String - wartość RGB
     */
    public static String HexToRGB(String hexa) {
        Integer R = Integer.valueOf(hexa.substring(0, 2), 16);
        Integer G = Integer.valueOf(hexa.substring(2, 4), 16);
        Integer B = Integer.valueOf(hexa.substring(4, 6), 16);
        return ("RGB:[" + R + "," + G + "," + B + "]");
    }

    /**
     * Zamienia liczbę HEX na RGB
     *
     * @param hexa int - podajemy liczbę typu 0xFFFFFF
     * @return String - wartość RGB
     */
    public static String HexToRGB(int hexa) {
        int R = (hexa >> 16) & 0xff;
        int G = (hexa >> 8) & 0xff;
        int B = hexa & 0xff;
        return ("RGB:[" + R + "," + G + "," + B + "]");
    }

    /**
     * Zamienia wartosci R, G, B na liczbę HEX
     *
     * @param r int - składnik czerwony
     * @param g int - składnik zielony
     * @param b int - składnik niebieski
     * @return String - waartość HEX
     */
    public static String RGBToHex(int r, int g, int b) {
        String r1 = Integer.toString(r, 16);
        String g1 = Integer.toString(g, 16);
        String b1 = Integer.toString(b, 16);
        if (r1.length() == 1) {
            r1 = "0" + r1;
        }
        if (g1.length() == 1) {
            g1 = "0" + g1;
        }
        if (b1.length() == 1) {
            b1 = "0" + b1;
        }
        return "0x" + r1 + g1 + b1;
    }

    /**
     * Zamienia wartosci R, G, B na liczbę int stosowaną w Javie
     *
     * @param r int - składnik czerwony;
     * @param g int - składnik zielony
     * @param b int - składnik niebieski
     * @return int - wartość RGB przeliczona na int;
     */
    public static int RGBToInt(int r, int g, int b) {
        String r1 = Integer.toString(r, 16);
        String g1 = Integer.toString(g, 16);
        String b1 = Integer.toString(b, 16);
        if (r1.length() == 1) {
            r1 = "0" + r1;
        }
        if (g1.length() == 1) {
            g1 = "0" + g1;
        }
        if (b1.length() == 1) {
            b1 = "0" + b1;
        }
        String cont = "" + r1 + g1 + b1;
        int i = Integer.valueOf(cont, 16);
        return (i | 0x00 << 16 | 0x00 << 8 | 0x00);
    }

    /**
     * Przelicza liczbę int na R, G, B
     *
     * @param rgb int - liczba int oznaczająca kolor;
     * @return String - wartość RGB
     */
    public static String IntToRGB(int rgb) {
        int R = (rgb >> 16) & 0xff;
        int G = (rgb >> 8) & 0xff;
        int B = (rgb >> 0) & 0xff;
        return ("RGB:[" + R + "," + G + "," + B + "]");
    }

    /**
     * Zamienia wartosci R, G, B, A na liczbę int stosowaną w Javie
     *
     * @param r int - składnik czerwony;
     * @param g int - składnik zielony
     * @param b int - składnik niebieski
     * @param a int - składnik alfa
     * @return int - wartość RGB przeliczona na int;
     */
    public static int RGBAToInt(int r, int g, int b, int a) {
        String a1 = Integer.toString(a, 16);
        String r1 = Integer.toString(r, 16);
        String g1 = Integer.toString(g, 16);
        String b1 = Integer.toString(b, 16);
        if (a1.length() == 1) {
            a1 = "0" + a1;
        }
        if (r1.length() == 1) {
            r1 = "0" + r1;
        }
        if (g1.length() == 1) {
            g1 = "0" + g1;
        }
        if (b1.length() == 1) {
            b1 = "0" + b1;
        }
        String cont = "" + a1 + r1 + g1 + b1;
        int i = Integer.valueOf(cont, 16);
        return (i | 0x00 << 24 | 0x00 << 16 | 0x00 << 8 | 0x00);
    }

    /**
     * Przelicza liczbę int na R, G, B, A
     *
     * @param rgba int - liczba int oznaczająca kolor;
     * @return String - wartość RGBA
     */
    public static String IntToRGBA(int rgba) {
        int A = (rgba >> 24) & 0xff;
        int R = (rgba >> 16) & 0xff;
        int G = (rgba >> 8) & 0xff;
        int B = (rgba >> 0) & 0xff;
        return ("RGBA:[" + R + "," + G + "," + B + "," + A + "]");
    }

    public static Color compositedColor(Color rgba1, Color rgba2, int rule) {
        int[] adst = {rgba1.getRed(), rgba1.getGreen(), rgba1.getBlue(),
                rgba1.getAlpha()};
        int[] asrc = {rgba2.getRed(), rgba2.getGreen(), rgba2.getBlue(),
                rgba2.getAlpha()};
        float src = asrc[3] / 255f;
        float dst = adst[3] / 255f;
        float fs = 0;
        float fd = 0;
        switch (rule) {
            case AlphaComposite.CLEAR:
                fs = 0;
                fd = 0;
                break;
            case AlphaComposite.DST:
                fs = 0;
                fd = 1f;
                break;
            case AlphaComposite.DST_ATOP:
                fs = 1f - dst;
                fd = src;
                break;
            case AlphaComposite.DST_IN:
                fs = 0;
                fd = src;
                break;
            case AlphaComposite.DST_OUT:
                fs = 0;
                fd = 1f - src;
                break;
            case AlphaComposite.DST_OVER:
                fs = 1f - dst;
                fd = 1f;
                break;
            case AlphaComposite.SRC:
                fs = 1f;
                fd = 0;
                break;
            case AlphaComposite.SRC_ATOP:
                fs = dst;
                fd = 1f - src;
                break;
            case AlphaComposite.SRC_IN:
                fs = dst;
                fd = 0;
                break;
            case AlphaComposite.SRC_OUT:
                fs = 1f - dst;
                fd = 0;
                break;
            case AlphaComposite.SRC_OVER:
                fs = 1f;
                fd = 1f - src;
                break;
            case AlphaComposite.XOR:
                fs = 1f - dst;
                fd = 1f - src;
                break;
        }
        float a = (fs * src + fd * dst) * 255;
        float r = fs * asrc[0] * src + fd * adst[0] * dst;
        float g = fs * asrc[1] * src + fd * adst[1] * dst;
        float b = fs * asrc[2] * src + fd * adst[2] * dst;
        return new Color(Math.round(r), Math.round(g), Math.round(b),
                Math.round(a));
    }

    /**
     * Przekształca HSL na RGB	 *
     *
     * @param h     Hue - wstopniach 0 - 360.
     * @param s     Saturation - %.
     * @param l     Lumanance - %.
     * @param alpha - 0.0 - 1.0	 *
     * @returns Color
     */
    static Color HSLToRGB(float h, float s, float l, float alpha) {
        if (s < 0.0f || s > 100.0f) {
            String message = "Saturation poza zakresem";
            throw new IllegalArgumentException(message);
        }
        if (l < 0.0f || l > 100.0f) {
            String message = "Luminance poza zakresem";
            throw new IllegalArgumentException(message);
        }
        if (alpha < 0.0f || alpha > 1.0f) {
            String message = "Alpha poza zakresem";
            throw new IllegalArgumentException(message);
        }
        h = h % 360.0f;
        h /= 360f;
        s /= 100f;
        l /= 100f;
        float q;
        if (l < 0.5)
            q = l * (1 + s);
        else
            q = (l + s) - (s * l);
        float p = 2 * l - q;
        float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f)));
        float g = Math.max(0, HueToRGB(p, q, h));
        float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f)));
        r = Math.min(r, 1.0f);
        g = Math.min(g, 1.0f);
        b = Math.min(b, 1.0f);
        return new Color(r, g, b, alpha);
    }

    /**
     * Przekształca RGB na HSL	 *
     *
     * @return tablica [H,L,S].
     */
    static float[] RGBToHSL(Color color) {
        float[] rgb = color.getRGBColorComponents(null);
        float r = rgb[0];
        float g = rgb[1];
        float b = rgb[2];
        //	Minimum and Maximum RGB values are used in the HSL calculations
        float min = Math.min(r, Math.min(g, b));
        float max = Math.max(r, Math.max(g, b));
        //  Calculate the Hue
        float h = 0;
        if (max == min)
            h = 0;
        else if (max == r)
            h = ((60 * (g - b) / (max - min)) + 360) % 360;
        else if (max == g)
            h = (60 * (b - r) / (max - min)) + 120;
        else if (max == b)
            h = (60 * (r - g) / (max - min)) + 240;
        //  Calculate the Luminance
        float l = (max + min) / 2;
        //  Calculate the Saturation
        float s;
        if (max == min)
            s = 0;
        else if (l <= .5f)
            s = (max - min) / (max + min);
        else
            s = (max - min) / (2 - max - min);
        return new float[]{h, s * 100, l * 100};
    }

    static float HueToRGB(float p, float q, float h) {
        if (h < 0)
            h += 1;
        if (h > 1)
            h -= 1;
        if (6 * h < 1) {
            return p + ((q - p) * 6 * h);
        }
        if (2 * h < 1) {
            return q;
        }
        if (3 * h < 2) {
            return p + ((q - p) * 6 * ((2.0f / 3.0f) - h));
        }
        return p;
    }

    static int HSBToRGB(float hue, float saturation, float brightness) {
        return Color.HSBtoRGB(hue, saturation, brightness);
    }

    static float[] RGBToHSB(int r, int g, int b) {
        return Color.RGBtoHSB(r, g, b, null);
    }
}
