/*!
 * \file lib/raster/color_xform.c
 *
 * \brief Raster Library - Colors management
 *
 * (C) 2001-2021 by the GRASS Development Team
 *
 * This program is free software under the GNU General Public License
 * (>=v2). Read the file COPYING that comes with GRASS for details.
 *
 * \author Original author CERL
 */

#include <math.h>

#include <grass/gis.h>
#include <grass/raster.h>

/*!
 * \brief Make histogram-stretched version of existing color table
 *
 * Generates a histogram contrast-stretched color table that goes from
 * the histogram information in the Cell_stats structure <i>statf</i>.
 * (See \ref Raster_Histograms).
 *
 * \param[out] dst struct to hold new colors
 * \param src struct containing original colors
 * \param statf cell stats info
 */
void Rast_histogram_eq_colors(struct Colors *dst, struct Colors *src,
                              struct Cell_stats *statf)
{
    DCELL min, max;
    int red, grn, blu;
    int red2, grn2, blu2;
    long count, total, sum;
    CELL cat, prev;
    int first;

    Rast_init_colors(dst);

    Rast_get_d_color_range(&min, &max, src);

    Rast_get_default_color(&red, &grn, &blu, src);
    Rast_set_default_color(red, grn, blu, dst);

    Rast_get_null_value_color(&red, &grn, &blu, src);
    Rast_set_null_value_color(red, grn, blu, dst);

    total = 0;

    Rast_rewind_cell_stats(statf);
    while (Rast_next_cell_stat(&cat, &count, statf))
        if (count > 0)
            total += count;

    if (total <= 0)
        return;

    sum = 0;
    prev = 0;
    first = 1;

    Rast_rewind_cell_stats(statf);
    while (Rast_next_cell_stat(&cat, &count, statf)) {
        DCELL x;

        if (count <= 0)
            continue;

        x = min + (max - min) * (sum + count / 2.0) / total;
        Rast_get_d_color(&x, &red2, &grn2, &blu2, src);

        sum += count;

        if (!first && red2 == red && blu2 == blu && grn2 == grn)
            continue;

        if (!first)
            Rast_add_c_color_rule(&prev, red, grn, blu, &cat, red2, grn2, blu2,
                                  dst);

        first = 0;

        prev = cat;
        red = red2;
        grn = grn2;
        blu = blu2;
    }

    if (!first && cat > prev)
        Rast_add_c_color_rule(&prev, red, grn, blu, &cat, red2, grn2, blu2,
                              dst);
}

/*!
 * \brief Make histogram-stretched version of existing color table (FP version)
 *
 * Generates a histogram contrast-stretched color table that goes from
 * the histogram information in the FP_stats structure <b>statf.</b>
 * (See \ref Raster_Histograms).
 *
 * \param[out] dst struct to hold new colors
 * \param src struct containing original colors
 * \param statf cell stats info
 */
void Rast_histogram_eq_fp_colors(struct Colors *dst, struct Colors *src,
                                 struct FP_stats *statf)
{
    DCELL min, max;
    int red, grn, blu;
    int red2, grn2, blu2;
    unsigned long sum;
    DCELL val, val2;
    int first;
    int i;

    Rast_init_colors(dst);

    Rast_get_d_color_range(&min, &max, src);

    Rast_get_default_color(&red, &grn, &blu, src);
    Rast_set_default_color(red, grn, blu, dst);

    Rast_get_null_value_color(&red, &grn, &blu, src);
    Rast_set_null_value_color(red, grn, blu, dst);

    if (!statf->total)
        return;

    sum = 0;
    first = 1;

    for (i = 0; i <= statf->count; i++) {
        DCELL x;

        val2 = statf->min + (statf->max - statf->min) * i / statf->count;
        if (statf->geometric)
            val2 = exp(val2);
        if (statf->geom_abs)
            val2 = exp(val2) - 1;
        if (statf->flip)
            val2 = -val2;
        x = min + (max - min) * sum / statf->total;
        Rast_get_d_color(&x, &red2, &grn2, &blu2, src);

        if (i < statf->count)
            sum += statf->stats[i];

        if (!first && red2 == red && blu2 == blu && grn2 == grn)
            continue;

        if (!first)
            Rast_add_d_color_rule(&val, red, grn, blu, &val2, red2, grn2, blu2,
                                  dst);

        first = 0;

        if (i == statf->count)
            break;

        val = val2;
        red = red2;
        grn = grn2;
        blu = blu2;
    }

