/*
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#define _BSD_SOURCE 1
    /* Make sure strdup() is in string.h */

#include <string.h>
#include "ppm.h"
#include "colorname.h"
#include "mallocvar.h"



static void
computeHexTable(int hexit[]) {
    int i;
    for ( i = 0; i < 256; ++i )
        hexit[i] = 1234567890;
    hexit['0'] = 0;
    hexit['1'] = 1;
    hexit['2'] = 2;
    hexit['3'] = 3;
    hexit['4'] = 4;
    hexit['5'] = 5;
    hexit['6'] = 6;
    hexit['7'] = 7;
    hexit['8'] = 8;
    hexit['9'] = 9;
    hexit['a'] = hexit['A'] = 10;
    hexit['b'] = hexit['B'] = 11;
    hexit['c'] = hexit['C'] = 12;
    hexit['d'] = hexit['D'] = 13;
    hexit['e'] = hexit['E'] = 14;
    hexit['f'] = hexit['F'] = 15;
}



static void
parseNewHexX11(const char colorname[], long const lmaxval,
               long * const rP, long * const gP, long * const bP) {

    int hexit[256];

    const char* cp;
    long r,g,b;
    int i;

    computeHexTable(hexit);

    cp = colorname + 4;
    r = g = b = 0;
    for ( i = 0; *cp != '/'; ++i, ++cp )
        r = r * 16 + hexit[(int)*cp];
    r = pm_rgbnorm( r, lmaxval, i, colorname );
    for ( i = 0, ++cp; *cp != '/'; ++i, ++cp )
        g = g * 16 + hexit[(int)*cp];
    g = pm_rgbnorm( g, lmaxval, i, colorname );
    for ( i = 0, ++cp; *cp != '\0'; ++i, ++cp )
        b = b * 16 + hexit[(int)*cp];
    b = pm_rgbnorm( b, lmaxval, i, colorname );
    if ( r < 0 || r > lmaxval || g < 0 || g > lmaxval 
         || b < 0 || b > lmaxval )
        pm_error("invalid color specifier - \"%s\"", colorname );

    *rP = r; *gP = g; *bP = b;
}



static void
parseNewDecX11(const char colorname[], long const lmaxval,
            long * const rP, long * const gP, long * const bP) {

    float fr, fg, fb;

    if ( sscanf( colorname, "rgbi:%f/%f/%f", &fr, &fg, &fb ) != 3 )
        pm_error("invalid color specifier - \"%s\"", colorname );
    if ( fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
         || fb < 0.0 || fb > 1.0 )
        pm_error( "invalid color specifier - \"%s\" - "
                  "values must be between 0.0 and 1.0", colorname );
    *rP = fr * lmaxval;
    *gP = fg * lmaxval;
    *bP = fb * lmaxval;
}




