// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Colors.
 *
 * Author:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *   Jon A. Cruz <jon@joncruz.org>
 *
 * Copyright (C) 2001-2002 Lauris Kaplinski
 * Copyright (C) 2001 Ximian, Inc.
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <cmath>
#include <cstdio>
#include <vector>
#include <boost/range/combine.hpp>
#include <glib.h>

#include "color.h"
#include "hsluv.h"
#include "strneq.h"
#include "svg/svg-color.h"

#include "svg/css-ostringstream.h"
#include "object/color-profile.h"

#define return_if_fail(x) if (!(x)) { printf("assertion failed: " #x); return; }
#define return_val_if_fail(x, val) if (!(x)) { printf("assertion failed: " #x); return val; }

using Inkscape::CSSOStringStream;

static bool profileMatches(SVGICCColor const &first, SVGICCColor const &second);

static constexpr double PROFILE_EPSILON = 1e-8;

SPColor::SPColor(SPColor const &other)
{
    *this = other;
}

SPColor::SPColor(float r, float g, float b)
{
    set( r, g, b );
}

SPColor::SPColor(guint32 value)
{
    set( value );
}

SPColor &SPColor::operator=(SPColor const &other)
{
    if (this == &other){
        return *this;
    }
    
    set(other.v.c[0], other.v.c[1], other.v.c[2]);
    copyColors(other);
    return *this;
}

/**
 * Returns true if colors match.
 */
bool SPColor::operator == (SPColor const& other) const
{
    bool match =
        (v.c[0] == other.v.c[0]) &&
        (v.c[1] == other.v.c[1]) &&
        (v.c[2] == other.v.c[2]);

    match &= profileMatches(_icc, other._icc);

    return match;
}

/**
 * Returns true if no RGB value differs epsilon or more in both colors,
 * false otherwise.
 */
bool SPColor::isClose(SPColor const& other, float epsilon) const
{
    bool match = (fabs((v.c[0]) - (other.v.c[0])) < epsilon)
        && (fabs((v.c[1]) - (other.v.c[1])) < epsilon)
        && (fabs((v.c[2]) - (other.v.c[2])) < epsilon);

    match &= profileMatches(_icc, other._icc);

    return match;
}

/**
 * Matches two profile colors within PROFILE_EPSILON distance.
 */
static bool profileMatches(SVGICCColor const &first, SVGICCColor const &second)
{
    if (first.colorProfile != second.colorProfile || first.colors.size() != second.colors.size()) {
        return false;
    }

    for (unsigned i = 0; i < first.colors.size(); i++) {
        if (fabs(first.colors[i] - second.colors[i]) > PROFILE_EPSILON) {
            return false;
        }
    }
    return true;
}

/**
 * Sets RGB values and colorspace in color.
 * \pre 0 <={r,g,b}<=1
 */
void SPColor::set(float r, float g, float b)
{
    return_if_fail(r >= 0.0);
    return_if_fail(r <= 1.0);
    return_if_fail(g >= 0.0);
    return_if_fail(g <= 1.0);
    return_if_fail(b >= 0.0);
    return_if_fail(b <= 1.0);

    v.c[0] = r;
    v.c[1] = g;
    v.c[2] = b;

    // Remove icc colors, but not the profile
    unsetColors();
}

/**
 * Converts 32bit value to RGB floats and sets color.
 */
void SPColor::set(guint32 value)
{
    v.c[0] = (value >> 24) / 255.0F;
    v.c[1] = ((value >> 16) & 0xff) / 255.0F;
    v.c[2] = ((value >> 8) & 0xff) / 255.0F;
    // Remove icc colors, but not the profile
    unsetColors();
}

/**
 * Returns true if this color has a color profile set.
 */
bool SPColor::hasColorProfile() const
{
    return !_icc.colorProfile.empty();
}

/**
 * Returns true if there is a defined color for the set profile.
 */
bool SPColor::hasColors() const
{
    return hasColorProfile() && !_icc.colors.empty() && _icc.colors[0] != -1.0;
}

void SPColor::setColorProfile(Inkscape::ColorProfile *profile)
{
    unsetColorProfile();
    if (profile) {
        _icc.colorProfile = profile->name;
        for (int i = 0; i < profile->getChannelCount(); i++) {
            _icc.colors.emplace_back(-1.0);
        }
    }
}

void SPColor::setColors(std::vector<double> &&values)
{
    if (values.size() != _icc.colors.size()) {
        g_error("Can't set profile-based color, wrong number of colors.");
        unsetColors();
        return;
    }
    _icc.colors = std::move(values);
}

void SPColor::copyColors(const SPColor &other)
{
    if (!profileMatches(_icc, other._icc)) {
        _icc = other._icc; // copy
    }
}

void SPColor::setColor(unsigned int index, double value)
{
    if (index < 0 || index > _icc.colors.size()) {
        g_warning("Can't set profile-based color, index out of range.");
    }
    _icc.colors[index] = value;
}

