/******************************************************************************
                               pamsummcol
*******************************************************************************
  Summarize the columns of a PAM image with various functions.

  By Bryan Henderson, San Jose CA 2004.02.07.

  Contributed to the public domain


******************************************************************************/

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

enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *inputFilespec;  /* Filespec of input file */
    enum function function;
    unsigned int verbose;
};


static void
parseCommandLine(int argc, char ** const argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry *option_def = malloc(100*sizeof(optEntry));
        /* Instructions to OptParseOptions2 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;

    unsigned int sumSpec, meanSpec, minSpec, maxSpec;

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0,   "sum",      OPT_FLAG,  NULL, &sumSpec,           0);
    OPTENT3(0,   "mean",     OPT_FLAG,  NULL, &meanSpec,          0);
    OPTENT3(0,   "min",      OPT_FLAG,  NULL, &minSpec,           0);
    OPTENT3(0,   "max",      OPT_FLAG,  NULL, &maxSpec,           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 have no parms that are negative numbers */

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

    if (sumSpec + minSpec + maxSpec > 1)
        pm_error("You may specify at most one of -sum, -min, and -max");

    if (sumSpec) {
        cmdlineP->function = FN_ADD;
    } else if (meanSpec) {
        cmdlineP->function = FN_MEAN;
    } else if (minSpec) {
        cmdlineP->function = FN_MIN;
    } else if (maxSpec) {
        cmdlineP->function = FN_MAX;
    } else 
        pm_error("You must specify one of -sum, -min, or -max");
        
    if (argc-1 > 1)
        pm_error("Too many arguments (%d).  File spec is the only argument.",
                 argc-1);

    if (argc-1 < 1)
        cmdlineP->inputFilespec = "-";
    else 
        cmdlineP->inputFilespec = argv[1];
    
}


struct accum {
    union {
        unsigned int sum;
        unsigned int min;
        unsigned int max;
    } u;
};



static void
createAccumulator(enum function    const function,
                  unsigned int     const cols,
                  unsigned int     const planes,
                  struct accum *** const accumulatorP) {
    
    struct accum ** accumulator;
    unsigned int col;

    MALLOCARRAY_NOFAIL(accumulator, cols);

    for (col = 0; col < cols; ++col) {
        unsigned int plane;

        MALLOCARRAY_NOFAIL(accumulator[col], planes);

        for (plane = 0; plane < planes; ++plane) {
            switch(function) {
            case FN_ADD:  accumulator[col][plane].u.sum = 0;        break;
            case FN_MEAN: accumulator[col][plane].u.sum = 0;        break;
            case FN_MIN:  accumulator[col][plane].u.min = UINT_MAX; break;
            case FN_MAX:  accumulator[col][plane].u.max = 0;        break;
            } 
        }
    }
    *accumulatorP = accumulator;
}



static void
destroyAccumulator(struct accum **    accumulator,
                   unsigned int const cols) {

    unsigned int col;
    for (col = 0; col < cols; ++col)
        free(accumulator[col]);

    free(accumulator);
}



static void
aggregate(struct pam *    const inpamP,
          tuple *         const tupleRow,
          enum function   const function,
          struct accum ** const accumulator) {

    unsigned int col;

    for (col = 0; col < inpamP->width; ++col) {
        unsigned int plane;
        for (plane = 0; plane < inpamP->depth; ++plane) {
            switch(function) {
            case FN_ADD:  
            case FN_MEAN: 
                if (accumulator[col][plane].u.sum > 
                    UINT_MAX - tupleRow[col][plane])
                    pm_error("Numerical overflow in Column %u", col);
                accumulator[col][plane].u.sum += tupleRow[col][plane];
            break;
            case FN_MIN:  
                if (tupleRow[col][plane] < accumulator[col][plane].u.min)
                    accumulator[col][plane].u.min = tupleRow[col][plane];
                break;
            case FN_MAX:
                if (tupleRow[col][plane] > accumulator[col][plane].u.min)
                    accumulator[col][plane].u.min = tupleRow[col][plane];
                break;
            } 
        }
    }
}



static void
makeSummaryRow(struct accum ** const accumulator,
               unsigned int  const   count,
               struct pam *  const   pamP,
               enum function const   function,
               tuple *       const   tupleRow) {
    
    unsigned int col;

    for (col = 0; col < pamP->width; ++col) {
        unsigned int plane;
        for (plane = 0; plane < pamP->depth; ++plane) {
            switch(function) {
            case FN_ADD:  
                tupleRow[col][plane] = 
                    MIN(accumulator[col][plane].u.sum, pamP->maxval);
                break;
            case FN_MEAN: 
                tupleRow[col][plane] = 
                    ROUNDU((double)accumulator[col][plane].u.sum / count);
                break;
            case FN_MIN:  
                tupleRow[col][plane] = 
                    accumulator[col][plane].u.min;
                break;
            case FN_MAX:
                tupleRow[col][plane] = 
                    accumulator[col][plane].u.max;
                break;
            }
        } 
    }
}



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

    FILE* ifP;
    tuple* inputRow;   /* Row from input image */
    tuple* outputRow;  /* Output row */
    int row;
    struct cmdlineInfo cmdline;
    struct pam inpam;   /* Input PAM image */
    struct pam outpam;  /* Output PAM image */
    struct accum ** accumulator;  /* malloc'ed two-dimensional array */

    pnm_init( &argc, argv );

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFilespec);

    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));

    createAccumulator(cmdline.function, inpam.width, inpam.depth, 
                      &accumulator);

    inputRow = pnm_allocpamrow(&inpam);

    outpam = inpam;    /* Initial value -- most fields should be same */
    outpam.file = stdout;
    outpam.height = 1;

    pnm_writepaminit(&outpam);

    outputRow = pnm_allocpamrow(&outpam);

    for (row = 0; row < inpam.height; row++) {
        pnm_readpamrow(&inpam, inputRow);

        aggregate(&inpam, inputRow, cmdline.function, accumulator);
    }
    makeSummaryRow(accumulator, inpam.height, &outpam, cmdline.function, 
                   outputRow);
    pnm_writepamrow(&outpam, outputRow);

    pnm_freepamrow(outputRow);
    pnm_freepamrow(inputRow);
    destroyAccumulator(accumulator, inpam.width);
    pm_close(inpam.file);
    pm_close(outpam.file);
    
    return 0;
}