static void
parseOldX11(const char colorname[], long const lmaxval,
            long * const rP, long * const gP, long * const bP) {

    int hexit[256];
    long r,g,b;

    computeHexTable(hexit);

    switch ( strlen( colorname ) - 1 /* (Number of hex digits) */ )
    {
    case 3:
        r = hexit[(int)colorname[1]];
        g = hexit[(int)colorname[2]];
        b = hexit[(int)colorname[3]];
        r = pm_rgbnorm( r, lmaxval, 1, colorname );
        g = pm_rgbnorm( g, lmaxval, 1, colorname );
        b = pm_rgbnorm( b, lmaxval, 1, colorname );
        break;

    case 6:
        r = ( hexit[(int)colorname[1]] << 4 ) + hexit[(int)colorname[2]];
        g = ( hexit[(int)colorname[3]] << 4 ) + hexit[(int)colorname[4]];
        b = ( hexit[(int)colorname[5]] << 4 ) + hexit[(int)colorname[6]];
        r = pm_rgbnorm( r, lmaxval, 2, colorname );
        g = pm_rgbnorm( g, lmaxval, 2, colorname );
        b = pm_rgbnorm( b, lmaxval, 2, colorname );
        break;

    case 9:
        r = ( hexit[(int)colorname[1]] << 8 ) +
            ( hexit[(int)colorname[2]] << 4 ) +
            hexit[(int)colorname[3]];
        g = ( hexit[(int)colorname[4]] << 8 ) + 
            ( hexit[(int)colorname[5]] << 4 ) +
            hexit[(int)colorname[6]];
        b = ( hexit[(int)colorname[7]] << 8 ) + 
            ( hexit[(int)colorname[8]] << 4 ) +
            hexit[(int)colorname[9]];
        r = pm_rgbnorm( r, lmaxval, 3, colorname );
        g = pm_rgbnorm( g, lmaxval, 3, colorname );
        b = pm_rgbnorm( b, lmaxval, 3, colorname );
        break;

    case 12:
        r = ( hexit[(int)colorname[1]] << 12 ) + 
            ( hexit[(int)colorname[2]] << 8 ) +
            ( hexit[(int)colorname[3]] << 4 ) + hexit[(int)colorname[4]];
        g = ( hexit[(int)colorname[5]] << 12 ) + 
            ( hexit[(int)colorname[6]] << 8 ) +
            ( hexit[(int)colorname[7]] << 4 ) + hexit[(int)colorname[8]];
        b = ( hexit[(int)colorname[9]] << 12 ) + 
            ( hexit[(int)colorname[10]] << 8 ) +
            ( hexit[(int)colorname[11]] << 4 ) + hexit[(int)colorname[12]];
        r = pm_rgbnorm( r, lmaxval, 4, colorname );
        g = pm_rgbnorm( g, lmaxval, 4, colorname );
        b = pm_rgbnorm( b, lmaxval, 4, colorname );
        break;

    default:
        pm_error("invalid color specifier - \"%s\"", colorname );
    }
    if ( r < 0 || r > lmaxval || g < 0 || g > lmaxval 
         || b < 0 || b > lmaxval )
        pm_error("invalid color specifier - \"%s\"", colorname );
    
    *rP = r; *gP = g; *bP = b;
}




static void
parseOldX11Dec(const char colorname[], long const lmaxval,
               long * const rP, long * const gP, long * const bP) {

    float fr, fg, fb;

    if ( sscanf( colorname, "%f,%f,%f", &fr, &fg, &fb ) != 3 )
        pm_error("invalid color specifier - \"%s\"", colorname );
    if ( fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
         || fb < 0.0 || fb > 1.0 )
        pm_error( "invalid color specifier - \"%s\" - "
                  "values must be between 0.0 and 1.0", colorname );
    *rP = fr * lmaxval;
    *gP = fg * lmaxval;
    *bP = fb * lmaxval;
}




pixel
ppm_parsecolor(const char * const colorname, pixval const maxval) {

    pixel p;
    long lmaxval, r, g, b;
    
    lmaxval = maxval;
    if ( strncmp( colorname, "rgb:", 4 ) == 0 )
        /* It's a new-X11-style hexadecimal rgb specifier. */
        parseNewHexX11(colorname, lmaxval, &r, &g, &b);
    else if ( strncmp( colorname, "rgbi:", 5 ) == 0 )
        /* It's a new-X11-style decimal/float rgb specifier. */
        parseNewDecX11(colorname, lmaxval, &r, &g, &b);
    else if ( colorname[0] == '#' )
        /* It's an old-X11-style hexadecimal rgb specifier. */
        parseOldX11(colorname, lmaxval, &r, &g, &b);
    else if ( ( colorname[0] >= '0' && colorname[0] <= '9' ) ||
              colorname[0] == '.' )
        /* It's an old-style decimal/float rgb specifier. */
        parseOldX11Dec(colorname, lmaxval, &r, &g, &b);
    else 
        /* Must be a name from the X-style rgb file. */
        pm_parse_dictionary_name(colorname, lmaxval, &r, &g, &b);

    PPM_ASSIGN( p, r, g, b );
    return p;
}



