/* libppm4.c - ppm utility library part 4
**
** 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"



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;
}




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

    FILE* f;
    bool gotit;
    bool colorfileExhausted;
    struct colorfile_entry colorfile_entry;
    char * canoncolor;
    long r,g,b;

    f = pm_openColornameFile(TRUE);  /* exits if error */
    canoncolor = strdup( colorname );
    pm_canonstr( canoncolor );
    gotit = FALSE;
    colorfileExhausted = FALSE;
    while ( !gotit && !colorfileExhausted ) {
        colorfile_entry = pm_colorget( f );
        if (colorfile_entry.colorname) {
            pm_canonstr( colorfile_entry.colorname );
            if ( strcmp( canoncolor, colorfile_entry.colorname ) == 0 )
                gotit = TRUE;
        } else
            colorfileExhausted = TRUE;
    }
    fclose( f );
    
    if (!gotit)
        pm_error( "unknown color - \"%s\"", colorname );

        /* Rescale from [0..255] if necessary. */
    if ( lmaxval != 255 ) {
        r = colorfile_entry.r * lmaxval / 255;
        g = colorfile_entry.g * lmaxval / 255;
        b = colorfile_entry.b * lmaxval / 255;
    } else {
        r = colorfile_entry.r;
        g = colorfile_entry.g;
        b = colorfile_entry.b;
    }
    free( canoncolor );

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



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. */
        parseDictionaryName(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(!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;
}
