/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2017 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text.pdf;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.error_messages.MessageLocalization;

import java.util.Arrays;

public class PdfLabColor implements ICachedColorSpace {
    float[] whitePoint = new float[]{0.9505f, 1.0f, 1.0890f};
    float[] blackPoint = null;
    float[] range = null;

    public PdfLabColor() {}

    public PdfLabColor(float[] whitePoint) {
        if (whitePoint == null
                || whitePoint.length != 3
                || whitePoint[0] < 0.000001f || whitePoint[2] < 0.000001f
                || whitePoint[1] < 0.999999f || whitePoint[1] > 1.000001f)
            throw new RuntimeException(MessageLocalization.getComposedMessage("lab.cs.white.point"));
        this.whitePoint = whitePoint;
    }

    public PdfLabColor(float[] whitePoint, float[] blackPoint) {
        this(whitePoint);
        this.blackPoint = blackPoint;
    }

    public PdfLabColor(float[] whitePoint, float[] blackPoint, float[] range) {
        this(whitePoint, blackPoint);
        this.range = range;
    }

    public PdfObject getPdfObject(PdfWriter writer) {
        PdfArray array = new PdfArray(PdfName.LAB);
        PdfDictionary dictionary = new PdfDictionary();
        if (whitePoint == null
                || whitePoint.length != 3
                || whitePoint[0] < 0.000001f || whitePoint[2] < 0.000001f
                || whitePoint[1] < 0.999999f || whitePoint[1] > 1.000001f)
            throw new RuntimeException(MessageLocalization.getComposedMessage("lab.cs.white.point"));
        dictionary.put(PdfName.WHITEPOINT, new PdfArray(whitePoint));
        if (blackPoint != null) {
            if (blackPoint.length != 3
                    || blackPoint[0] < -0.000001f || blackPoint[1] < -0.000001f || blackPoint[2] < -0.000001f)
                throw new RuntimeException(MessageLocalization.getComposedMessage("lab.cs.black.point"));
            dictionary.put(PdfName.BLACKPOINT, new PdfArray(blackPoint));
        }
        if (range != null) {
            if (range.length != 4 || range[0] > range[1] || range[2] > range[3])
                throw new RuntimeException(MessageLocalization.getComposedMessage("lab.cs.range"));
            dictionary.put(PdfName.RANGE, new PdfArray(range));
        }
        array.add(dictionary);
        return array;
    }

    public BaseColor lab2Rgb(float l, float a, float b) {
        double[] clinear = lab2RgbLinear(l, a, b);
        return new BaseColor((float)clinear[0], (float)clinear[1], (float)clinear[2]);
    }

    CMYKColor lab2Cmyk(float l, float a, float b) {
        double[] clinear = lab2RgbLinear(l, a, b);

        double r = clinear[0];
        double g = clinear[1];
        double bee = clinear[2];
        double computedC = 0, computedM = 0, computedY = 0, computedK = 0;

        // BLACK
        if (r==0 && g==0 && b==0) {
            computedK = 1;
        } else {
            computedC = 1 - r;
            computedM = 1 - g;
            computedY = 1 - bee;

            double minCMY = Math.min(computedC,
                    Math.min(computedM,computedY));
            computedC = (computedC - minCMY) / (1 - minCMY) ;
            computedM = (computedM - minCMY) / (1 - minCMY) ;
            computedY = (computedY - minCMY) / (1 - minCMY) ;
            computedK = minCMY;
        }

        return new CMYKColor((float)computedC, (float)computedM, (float)computedY, (float)computedK);
    }

    protected double[] lab2RgbLinear(float l, float a, float b) {
        if (range != null && range.length == 4) {
            if (a < range[0])
                a = range[0];
            if (a > range[1])
                a = range[1];
            if (b < range[2])
                b = range[2];
            if (b > range[3])
                b = range[3];
        }
        double theta = 6.0 / 29.0;

        double fy = (l + 16) / 116.0;
        double fx = fy + (a / 500.0);
        double fz = fy - (b / 200.0);

        double x = fx > theta ? whitePoint[0] * (fx * fx * fx) : (fx - 16.0/116.0) * 3 * (theta * theta) * whitePoint[0];
        double y = fy > theta ? whitePoint[1] * (fy * fy * fy) : (fy - 16.0/116.0) * 3 * (theta * theta) * whitePoint[1];
        double z = fz > theta ? whitePoint[2] * (fz * fz * fz) : (fz - 16.0/116.0) * 3 * (theta * theta) * whitePoint[2];

        double[] clinear = new double[3];
        clinear[0] = x * 3.2410 - y * 1.5374 - z * 0.4986; // red
        clinear[1] = -x * 0.9692 + y * 1.8760 - z * 0.0416; // green
        clinear[2] = x * 0.0556 - y * 0.2040 + z * 1.0570; // blue

        for (int i = 0; i < 3; i++) {
            clinear[i] = (clinear[i] <= 0.0031308)
                    ? 12.92 * clinear[i]
                    : (1 + 0.055) * Math.pow(clinear[i], (1.0/2.4)) - 0.055;
            if (clinear[i] < 0)
                clinear[i] = 0;
            else if (clinear[i] > 1f)
                clinear[i] = 1.0;
        }

        return clinear;
    }

    public LabColor rgb2lab(BaseColor baseColor) {
        double rLinear = baseColor.getRed() / 255f;
        double gLinear = baseColor.getGreen() / 255f;
        double bLinear = baseColor.getBlue() / 255f;

        // convert to a sRGB form
        double r = (rLinear > 0.04045) ? Math.pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear/12.92);
        double g = (gLinear > 0.04045) ? Math.pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear/12.92);
        double b = (bLinear > 0.04045) ? Math.pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear/12.92);

        // converts
        double x = r * 0.4124 + g * 0.3576 + b * 0.1805;
        double y = r * 0.2126 + g * 0.7152 + b * 0.0722;
        double z = r * 0.0193 + g * 0.1192 + b * 0.9505;

        float l = Math.round((116.0 * fXyz(y/whitePoint[1]) - 16) * 1000) / 1000f;
        float a = Math.round((500.0*(fXyz(x/whitePoint[0]) - fXyz(y/whitePoint[1]))) * 1000) / 1000f;
        float bee = Math.round((200.0*(fXyz(y/whitePoint[1]) - fXyz(z/whitePoint[2]))) * 1000) / 1000f;

        return new LabColor(this, l, a, bee);
    }

    private static double fXyz(double t) {
        return ((t > 0.008856) ? Math.pow(t, (1.0/3.0)) : (7.787*t + 16.0/116.0));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PdfLabColor)) return false;

        PdfLabColor that = (PdfLabColor) o;

        if (!Arrays.equals(blackPoint, that.blackPoint)) return false;
        if (!Arrays.equals(range, that.range)) return false;
        if (!Arrays.equals(whitePoint, that.whitePoint)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = Arrays.hashCode(whitePoint);
        result = 31 * result + (blackPoint != null ? Arrays.hashCode(blackPoint) : 0);
        result = 31 * result + (range != null ? Arrays.hashCode(range) : 0);
        return result;
    }
}
