/*=============================================================================
  This file contains fuzzy color matching code.

  It is all based on algorithms and methods by Kenan Kalajdzic
  <kenan@unix.ba> in 2006.
=============================================================================*/

#include "netpbm/pm_c_util.h"
#include "netpbm/nstring.h"
#include "ppm.h"

typedef double fzLog;
    /* fuzzy logic truth value */

/*----------------------------------------------------------------------------
   Member functions
-----------------------------------------------------------------------------*/

static fzLog
memberS(fzLog const x1,
        fzLog const x2,
        fzLog const x) {

    fzLog retval;

    if (x <= x1)
        retval = 0;
    else if (x > x1 && x <= x2)
        retval = (x - x1) / (x2 - x1);
    else
        retval = 1;

    return retval;
}



static fzLog
memberZ(fzLog const x1,
        fzLog const x2,
        fzLog const x) {

    fzLog retval;

    if (x <= x1)
        retval = 1;
    else if (x > x1 && x <= x2)
        retval = (x2 - x) / (x2 - x1);
    else
        retval = 0;

    return retval;
}



static fzLog
memberTrapez(fzLog const x1,
             fzLog const x2,
             fzLog const x3,
             fzLog const x4,
             fzLog const x) {

    fzLog retval;

    if (x <= x1 || x > x4)
        retval = 0;
    else if (x > x1 && x <= x2)
        retval = (x - x1) / (x2 - x1);
    else if (x > x2 && x <= x3)
        retval = 1;
    else
        retval = (x4 - x) / (x4 - x3);

    return retval;
}



/*----------------------------------------------------------------------------
   Hue membership functions
-----------------------------------------------------------------------------*/

static fzLog
hueIsAround000(double const hue) {

    return memberZ(10, 20, hue);
}



static fzLog
hueIsAround015(double const hue) {

    return memberZ(20, 40, hue);
}



static fzLog
hueIsAround030(double const hue) {

    return memberTrapez(10, 20, 40, 60, hue);
}



static fzLog
hueIsAround060(double const hue) {

    return memberTrapez(40, 50, 60, 70, hue);
}



static fzLog
hueIsAround120(double const hue) {

    return memberTrapez(60, 70, 150, 180, hue);
}



static fzLog
hueIsAround180(double const hue) {

    return memberTrapez(150, 180, 240, 260, hue);
}



static fzLog
hueIsAround270(double const hue) {

    return memberTrapez(240, 260, 290, 310, hue);
}



static fzLog
hueIsAround320(double const hue) {

    return memberTrapez(290, 310, 320, 350, hue);
}



static fzLog
hueIsAround360(double const hue) {

    return memberS(320, 350, hue);
}



/*----------------------------------------------------------------------------
   Saturation membership functions
-----------------------------------------------------------------------------*/

static fzLog
satIsVeryLow(double const sat) {

    return memberZ(0.03, 0.08, sat);
}



static fzLog
satIsLow(double const sat) {
    return memberTrapez(0.03, 0.08, 0.17, 0.2, sat);
}



static fzLog
satIsMedium(double const sat) {

    return memberTrapez(0.17, 0.2, 0.6, 0.8, sat);
}



static fzLog
satIsHigh(double const sat) {

    return memberS(0.6, 0.8, sat);
}



/*----------------------------------------------------------------------------
   Value membership functions
-----------------------------------------------------------------------------*/

static fzLog
valIsVeryLow(double const val) {

    return memberZ(0.05, 0.2, val);
}



static fzLog
valIsLow(double const val) {

    return memberTrapez(0.05, 0.2, 0.25, 0.3, val);
}



static fzLog
valIsMedium(double const val) {

    return memberTrapez(0.25, 0.3, 0.6, 0.7, val);
}



static fzLog
valIsHigh(double const val) {

    return memberTrapez(0.6, 0.7, 0.95, 0.97, val);
}



static fzLog
valIsVeryHigh(double const val) {

    return memberS(0.95, 0.97, val);
}



/*----------------------------------------------------------------------------
   Fuzzy logic functions
-----------------------------------------------------------------------------*/

static fzLog
fzAnd(fzLog const opLeft,
      fzLog const opRight) {

    return MIN(opLeft, opRight);
}



static fzLog
fzOr(fzLog const opLeft,
     fzLog const opRight) {

    return MAX(opLeft, opRight);
}



static fzLog
fzNot(fzLog const op) {
    return 1.0 - op;
}



/*----------------------------------------------------------------------------
   Fuzzy color matching
-----------------------------------------------------------------------------*/