char*
ppm_colorname(const pixel* const colorP, 
              pixval       const maxval, 
              int          const hexok)   {

    int r, g, b;
    FILE* f;
    static char colorname[200];

    if ( maxval == 255 ) {
        r = PPM_GETR( *colorP );
        g = PPM_GETG( *colorP );
        b = PPM_GETB( *colorP );
    } else {
        r = (int) PPM_GETR( *colorP ) * 255 / (int) maxval;
        g = (int) PPM_GETG( *colorP ) * 255 / (int) maxval;
        b = (int) PPM_GETB( *colorP ) * 255 / (int) maxval;
    }

    f = pm_openColornameFile(NULL, !hexok);
    if (f != NULL) {
        int best_diff, this_diff;
        bool done;

        best_diff = 32767;
        done = FALSE;
        while ( !done ) {
            struct colorfile_entry const ce = pm_colorget( f );
            if ( ce.colorname )  {
                this_diff = 
                    abs( r - ce.r ) + abs( g - ce.g ) + abs( b - ce.b );
                if ( this_diff < best_diff ) {
                    best_diff = this_diff;
                    (void) strcpy( colorname, ce.colorname );
                }
            } else
                done = TRUE;
        }
        fclose( f );
        if ( best_diff != 32767 && ( best_diff == 0 || ! hexok ) )
            return colorname;
    }

    /* Color lookup failed, but caller is willing to take an X11-style
       hex specifier, so return that.
    */
    sprintf( colorname, "#%02x%02x%02x", r, g, b );
    return colorname;
}



#define MAXCOLORNAMES 1000u

static void
processColorfileEntry(struct colorfile_entry const ce,
                      colorhash_table        const cht,
                      const char **          const colornames,
                      unsigned int *         const colornameIndexP) {

    if (*colornameIndexP >= MAXCOLORNAMES)
        pm_error("Too many colors in colorname dictionary.  "
                 "Max allowed is %u", MAXCOLORNAMES);
    else {
        pixel color;

        PPM_ASSIGN(color, ce.r, ce.g, ce.b);

        if (ppm_lookupcolor(cht, &color) >= 0) {
            /* The color is already in the hash, which means we saw it
               earlier in the file.  We prefer the first name that the
               file gives for each color, so we just ignore the
               current entry.  
            */
        } else {
            ppm_addtocolorhash(cht, &color, *colornameIndexP);
            colornames[*colornameIndexP] = strdup(ce.colorname);
            if (colornames[*colornameIndexP] == NULL)
                pm_error("Unable to allocate space for color name");
            ++(*colornameIndexP);
        }
    }
}



void
ppm_readcolornamefile(const char *      const fileName, 
                      int               const mustOpen,
                      colorhash_table * const chtP, 
                      const char ***    const colornamesP) {

    colorhash_table cht;
    const char ** colornames;
    FILE * colorFile;

    cht = ppm_alloccolorhash();

    MALLOCARRAY(colornames, MAXCOLORNAMES);

    if (colornames == NULL)
        pm_error("Unable to allocate space for colorname table.");

    colorFile = pm_openColornameFile(fileName, mustOpen);

    if (colorFile != NULL) {
        unsigned int colornameIndex;
        bool done;

        colornameIndex = 0;  /* initial value */
        done = FALSE;
        while (!done) {
            struct colorfile_entry const ce = pm_colorget(colorFile);

            if (!ce.colorname)  
                done = TRUE;
            else 
                processColorfileEntry(ce, cht, colornames, &colornameIndex);
        }
        while (colornameIndex < MAXCOLORNAMES)
            colornames[colornameIndex++] = NULL;

        fclose(colorFile);
    }
    *chtP = cht;
    *colornamesP = colornames;
}



void
ppm_freecolornames(const char ** const colornames) {

    unsigned int i;

    for (i = 0; i < MAXCOLORNAMES; ++i)
        if (colornames[i])
            free((char *)colornames[i]);

    free(colornames);
}



static unsigned int 
nonnegative(unsigned int const arg) {

    if ((int)(arg) < 0)
        return 0;
    else
        return arg;
}



pixel
color_from_ycbcr(unsigned int const y, 
                 int          const cb, 
                 int          const cr) {
/*----------------------------------------------------------------------------
   Return the color that has luminence 'y', blue chrominance 'cb', and
   red chrominance 'cr'.

   The 3 input values can be on any scale (as long as it's the same
   scale for all 3) and the maxval of the returned pixel value is the
   same as that for the input values.

   Rounding may cause an output value to be greater than the maxval.
-----------------------------------------------------------------------------*/
    pixel retval;

    PPM_ASSIGN(retval, 
               y + 1.4022 * cr,
               nonnegative(y - 0.7145 * cr - 0.3456 * cb),
               y + 1.7710 * cb
        );
    
    return retval;
}
