/*=========================================================================
                             ppmcolormask
===========================================================================

  This program produces a PBM mask of areas containing a certain color.

  By Bryan Henderson, Olympia WA; April 2000.

  Contributed to the public domain by its author.
=========================================================================*/

#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
#define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
#include <assert.h>
#include <string.h>

#include "pm_c_util.h"
#include "shhopt.h"
#include "mallocvar.h"
#include "nstring.h"
#include "ppm.h"
#include "pam.h"

typedef enum {
    MATCH_EXACT,
    MATCH_BK
} MatchType;

struct CmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char * inputFilename;
    unsigned int colorCt;
    struct {
        MatchType matchType;
        union {
            tuplen   color;   /* matchType == MATCH_EXACT */
            bk_color bkColor; /* matchType == MATCH_BK */
        } u;
    } maskColor[16];
    unsigned int verbose;
};



static void
freeCmdline(struct CmdlineInfo * const cmdlineP) {

    unsigned int i;

    for (i = 0; i < cmdlineP->colorCt; ++ i) {
        if (cmdlineP->maskColor[i].matchType == MATCH_EXACT)
            free(cmdlineP->maskColor[i].u.color);
    }
}



static void
parseColorOpt(const char *         const colorOpt,
              struct CmdlineInfo * const cmdlineP) {

    unsigned int colorCt;
    char * colorOptWork;
    char * cursor;
    bool eol;

    colorOptWork = strdup(colorOpt);
    cursor = &colorOptWork[0];

    eol = FALSE;    /* initial value */
    colorCt = 0;    /* initial value */
    while (!eol && colorCt < ARRAY_SIZE(cmdlineP->maskColor)) {
        const char * token;
        token = pm_strsep(&cursor, ",");
        if (token) {
            if (strneq(token, "bk:", 3)) {
                cmdlineP->maskColor[colorCt].matchType = MATCH_BK;
                cmdlineP->maskColor[colorCt].u.bkColor =
                    ppm_bk_color_from_name(&token[3]);
            } else {
                cmdlineP->maskColor[colorCt].matchType = MATCH_EXACT;
                cmdlineP->maskColor[colorCt].u.color =
                    pnm_parsecolorn(token);
            }
            ++colorCt;
        } else
            eol = TRUE;
    }
    free(colorOptWork);

    cmdlineP->colorCt = colorCt;
}



static void
parseCommandLine(int argc, const char ** argv,
                 struct CmdlineInfo *cmdlineP) {
/*----------------------------------------------------------------------------
   Note that many of the strings that this function returns in the
   *cmdlineP structure are actually in the supplied argv array.  And
   sometimes, one of these strings is actually just a suffix of an entry
   in argv!
-----------------------------------------------------------------------------*/
    optEntry * option_def;
        /* Instructions to OptParseOptions3 on how to parse our options. */
    optStruct3 opt;

    unsigned int option_def_index;
    const char * colorOpt;
    unsigned int colorSpec;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0, "color",      OPT_STRING, &colorOpt, &colorSpec,           0);
    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,        0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */

    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and all of *cmdlineP. */

    if (colorSpec)
        parseColorOpt(colorOpt, cmdlineP);

    if (colorSpec) {
        if (argc-1 < 1)
            cmdlineP->inputFilename = "-";  /* he wants stdin */
        else if (argc-1 == 1)
            cmdlineP->inputFilename = argv[1];
        else
            pm_error("Too many arguments.  When you specify -color, "
                     "the only argument accepted is the optional input "
                     "file name.");
    } else {
        if (argc-1 < 1)
            pm_error("You must specify the -color option.");
        else {
            cmdlineP->colorCt = 1;
            cmdlineP->maskColor[0].matchType = MATCH_EXACT;
            cmdlineP->maskColor[0].u.color = pnm_parsecolorn(argv[1]);

            if (argc - 1 < 2)
                cmdlineP->inputFilename = "-";  /* he wants stdin */
            else if (argc-1 == 2)
                cmdlineP->inputFilename = argv[2];
            else
                pm_error("Too many arguments.  The only arguments accepted "
                         "are the mask color and optional input file name");
        }
    }
}