/**
 * Clears the saved profile color, but retains the color profile for imediate reuse.
 */
void SPColor::unsetColors()
{
    for (double &color : _icc.colors) {
        color = -1.0;
    }
}

/**
 * Remove the color profile and save color, reverting to sRGB only.
 */
void SPColor::unsetColorProfile()
{
    _icc.colorProfile = "";
    _icc.colors.clear();
}

/**
 * Convert SPColor with integer alpha value to 32bit RGBA value.
 * \pre alpha < 256
 */
guint32 SPColor::toRGBA32( int alpha ) const
{
    return_val_if_fail (alpha <= 0xff, 0x0);

    if (!is_set()) {
        return SP_RGBA32_U_COMPOSE(0, 0, 0, alpha);
    }

    guint32 rgba = SP_RGBA32_U_COMPOSE( SP_COLOR_F_TO_U(v.c[0]),
                                        SP_COLOR_F_TO_U(v.c[1]),
                                        SP_COLOR_F_TO_U(v.c[2]),
                                        alpha );
    return rgba;
}

/**
 * Convert SPColor with float alpha value to 32bit RGBA value.
 * \pre color != NULL && 0 <= alpha <= 1
 */
guint32 SPColor::toRGBA32( double alpha ) const
{
    return_val_if_fail(alpha >= 0.0, 0x0);
    return_val_if_fail(alpha <= 1.0, 0x0);

    return toRGBA32( static_cast<int>(SP_COLOR_F_TO_U(alpha)) );
}

std::string SPColor::toString() const
{
    CSSOStringStream css;
    char tmp[64] = {0};

    sp_svg_write_color(tmp, sizeof(tmp), toRGBA32(0x0ff));
    css << tmp;

    if (hasColors()) {
        if ( !css.str().empty() ) {
            css << " ";
        }
        css << "icc-color(" << _icc.colorProfile;
        for (double color : _icc.colors) {
            css << ", " << color;
        }
        css << ')';
    }

    return css.str();
}

/**
 * Set the color from the given string.
 *
 * @param str - The CSS3 formatted string input (see toString)
 * @returns true if the color was parsed correctly.
 */
bool SPColor::fromString(char const *str)
{
    guint32 const rgb0 = sp_svg_read_color(str, &str, 0xff);
    if (rgb0 == 0xff) {
        return false;
    }
    set(rgb0);
    while (g_ascii_isspace(*str)) {
        ++str;
    }
    if (strneq(str, "icc-color(", 10)) {
        if (!sp_svg_read_icc_color(str, &str, &_icc)) {
            g_warning("Couldn't parse icc-color format in css.");
            unsetColorProfile();
        }
    }
    return true;
}

/**
 * Fill rgb float array with values from SPColor.
 * \pre color != NULL && rgb != NULL && rgb[0-2] is meaningful
 */
void
SPColor::get_rgb_floatv(float *rgb) const
{
    return_if_fail (rgb != nullptr);
    if (!is_set()) {
        return;
    }

    rgb[0] = v.c[0];
    rgb[1] = v.c[1];
    rgb[2] = v.c[2];
}

/**
 * Fill cmyk float array with values from SPColor.
 * \pre color != NULL && cmyk != NULL && cmyk[0-3] is meaningful
 */
void
SPColor::get_cmyk_floatv(float *cmyk) const
{
    return_if_fail (cmyk != nullptr);
    if (!is_set()) {
        return;
    }

    SPColor::rgb_to_cmyk_floatv( cmyk,
                                 v.c[0],
                                 v.c[1],
                                 v.c[2] );
}

/* Plain mode helpers */

/**
 * Fill hsv float array from r,g,b float values.
 */
void
SPColor::rgb_to_hsv_floatv (float *hsv, float r, float g, float b)
{
    float max, min, delta;

    max = MAX (MAX (r, g), b);
    min = MIN (MIN (r, g), b);
    delta = max - min;

    hsv[2] = max;

    if (max > 0) {
        hsv[1] = delta / max;
    } else {
        hsv[1] = 0.0;
    }

    if (hsv[1] != 0.0) {
        if (r == max) {
            hsv[0] = (g - b) / delta;
        } else if (g == max) {
            hsv[0] = 2.0 + (b - r) / delta;
        } else {
            hsv[0] = 4.0 + (r - g) / delta;
        }

        hsv[0] = hsv[0] / 6.0;

        if (hsv[0] < 0) hsv[0] += 1.0;
    }
    else
        hsv[0] = 0.0;
}

/**
 * Fill rgb float array from h,s,v float values.
 */
