#include "ppm.h"
#include "ppmcmap.h"


/*
 * Yep, it's a very simple algorithm, but it was something I wanted to have
 * available.
 */

struct colorToGrayEntry {
	pixel           color;
	gray            gray;
	int             frequency;
};

/*
 * BUG: This number was chosen pretty arbitrarily.  The program is * probably
 * only useful for a very small numbers of colors - and that's * not only
 * because of the O(n) search that's used.  The idea lends * itself primarily
 * to low color (read: simple, machine generated) images.
 */
#define MAXCOLORS 255


int main ARGS((int argc, char *argv[]));
gray newGrayValue ARGS((pixel * pix, struct colorToGrayEntry * colorToGrayMap, int colors));
int cmpColorToGrayEntryByIntensity ARGS((void *entry1, void *entry2));
int cmpColorToGrayEntryByFrequency ARGS((void *entry1, void *entry2));


int
main(argc, argv)
    int argc;
    char *argv[];
{
    FILE           *ifp;
    int             col, cols, row, rows, color, colors, argn;
    int             frequency;
    pixval          maxval;
    pixel         **pixels;
    pixel          *pP;
    colorhist_vector hist;
    gray           *grayrow;
    gray           *gP;
    struct colorToGrayEntry *colorToGrayMap;


    ppm_init(&argc, argv);

    argn = 1;
    /* Default is to sort colors by intensity */
    frequency = 0;

    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
	if (pm_keymatch(argv[argn], "-frequency", 2))
	    frequency = 1;
	else if (pm_keymatch(argv[argn], "-intensity", 2))
	    frequency = 0;
	else
	    pm_usage( "[-frequency|-intensity] [ppmfile]" );
	++argn;
    }

    if (argn < argc) {
	ifp = pm_openr(argv[argn]);
	++argn;
    } else
	ifp = stdin;

    pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
    pm_close(ifp);
    /* all done with the input file - it's entirely in memory */

    /*
     * Compute a histogram of the colors in the input.  This is good for
     * both frequency, and indirectly the intensity, of a color.
     */
    hist = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);

    if (hist == (colorhist_vector) 0)
	/*
	 * BUG: This perhaps should use an exponential backoff, in
	 * the number of colors, until success - cf ppmquant's
	 * approach.  The results are then more what's expected, but
	 * not necessarily very useful.
	 */
	pm_error("Too many colors - Try reducing with ppmquant");

    /* copy the colors into another structure for sorting */
    colorToGrayMap = (struct colorToGrayEntry *)
	malloc(sizeof(struct colorToGrayEntry) * colors);
    for (color = 0; color < colors; color++) {
	colorToGrayMap[color].color = hist[color].color;
	colorToGrayMap[color].frequency = hist[color].value;
	/*
	 * This next is derivable, of course, but it's far faster to
	 * store it precomputed.  This can be skipped, when sorting
	 * by frequency - but again, for a small number of colors
	 * it's a small matter.
	 */
	colorToGrayMap[color].gray = PPM_LUMIN(hist[color].color);
    }

    /*
     * sort by intensity - sorting by frequency (in the histogram) is
     * worth considering as a future addition.
     */
    if (frequency)
	qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry),
	      cmpColorToGrayEntryByFrequency);
    else
	qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry),
	      cmpColorToGrayEntryByIntensity);

    /*
     * create mapping between the n colors in input, to n evenly spaced
     * grey-scale intensities.  This is done by overwriting the neatly
     * formed gray values corresponding to the input-colors, with a new
     * set of evenly spaced gray values.  Since maxval can be changed on
     * a lark, we just use gray levels 0..colors-1, and adjust maxval
     * accordingly
     */
    maxval = colors - 1;
    for (color = 0; color < colors; color++)
	colorToGrayMap[color].gray = color;

    /* write pgm file, mapping colors to intensities */
    pgm_writepgminit(stdout, cols, rows, maxval, 0);

    grayrow = pgm_allocrow(cols);

    for (row = 0; row < rows; row++) {
	for (col = 0, pP = pixels[row], gP = grayrow; col < cols;
	     col++, pP++, gP++)
	    *gP = newGrayValue(pP, colorToGrayMap, colors);
	pgm_writepgmrow(stdout, grayrow, cols, maxval, 0);
    }

    pm_close(stdout);

    exit(0);
}

gray
newGrayValue(pix, colorToGrayMap, colors)
    pixel *pix;
    struct colorToGrayEntry *colorToGrayMap;
    int colors;
{
    int color;
    /*
     * Allowing this to be O(n), since the program is intended for small
     * n.  Later, perhaps sort by color (r, then g, then b) and bsearch.
     */
    for (color = 0; color < colors; color++) {
	if (PPM_EQUAL(*pix, colorToGrayMap[color].color))
	    return colorToGrayMap[color].gray;
    }
    pm_error("This should never happen - contact the maintainer");
}

int
cmpColorToGrayEntryByIntensity(entry1, entry2)
    void *entry1, *entry2;
{
    return ((struct colorToGrayEntry *) entry1)->gray -
	((struct colorToGrayEntry *) entry2)->gray;
}

int
cmpColorToGrayEntryByFrequency(entry1, entry2)
    void *entry1, *entry2;
{
    return ((struct colorToGrayEntry *) entry1)->frequency -
	((struct colorToGrayEntry *) entry2)->frequency;
}
