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

  Combine the channels (stack the planes) of multiple PAM images to create
  a single PAM image.


  By Bryan Henderson, San Jose CA 2000.08.05

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

#include <string.h>

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

#define MAX_INPUTS 16
    /* The most input PAMs 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 *tupletype;       /* Tuple type for output PAM */
    unsigned int nInput;
        /* The number of input PAMs.  At least 1, at most 16. */
    const char * inputFileName[MAX_INPUTS];
        /* The PAM files to combine, in order. */
};



static void
parseCommandLine(int argc, char ** argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec strings we return are stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry * option_def;
        /* Instructions to pm_optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;
    extern struct pam pam;  /* Just so we can look at field sizes */

    unsigned int option_def_index;
    unsigned int tupletypeSpec;

    MALLOCARRAY_NOFAIL(option_def, 100);
    
    option_def_index = 0;   /* incremented by OPTENTRY */
    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 may have 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 (!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->nInput = 0;  /* initial value */
    { 
        unsigned int argn;
        bool stdinUsed;
        for (argn = 1, stdinUsed = false; argn < argc; ++argn) {
            if (cmdlineP->nInput >= MAX_INPUTS) 
                pm_error("You may not specify more than %u input images.",
                         MAX_INPUTS);
            cmdlineP->inputFileName[cmdlineP->nInput++] = argv[argn];
            if (streq(argv[argn], "-")) {
                if (stdinUsed)
                    pm_error("You cannot specify Standard Input ('-') "
                             "for more than one input file");
                stdinUsed = true;
            }
        }
    }
    if (cmdlineP->nInput < 1)
        cmdlineP->inputFileName[cmdlineP->nInput++] = "-";
}



static void
openAllStreams(unsigned int  const nInput,
               const char ** const inputFileName,
               FILE **       const ifP) {

    unsigned int inputSeq;

    for (inputSeq = 0; inputSeq < nInput; ++inputSeq)
        ifP[inputSeq] = pm_openr(inputFileName[inputSeq]);
}



static void
outputRaster(const struct pam       inpam[], 
             unsigned int     const nInput,
             struct pam             outpam) {

    tuple *inrow;
    tuple *outrow;
        
    outrow = pnm_allocpamrow(&outpam);
    inrow = pnm_allocpamrow(&outpam);      

    { 
        int row;
        
        for (row = 0; row < outpam.height; row++) {
            unsigned int inputSeq;
            int outplane;
            outplane = 0;  /* initial value */
            for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
                struct pam thisInpam = inpam[inputSeq];
                int col;

                pnm_readpamrow(&thisInpam, inrow);

                for (col = 0; col < outpam.width; col ++) {
                    int inplane;
                    for (inplane = 0; inplane < thisInpam.depth; ++inplane) 
                        outrow[col][outplane+inplane] = inrow[col][inplane];
                }
                outplane += thisInpam.depth;
            }
            pnm_writepamrow(&outpam, outrow);
        }
    }
    pnm_freepamrow(outrow);
    pnm_freepamrow(inrow);        
}



static void
processOneImageInAllStreams(unsigned int const nInput,
                            FILE *       const ifP[],
                            FILE *       const ofP,
                            const char * const tupletype) {

    struct pam inpam[MAX_INPUTS];   /* Input PAM images */
    struct pam outpam;  /* Output PAM image */

    unsigned int inputSeq;
        /* The horizontal sequence -- i.e. the sequence of the
           input stream, not the sequence of an image within a
           stream.
        */

    unsigned int outputDepth;
    outputDepth = 0;  /* initial value */
    
    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {

        pnm_readpaminit(ifP[inputSeq], &inpam[inputSeq], 
                        PAM_STRUCT_SIZE(tuple_type));

        if (inputSeq > 0) {
            /* All images, including this one, must be compatible with the 
               first image.
            */
            if (inpam[inputSeq].width != inpam[0].width)
                pm_error("Image no. %u does not have the same width as "
                         "Image 0.", inputSeq);
            if (inpam[inputSeq].height != inpam[0].height)
                pm_error("Image no. %u does not have the same height as "
                         "Image 0.", inputSeq);
            if (inpam[inputSeq].maxval != inpam[0].maxval)
                pm_error("Image no. %u does not have the same maxval as "
                         "Image 0.", inputSeq);
        }
        if (inpam[inputSeq].depth > UINT_MAX - outputDepth)
            pm_error("Total number of planes is too large to compute "
                     "(at least %u plus %u)",
                     outputDepth, inpam[inputSeq].depth);

        outputDepth += inpam[inputSeq].depth;
    }

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

    pm_message("Writing %u channel PAM image", outpam.depth);

    pnm_writepaminit(&outpam);

    outputRaster(inpam, nInput, outpam);
}



static void
nextImageAllStreams(unsigned int const nInput,
                    FILE *       const ifP[],
                    bool *       const eofP) {
/*----------------------------------------------------------------------------
   Advance all the streams ifP[] to the next image.

   Return *eofP == TRUE iff at least one stream has no next image.
-----------------------------------------------------------------------------*/
    unsigned int inputSeq;

    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
        int eof;
        pnm_nextimage(ifP[inputSeq], &eof);
        if (eof)
            *eofP = true;
    }
}



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

    struct cmdlineInfo cmdline;
    FILE * ifP[MAX_INPUTS];
    bool eof;

    pnm_init(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    openAllStreams(cmdline.nInput, cmdline.inputFileName, ifP);

    eof = FALSE;
    while (!eof) {
        processOneImageInAllStreams(cmdline.nInput, ifP, stdout,
                                    cmdline.tupletype);

        nextImageAllStreams(cmdline.nInput, ifP, &eof);
    }

    return 0;
}