static void
matchBk(pixel     const color,
        pixval    const maxval,
        fzLog (* const bkMatchP)[BKCOLOR_COUNT]) {

    struct hsv const hsv = ppm_hsv_from_color(color, maxval);

    fzLog const satVeryLow = satIsVeryLow(hsv.s);
    fzLog const satLow     = satIsLow(hsv.s);
    fzLog const satMedium  = satIsMedium(hsv.s);
    fzLog const satHigh    = satIsHigh(hsv.s);

    fzLog const valVeryLow  = valIsVeryLow(hsv.v);
    fzLog const valLow      = valIsLow(hsv.v);
    fzLog const valMedium   = valIsMedium(hsv.v);
    fzLog const valHigh     = valIsHigh(hsv.v);
    fzLog const valVeryHigh = valIsVeryHigh(hsv.v);

    fzLog const hueAround000 = hueIsAround000(hsv.h);
    fzLog const hueAround015 = hueIsAround015(hsv.h);
    fzLog const hueAround030 = hueIsAround030(hsv.h);
    fzLog const hueAround060 = hueIsAround060(hsv.h);
    fzLog const hueAround120 = hueIsAround120(hsv.h);
    fzLog const hueAround180 = hueIsAround180(hsv.h);
    fzLog const hueAround270 = hueIsAround270(hsv.h);
    fzLog const hueAround320 = hueIsAround320(hsv.h);
    fzLog const hueAround360 = hueIsAround360(hsv.h);

    (*bkMatchP)[BKCOLOR_BLACK]  =
        fzAnd(fzOr(satVeryLow, satLow), fzOr(valVeryLow, valLow));

    (*bkMatchP)[BKCOLOR_GRAY]   =
        fzAnd(satVeryLow, fzAnd(fzNot(valVeryLow), fzNot(valVeryHigh)));

    (*bkMatchP)[BKCOLOR_WHITE]  =
        fzAnd(satVeryLow, valVeryHigh);

    (*bkMatchP)[BKCOLOR_RED]    =
        fzAnd(fzAnd(fzOr(hueAround000, hueAround360), fzNot(satVeryLow)),
              fzOr(valMedium, valHigh)
             );

    (*bkMatchP)[BKCOLOR_ORANGE] =
        fzAnd(fzAnd(hueAround030, fzOr(satMedium, satHigh)),
              fzOr(fzOr(valMedium, valHigh), valVeryHigh)
             );

    (*bkMatchP)[BKCOLOR_YELLOW] =
        fzAnd(fzAnd(hueAround060, fzOr(satMedium, satHigh)),
              fzOr(valHigh, valVeryHigh)
             );

    (*bkMatchP)[BKCOLOR_GREEN]  =
        fzAnd(fzAnd(hueAround120, fzOr(satMedium, satHigh)),
              fzAnd(fzNot(valVeryLow), fzNot(valLow))
             );

    (*bkMatchP)[BKCOLOR_BLUE]   =
        fzAnd(fzAnd(hueAround180, fzNot(satVeryLow)),
              fzNot(valVeryLow)
             );

    (*bkMatchP)[BKCOLOR_VIOLET] =
        fzAnd(fzAnd(hueAround270, fzOr(satMedium, satHigh)),
              fzOr(valMedium, valHigh)
             );

    (*bkMatchP)[BKCOLOR_PURPLE] =
        fzAnd(fzAnd(hueAround320, fzOr(satMedium, satHigh)),
              fzOr(valMedium, valHigh)
             );

    (*bkMatchP)[BKCOLOR_BROWN]  =
        fzOr(
             fzAnd(fzOr(hueAround015, hueAround360),
                   fzAnd(fzNot(satVeryLow), fzOr(valLow, valMedium))),
             fzAnd(hueAround015, satLow)
            );
}



bk_color
ppm_bk_color_from_color(pixel  const color,
                        pixval const maxval) {

    fzLog bkmatch[BKCOLOR_COUNT];
    bk_color i;
    bk_color bestSoFar;
    fzLog bestMatch;

    matchBk(color, maxval, &bkmatch);

    for (i = 0, bestSoFar = 0, bestMatch = 0.0; i < BKCOLOR_COUNT; ++i) {
        if (bkmatch[i] > bestMatch) {
            bestSoFar = i;
            bestMatch = bkmatch[i];
        }
    }
    return bestSoFar;
}



static pixel const bkColorMap[BKCOLOR_COUNT] = {
    {174, 174, 174}, /* BKCOLOR_GRAY   */
    {128,  42,  42}, /* BKCOLOR_BROWN  */
    {255, 128,   0}, /* BKCOLOR_ORANGE */
    {255,   0,   0}, /* BKCOLOR_RED    */
    {255, 255,   0}, /* BKCOLOR_YELLOW */
    {  0, 255,   0}, /* BKCOLOR_GREEN  */
    {  0,   0, 255}, /* BKCOLOR_BLUE   */
    {143,  94, 153}, /* BKCOLOR_VIOLET */
    {160,  32, 240}, /* BKCOLOR_PURPLE */
    {255, 255, 255}, /* BKCOLOR_WHITE  */
    {  0,   0,   0}  /* BKCOLOR_BLACK  */
};



pixel
ppm_color_from_bk_color(bk_color const bkColor,
                        pixval   const maxval) {

    pixel const color255 = bkColorMap[bkColor];

    pixel retval;

    if (maxval != 255) {
        PPM_DEPTH(retval, color255, 255, maxval);
    } else
        retval = color255;

    return retval;
}



static const char * const bkColorNameMap[BKCOLOR_COUNT] = {
    "gray",
    "brown",
    "orange",
    "red",
    "yellow",
    "green",
    "blue",
    "violet",
    "purple",
    "white",
    "black"
};



bk_color
ppm_bk_color_from_name(const char * const name) {

    bk_color i;

    for (i = 0; i < BKCOLOR_COUNT; ++i) {
        if (streq(name, bkColorNameMap[i]))
            return i;
    }
    pm_error("Invalid Berlin-Kay color name: '%s'", name);
    return 0;  /* quiet compiler warning */
}



const char *
ppm_name_from_bk_color(bk_color const bkColor) {

    if (bkColor >= BKCOLOR_COUNT)
        pm_error("Invalid color passed to name_from_bk_color(): %u",
                 bkColor);

    return bkColorNameMap[bkColor];
}
