/*----------------------------------------------------------------------------
                               pamchannel
------------------------------------------------------------------------------
  Part of the Netpbm package.

  Extract specified channels (planes) from a PAM image


  By Bryan Henderson, San Jose CA 2000.08.05

  Contributed to the public domain by its author 2000.08.05.
-----------------------------------------------------------------------------*/

#include <string.h>

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

#define MAX_CHANNELS 16
    /* The most channels we allow user to specify */

struct CmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char * inputFileName;  /* Filename of input files */
    const char * tupletype;      /* Tuple type for output PAM */
    int n_channel;
        /* The number of channels to extract.  At least 1, at most 16. */
    unsigned int channel_to_extract[MAX_CHANNELS];
        /* The channel numbers to extract, in order. */
};



static void
parseCommandLine(int argc, const char ** 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;
    optStruct3 opt;
    extern struct pam pam;  /* Just so we can look at field sizes */

    unsigned int option_def_index;
    unsigned int infileSpec, tupletypeSpec;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0, "infile",     OPT_STRING, &cmdlineP->inputFileName,
            &infileSpec, 0);
    OPTENT3(0, "tupletype",  OPT_STRING, &cmdlineP->tupletype,
            &tupletypeSpec, 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_optParseOptions4(&argc, argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (!infileSpec)
        cmdlineP->inputFileName = "-";

    if (!tupletypeSpec)
        cmdlineP->tupletype = "";
    else
        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
            pm_error("Tuple type name specified is too long.  Maximum of "
                     "%u characters allowed.",
                     (unsigned)sizeof(pam.tuple_type));

    cmdlineP->n_channel = 0;  /* initial value */
    {
        int argn;
        for (argn = 1; argn < argc; argn++) {
            int n;
            char *endptr;

            if (cmdlineP->n_channel >= MAX_CHANNELS)
                pm_error("You may not specify more than %d channels.",
                         MAX_CHANNELS);

            n = strtol(argv[argn], &endptr, 10);
            if (n < 0)
                pm_error("Channel numbers cannot be negative.  "
                         "You specified %d", n);
            if (endptr == NULL)
                pm_error("non-numeric channel number argument: '%s'",
                         argv[argn]);

            cmdlineP->channel_to_extract[cmdlineP->n_channel++] = n;
        }
    }
    if (cmdlineP->n_channel < 1)
        pm_error("You must specify at least one channel to extract.");
}



static void
validateChannels(int          const n_channel,
                 unsigned int const channels[],
                 int          const depth) {

    unsigned int i;

    for (i = 0; i < n_channel; ++i)
        if (channels[i] > depth-1)
            pm_error("You specified channel number %u.  The highest numbered "
                     "channel in the input image is %d.",
                     channels[i], depth-1);
}



static void
doOneImage(FILE *       const ifP,
           FILE *       const ofP,
           unsigned int const nChannel,
           unsigned int const channelToExtract[],
           const char * const tupletype) {

    struct pam inpam;   /* Input PAM image */
    struct pam outpam;  /* Output PAM image */

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

    validateChannels(nChannel, channelToExtract, inpam.depth);

    outpam = inpam;     /* Initial value */
    outpam.file   = ofP;
    outpam.depth  = nChannel;
    outpam.format = PAM_FORMAT;
    strcpy(outpam.tuple_type, tupletype);

    pnm_writepaminit(&outpam);

    {
        tuple * inrow;
        tuple * outrow;

        inrow  = pnm_allocpamrow(&inpam);
        outrow = pnm_allocpamrow(&outpam);
        {
            unsigned int row;

            for (row = 0; row < inpam.height; ++row) {
                unsigned int col;

                pnm_readpamrow(&inpam, inrow);

                for (col = 0; col < inpam.width; ++col) {
                    unsigned int plane;
                    for (plane = 0; plane < nChannel; ++plane)
                        outrow[col][plane] =
                            inrow[col][channelToExtract[plane]];
                }
                pnm_writepamrow(&outpam, outrow);
            }
        }
        pnm_freepamrow(outrow);
        pnm_freepamrow(inrow);
    }
}



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

    struct CmdlineInfo cmdline;
    FILE * ifP;
    int eof;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFileName);

    eof = FALSE;
    while (!eof) {
        doOneImage(ifP, stdout, cmdline.n_channel, cmdline.channel_to_extract,
                   cmdline.tupletype);

        pnm_nextimage(ifP, &eof);
    }

    pm_close(ifP);

    return 0;
}