void
SPColor::hsv_to_rgb_floatv (float *rgb, float h, float s, float v)
{
    double f, w, q, t, d;

    d = h * 5.99999999;
    f = d - floor (d);
    w = v * (1.0 - s);
    q = v * (1.0 - (s * f));
    t = v * (1.0 - (s * (1.0 - f)));

    if (d < 1.0) {
        *rgb++ = v;
        *rgb++ = t;
        *rgb++ = w;
    } else if (d < 2.0) {
        *rgb++ = q;
        *rgb++ = v;
        *rgb++ = w;
    } else if (d < 3.0) {
        *rgb++ = w;
        *rgb++ = v;
        *rgb++ = t;
    } else if (d < 4.0) {
        *rgb++ = w;
        *rgb++ = q;
        *rgb++ = v;
    } else if (d < 5.0) {
        *rgb++ = t;
        *rgb++ = w;
        *rgb++ = v;
    } else {
        *rgb++ = v;
        *rgb++ = w;
        *rgb++ = q;
    }
}

/**
 * Fill hsl float array from r,g,b float values.
 */
void
SPColor::rgb_to_hsl_floatv (float *hsl, float r, float g, float b)
{
    float max = MAX (MAX (r, g), b);
    float min = MIN (MIN (r, g), b);
    float delta = max - min;

    hsl[2] = (max + min)/2.0;

    if (delta == 0) {
        hsl[0] = 0;
        hsl[1] = 0;
    } else {
        if (hsl[2] <= 0.5)
            hsl[1] = delta / (max + min);
        else
            hsl[1] = delta / (2 - max - min);

        if (r == max) hsl[0] = (g - b) / delta;
        else if (g == max) hsl[0] = 2.0 + (b - r) / delta;
        else if (b == max) hsl[0] = 4.0 + (r - g) / delta;

        hsl[0] = hsl[0] / 6.0;

        if (hsl[0] < 0) hsl[0] += 1;
        if (hsl[0] > 1) hsl[0] -= 1;
    }
}

static float
hue_2_rgb (float v1, float v2, float h)
{
    if (h < 0) h += 6.0;
    if (h > 6) h -= 6.0;

    if (h < 1) return v1 + (v2 - v1) * h;
    if (h < 3) return v2;
    if (h < 4) return v1 + (v2 - v1) * (4 - h);
    return v1;
}

/**
 * Fill rgb float array from h,s,l float values.
 */
void
SPColor::hsl_to_rgb_floatv (float *rgb, float h, float s, float l)
{
    if (s == 0) {
        rgb[0] = l;
        rgb[1] = l;
        rgb[2] = l;
    } else {
        float v2;
        if (l < 0.5) {
            v2 = l * (1 + s);
        } else {
            v2 = l + s - l*s;
        }
        float v1 = 2*l - v2;

        rgb[0] = hue_2_rgb (v1, v2, h*6 + 2.0);
        rgb[1] = hue_2_rgb (v1, v2, h*6);
        rgb[2] = hue_2_rgb (v1, v2, h*6 - 2.0);
    }
}

/**
 * Fill cmyk float array from r,g,b float values.
 */
void
SPColor::rgb_to_cmyk_floatv (float *cmyk, float r, float g, float b)
{
    float c, m, y, k, kd;

    c = 1.0 - r;
    m = 1.0 - g;
    y = 1.0 - b;
    k = MIN (MIN (c, m), y);

    c = c - k;
    m = m - k;
    y = y - k;

    kd = 1.0 - k;

    if (kd > 1e-9) {
        c = c / kd;
        m = m / kd;
        y = y / kd;
    }

    cmyk[0] = c;
    cmyk[1] = m;
    cmyk[2] = y;
    cmyk[3] = k;
}

/**
 * Fill rgb float array from c,m,y,k float values.
 */
void
SPColor::cmyk_to_rgb_floatv (float *rgb, float c, float m, float y, float k)
{
    float kd;

    kd = 1.0 - k;

    c = c * kd;
    m = m * kd;
    y = y * kd;

    c = c + k;
    m = m + k;
    y = y + k;

    rgb[0] = 1.0 - c;
    rgb[1] = 1.0 - m;
    rgb[2] = 1.0 - y;
}

/**
 * Fill hsluv float array from r,g,b float values.
 */
void
SPColor::rgb_to_hsluv_floatv(float *hsluv, float r, float g, float b)
{
    auto tmp = Hsluv::rgb_to_hsluv(r, g, b);
    tmp[0] /= 360.0;
    tmp[1] /= 100.0;
    tmp[2] /= 100.0;
    for (size_t i : {0, 1, 2}) {
        hsluv[i] = std::clamp(tmp[i], 0.0, 1.0);
    }
}

/**
 * Fill rgb float array from h,s,l float values.
 */
void
SPColor::hsluv_to_rgb_floatv(float *rgb, float h, float s, float l)
{
    auto tmp = Hsluv::hsluv_to_rgb(h * 360, s * 100, l * 100);
    for (size_t i : {0, 1, 2}) {
        rgb[i] = tmp[i];
    }
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
