/*!
   \file lib/raster/color_rule.c

   \brief Raster Library - Color rules.

   (C) 2001-2009 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 <grass/gis.h>
#include <grass/raster.h>

#define LIMIT(x)      \
    if (x < 0)        \
        x = 0;        \
    else if (x > 255) \
        x = 255;

static void add_color_rule(const void *, int, int, int, const void *, int, int,
                           int, struct _Color_Info_ *, int, DCELL *, DCELL *,
                           RASTER_MAP_TYPE);

/*!
   \brief Adds the floating-point color rule (DCELL version)

   See Rast_add_color_rule() for details.

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure
 */
void Rast_add_d_color_rule(const DCELL *val1, int r1, int g1, int b1,
                           const DCELL *val2, int r2, int g2, int b2,
                           struct Colors *colors)
{
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->fixed,
                   colors->version, &colors->cmin, &colors->cmax, DCELL_TYPE);
}

/*!
   \brief Adds the floating-point color rule (FCELL version)

   See Rast_add_color_rule() for details.

   \param cat1 cell value
   \param r1,g1,b1 color value
   \param cat2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure
 */
void Rast_add_f_color_rule(const FCELL *cat1, int r1, int g1, int b1,
                           const FCELL *cat2, int r2, int g2, int b2,
                           struct Colors *colors)
{
    add_color_rule(cat1, r1, g1, b1, cat2, r2, g2, b2, &colors->fixed,
                   colors->version, &colors->cmin, &colors->cmax, FCELL_TYPE);
}

/*!
   \brief Adds the integer color rule (CELL version)

   See Rast_add_color_rule() for details.

   \param cat1 cell value
   \param r1,g1,b1 color value
   \param cat2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure
 */
void Rast_add_c_color_rule(const CELL *cat1, int r1, int g1, int b1,
                           const CELL *cat2, int r2, int g2, int b2,
                           struct Colors *colors)
{
    add_color_rule(cat1, r1, g1, b1, cat2, r2, g2, b2, &colors->fixed,
                   colors->version, &colors->cmin, &colors->cmax, CELL_TYPE);
}

/*!
   \brief Adds the color rule

   Adds the floating-point rule that the range [<em>v1,v2</em>] gets a
   linear ramp of colors from [<em>r1,g1,b1</em>] to
   [<em>r2,g2,b2</em>].
   If either <em>v1</em> or <em>v2</em> is the NULL-value, this call is
   converted ino <tt>Rast_set_null_value_color (r1, g1, b1, colors)</tt>

   - If <em>map_type</em> is CELL_TYPE, calls Rast_add_c_color_rule()
   - If <em>map_type</em> is FCELL_TYPE, calls Rast_add_f_color_rule()
   - If <em>map_type</em> is DCELL_TYPE, calls Rast_add_d_color_rule()

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure
   \param data_type raster data type (CELL, FCELL, DCELL)
 */
void Rast_add_color_rule(const void *val1, int r1, int g1, int b1,
                         const void *val2, int r2, int g2, int b2,
                         struct Colors *colors, RASTER_MAP_TYPE data_type)
{
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->fixed,
                   colors->version, &colors->cmin, &colors->cmax, data_type);
}

/*!
   \brief Add modular floating-point color rule (DCELL version)

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure

   \return -1 on failure
   \return 1 on success
 */
int Rast_add_modular_d_color_rule(const DCELL *val1, int r1, int g1, int b1,
                                  const DCELL *val2, int r2, int g2, int b2,
                                  struct Colors *colors)
{
    DCELL min, max;

    if (colors->version < 0)
        return -1; /* can't use this on 3.0 colors */
    min = colors->cmin;
    max = colors->cmax;
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->modular, 0,
                   &colors->cmin, &colors->cmax, DCELL_TYPE);
    colors->cmin = min; /* don't reset these */
    colors->cmax = max;

    return 1;
}

/*!
   \brief Add modular floating-point color rule (FCELL version)

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure

   \return -1 on failure
   \return 1 on success
 */
int Rast_add_modular_f_color_rule(const FCELL *val1, int r1, int g1, int b1,
                                  const FCELL *val2, int r2, int g2, int b2,
                                  struct Colors *colors)
{
    DCELL min, max;

    if (colors->version < 0)
        return -1; /* can;t use this on 3.0 colors */
    min = colors->cmin;
    max = colors->cmax;
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->modular, 0,
                   &colors->cmin, &colors->cmax, FCELL_TYPE);
    colors->cmin = min; /* don't reset these */
    colors->cmax = max;

    return 1;
}

/*!
   \brief Add modular integer color rule (CELL version)

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure

   \return -1 on failure
   \return 1 on success
 */
int Rast_add_modular_c_color_rule(const CELL *val1, int r1, int g1, int b1,
                                  const CELL *val2, int r2, int g2, int b2,
                                  struct Colors *colors)
{
    CELL min, max;

    if (colors->version < 0)
        return -1; /* can;t use this on 3.0 colors */
    min = colors->cmin;
    max = colors->cmax;
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->modular, 0,
                   &colors->cmin, &colors->cmax, CELL_TYPE);
    colors->cmin = min; /* don't reset these */
    colors->cmax = max;

    return 1;
}