    if (!first && val2 > val)
        Rast_add_d_color_rule(&val, red, grn, blu, &val2, red2, grn2, blu2,
                              dst);
}

/*!
 * \brief Make logarithmically-scaled version of an existing color table
 *
 * \param[out] dst struct to hold new colors
 * \param src struct containing original colors
 * \param samples number of samples
 */
void Rast_log_colors(struct Colors *dst, struct Colors *src, int samples)
{
    DCELL min, max;
    double delta, lmin, lmax;
    int red, grn, blu;
    DCELL prev;
    int i;

    Rast_init_colors(dst);

    Rast_get_d_color_range(&min, &max, src);

    if (min <= 0.0) {
        /* shift cell values by 1 - min so that they are in [1, max - min + 1]
         */
        delta = 1 - min;
        lmin = log(min + delta);
        lmax = log(max + delta);
    }
    else {
        delta = 0;
        lmin = log(min);
        lmax = log(max);
    }

    Rast_get_default_color(&red, &grn, &blu, src);
    Rast_set_default_color(red, grn, blu, dst);

    Rast_get_null_value_color(&red, &grn, &blu, src);
    Rast_set_null_value_color(red, grn, blu, dst);

    for (i = 0; i <= samples; i++) {
        int red2, grn2, blu2;
        double lx;
        DCELL x, y;

        y = min + (max - min) * i / samples;
        Rast_get_d_color(&y, &red2, &grn2, &blu2, src);

        if (i == 0)
            x = min;
        else if (i == samples)
            x = max;
        else {
            lx = lmin + (lmax - lmin) * i / samples;
            /* restore cell values approximately */
            x = exp(lx) - delta;
        }

        if (i > 0)
            Rast_add_d_color_rule(&prev, red, grn, blu, &x, red2, grn2, blu2,
                                  dst);

        prev = x;

        red = red2;
        grn = grn2;
        blu = blu2;
    }
}

/*!
 * \brief Make logarithmically-scaled version of an existing color
 * table, allowing for signed values
 *
 * \param[out] dst struct to hold new colors
 * \param src struct containing original colors
 * \param samples number of samples
 */
void Rast_abs_log_colors(struct Colors *dst, struct Colors *src, int samples)
{
    DCELL min, max;
    double absmin, absmax, amin, amax, delta, lamin, lamax;
    int red, grn, blu;
    DCELL prev;
    int i;

    Rast_init_colors(dst);

    Rast_get_d_color_range(&min, &max, src);

    absmin = fabs(min);
    absmax = fabs(max);
    amin = MIN(absmin, absmax);
    amax = MAX(absmin, absmax);

    if (min * max <= 0.0) {
        /* 0 <= abs(cell) <= amax */
        amin = 0;
        /* use the same shifting for Rast_log_colors */
        delta = 1 - amin;
        lamin = log(amin + delta);
        lamax = log(amax + delta);
    }
    else {
        /* 0 < amin <= abs(cell) <= amax */
        delta = 0;
        lamin = log(amin);
        lamax = log(amax);
    }

    Rast_get_default_color(&red, &grn, &blu, src);
    Rast_set_default_color(red, grn, blu, dst);

    Rast_get_null_value_color(&red, &grn, &blu, src);
    Rast_set_null_value_color(red, grn, blu, dst);

    for (i = 0; i <= samples; i++) {
        int red2, grn2, blu2;
        double lx;
        DCELL x, y;

        y = min + (max - min) * i / samples;
        Rast_get_d_color(&y, &red2, &grn2, &blu2, src);

        if (i == 0)
            x = amin;
        else if (i == samples)
            x = amax;
        else {
            lx = lamin + (lamax - lamin) * i / samples;
            /* restore cell values approximately */
            x = exp(lx) - delta;
        }

        if (i > 0) {
            DCELL x0 = prev, x1 = x;

            Rast_add_d_color_rule(&x0, red, grn, blu, &x1, red2, grn2, blu2,
                                  dst);
            x0 = -x0;
            x1 = -x1;
            Rast_add_d_color_rule(&x0, red, grn, blu, &x1, red2, grn2, blu2,
                                  dst);
        }

        prev = x;

        red = red2;
        grn = grn2;
        blu = blu2;
    }
}