static void
setupOutput(FILE *       const fileP,
            unsigned int const width,
            unsigned int const height,
            struct pam * const outPamP) {

    outPamP->size             = sizeof(*outPamP);
    outPamP->len              = PAM_STRUCT_SIZE(tuple_type);
    outPamP->file             = fileP;
    outPamP->format           = RPBM_FORMAT;
    outPamP->plainformat      = 0;
    outPamP->height           = height;
    outPamP->width            = width;
    outPamP->depth            = 1;
    outPamP->maxval           = 1;
    outPamP->bytes_per_sample = 1;
    strcpy(outPamP->tuple_type, PAM_PBM_TUPLETYPE);
}



static bool
isBkColor(tuple        const comparator,
          struct pam * const pamP,
          bk_color     const comparand) {

    pixel comparatorPixel;
    bk_color comparatorBk;

    /* TODO: keep a cache of the bk color for each color in
       a colorhash_table.
    */

    assert(pamP->depth >= 3);

    PPM_ASSIGN(comparatorPixel,
               comparator[PAM_RED_PLANE],
               comparator[PAM_GRN_PLANE],
               comparator[PAM_BLU_PLANE]);

    comparatorBk = ppm_bk_color_from_color(comparatorPixel, pamP->maxval);

    return comparatorBk == comparand;
}



static bool
colorIsInSet(tuple              const color,
             struct pam *       const pamP,
             struct CmdlineInfo const cmdline) {

    bool isInSet;
    unsigned int i;
    tuple maskColorUnnorm;

    maskColorUnnorm = pnm_allocpamtuple(pamP);

    for (i = 0, isInSet = FALSE; i < cmdline.colorCt && !isInSet; ++i) {

        assert(i < ARRAY_SIZE(cmdline.maskColor));

        switch(cmdline.maskColor[i].matchType) {
        case MATCH_EXACT:
            pnm_unnormalizetuple(pamP,
                                 cmdline.maskColor[i].u.color,
                                 maskColorUnnorm);
            if (pnm_tupleequal(pamP, color, maskColorUnnorm))
                isInSet = TRUE;
            break;
        case MATCH_BK:
            if (isBkColor(color, pamP, cmdline.maskColor[i].u.bkColor))
                isInSet = TRUE;
            break;
        }
    }

    free(maskColorUnnorm);

    return isInSet;
}



int
main(int argc, const char *argv[]) {

    struct CmdlineInfo cmdline;

    FILE * ifP;
    struct pam inPam;
    struct pam outPam;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFilename);

    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(allocation_depth));

    pnm_setminallocationdepth(&inPam, 3);

    setupOutput(stdout, inPam.width, inPam.height, &outPam);

    pnm_writepaminit(&outPam);
    {
        tuple * const inputRow = pnm_allocpamrow(&inPam);
        tuple * const maskRow  = pnm_allocpamrow(&outPam);

        unsigned int numPixelsMasked;

        unsigned int row;

        for (row = 0, numPixelsMasked = 0; row < inPam.height; ++row) {
            unsigned int col;
            pnm_readpamrow(&inPam, inputRow);
            pnm_makerowrgb(&inPam, inputRow);
            for (col = 0; col < inPam.width; ++col) {
                if (colorIsInSet(inputRow[col], &inPam, cmdline)) {
                    maskRow[col][0] = PAM_BLACK;
                    ++numPixelsMasked;
                } else
                    maskRow[col][0] = PAM_BW_WHITE;
            }
            pnm_writepamrow(&outPam, maskRow);
        }

        if (cmdline.verbose)
            pm_message("%u pixels found matching %u requested colors",
                       numPixelsMasked, cmdline.colorCt);

        pnm_freepamrow(maskRow);
        pnm_freepamrow(inputRow);
    }
    freeCmdline(&cmdline);
    pm_close(ifP);

    return 0;
}