/*!
   \brief Add modular color rule

   \todo Question: shouldn't this function call
   G_add_modular_<data_type>_raster_color_rule() instead?

   \param val1 cell value
   \param r1,g1,b1 color value
   \param val2 cell value
   \param r2,g2,b2 color value
   \param[in,out] colors pointer to color table structure
   \param data_type raster data type

   \return -1 on failure
   \return 1 on success
 */
int Rast_add_modular_color_rule(const void *val1, int r1, int g1, int b1,
                                const void *val2, int r2, int g2, int b2,
                                struct Colors *colors,
                                RASTER_MAP_TYPE data_type)
{
    CELL min, max;

    if (colors->version < 0)
        return -1; /* can't use this on 3.0 colors */
    min = colors->cmin;
    max = colors->cmax;
    add_color_rule(val1, r1, g1, b1, val2, r2, g2, b2, &colors->modular, 0,
                   &colors->cmin, &colors->cmax, data_type);
    colors->cmin = min; /* don't reset these */
    colors->cmax = max;

    return 1;
}

static void add_color_rule(const void *pt1, int r1, int g1, int b1,
                           const void *pt2, int r2, int g2, int b2,
                           struct _Color_Info_ *cp, int version, DCELL *cmin,
                           DCELL *cmax, RASTER_MAP_TYPE data_type)
{
    struct _Color_Rule_ *rule, *next;
    unsigned char red, grn, blu;
    DCELL min, max, val1, val2;
    CELL cat;

    val1 = Rast_get_d_value(pt1, data_type);
    val2 = Rast_get_d_value(pt2, data_type);
    /* allocate a low:high rule */
    rule = (struct _Color_Rule_ *)G_malloc(sizeof(*rule));
    rule->next = rule->prev = NULL;

    /* make sure colors are in the range [0,255] */
    LIMIT(r1);
    LIMIT(g1);
    LIMIT(b1);
    LIMIT(r2);
    LIMIT(g2);
    LIMIT(b2);

    /* val1==val2, use average color */
    /* otherwise make sure low < high */
    if (val1 == val2) {
        rule->low.value = rule->high.value = val1;
        rule->low.red = rule->high.red = (r1 + r2) / 2;
        rule->low.grn = rule->high.grn = (g1 + g2) / 2;
        rule->low.blu = rule->high.blu = (b1 + b2) / 2;
    }
    else if (val1 < val2) {
        rule->low.value = val1;
        rule->low.red = r1;
        rule->low.grn = g1;
        rule->low.blu = b1;

        rule->high.value = val2;
        rule->high.red = r2;
        rule->high.grn = g2;
        rule->high.blu = b2;
    }
    else {
        rule->low.value = val2;
        rule->low.red = r2;
        rule->low.grn = g2;
        rule->low.blu = b2;

        rule->high.value = val1;
        rule->high.red = r1;
        rule->high.grn = g1;
        rule->high.blu = b1;
    }

    /* keep track of the overall min and max, excluding null */
    if (Rast_is_d_null_value(&(rule->low.value)))
        return;
    if (Rast_is_d_null_value(&(rule->high.value)))
        return;
    min = rule->low.value;
    max = rule->high.value;
    if (min <= max) {
        if (cp->min > cp->max) {
            cp->min = min;
            cp->max = max;
        }
        else {
            if (cp->min > min)
                cp->min = min;
            if (cp->max < max)
                cp->max = max;
        }
    }
    if (*cmin > *cmax) {
        *cmin = cp->min;
        *cmax = cp->max;
    }
    else {
        if (*cmin > cp->min)
            *cmin = cp->min;
        if (*cmax < cp->max)
            *cmax = cp->max;
    }

    /* If version is old style (i.e., pre 4.0),
     *     interpolate this rule from min to max
     *     and insert each cat into the lookup table.
     *     Then free the rule.
     * Otherwise, free the lookup table, if active.
     *     G_organize_colors() will regenerate it
     *     Link this rule into the list of rules
     */

    if (version < 0) {
        for (cat = (CELL)min; cat <= (CELL)max; cat++) {
            Rast__interpolate_color_rule((DCELL)cat, &red, &grn, &blu, rule);
            Rast__insert_color_into_lookup(cat, (int)red, (int)grn, (int)blu,
                                           cp);
        }
        G_free(rule);
    }
    else {
        if (cp->rules)
            cp->rules->prev = rule;
        rule->next = cp->rules;
        cp->rules = rule;

        /* prune the rules:
         * remove all rules that are contained by this rule
         */
        min = rule->low.value;  /* mod 4.1 */
        max = rule->high.value; /* mod 4.1 */
        cp->n_rules++;
        for (rule = rule->next; rule; rule = next) {
            next = rule->next; /* has to be done here, not in for stmt */
            if (min <= rule->low.value && max >= rule->high.value) {
                if ((rule->prev->next = next)) /* remove from the list */
                    next->prev = rule->prev;
                G_free(rule);
                cp->n_rules--;
            }
        }

        /* free lookup array, if allocated */
        Rast__color_free_lookup(cp);
        Rast__color_free_fp_lookup(cp);
    }
}
