/*****************************************************************************
                              pamtojpeg2k
******************************************************************************

  Convert a PNM image to JPEG-2000 code stream image

  By Bryan Henderson, San Jose CA  2002.10.26

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

#define _BSD_SOURCE 1    /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */
    /* In 2014.09, this was _XOPEN_SOURCE 600, with a comment saying it was
       necessary to make <inttypes.h> define int_fast32_t, etc. on AIX.
       <jasper/jasper.h> does use int_fast32_t and does include <inttypes.h>,
       but plenty of source files of libjasper do too, and they did not have
       _XOPEN_SOURCE 600, so it would seem to be superfluous here too.
    */

#include <string.h>

#include <jasper/jasper.h>

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

#include "libjasper_compat.h"


enum compmode {COMPMODE_INTEGER, COMPMODE_REAL};

enum progression {PROG_LRCP, PROG_RLCP, PROG_RPCL, PROG_PCRL, PROG_CPRL};

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    char * inputFilename;
    unsigned int imgareatlx;
    unsigned int imgareatly;
    unsigned int tilegrdtlx;
    unsigned int tilegrdtly;
    unsigned int tilewidth;
    unsigned int tileheight;
    unsigned int prcwidth;
    unsigned int prcheight;
    unsigned int cblkwidth;
    unsigned int cblkheight;
    enum compmode compmode;
    unsigned int compressionSpec;
    float        compression;
    char *       ilyrrates;
    enum progression progression;
    unsigned int numrlvls;
    unsigned int numgbits;
    unsigned int nomct;
    unsigned int sop;
    unsigned int eph;
    unsigned int lazy;
    unsigned int termall;
    unsigned int segsym;
    unsigned int vcausal;
    unsigned int pterm;
    unsigned int resetprob;
    unsigned int debuglevel;  /* Jasper library debug level */
    unsigned int verbose;
};


static void
parseCommandLine(int argc, char ** argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that many of the strings that this function returns in the
   *cmdline_p 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 imgareatlxSpec, imgareatlySpec;
    unsigned int tilegrdtlxSpec, tilegrdtlySpec;
    unsigned int tilewidthSpec, tileheightSpec;
    unsigned int prcwidthSpec, prcheightSpec;
    unsigned int cblkwidthSpec, cblkheightSpec;
    unsigned int modeSpec, ilyrratesSpec;
    unsigned int progressionSpec, numrlvlsSpec, numgbitsSpec;
    unsigned int debuglevelSpec;

    char * progressionOpt;
    char * modeOpt;

    unsigned int option_def_index;
    
    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0, "imgareatlx",   OPT_UINT,   &cmdlineP->imgareatlx,
            &imgareatlxSpec,       0);
    OPTENT3(0, "imgareatly",   OPT_UINT,   &cmdlineP->imgareatly,
            &imgareatlySpec,       0);
    OPTENT3(0, "tilegrdtlx",   OPT_UINT,   &cmdlineP->tilegrdtlx,
            &tilegrdtlxSpec,       0);
    OPTENT3(0, "tilegrdtly",   OPT_UINT,   &cmdlineP->tilegrdtly,
            &tilegrdtlySpec,       0);
    OPTENT3(0, "tilewidth",    OPT_UINT,   &cmdlineP->tilewidth,
            &tilewidthSpec,        0);
    OPTENT3(0, "tileheight",   OPT_UINT,   &cmdlineP->tileheight,
            &tileheightSpec,       0);
    OPTENT3(0, "prcwidth",     OPT_UINT,   &cmdlineP->prcwidth,
            &prcwidthSpec,       0);
    OPTENT3(0, "prcheight",    OPT_UINT,   &cmdlineP->prcheight,
            &prcheightSpec,      0);
    OPTENT3(0, "cblkwidth",    OPT_UINT,   &cmdlineP->cblkwidth,
            &cblkwidthSpec,      0);
    OPTENT3(0, "cblkheight",   OPT_UINT,   &cmdlineP->cblkheight,
            &cblkheightSpec,     0);
    OPTENT3(0, "mode",         OPT_STRING, &modeOpt,
            &modeSpec,           0);
    OPTENT3(0, "compression",  OPT_FLOAT,  &cmdlineP->compression,
            &cmdlineP->compressionSpec,    0);
    OPTENT3(0, "ilyrrates",    OPT_STRING, &cmdlineP->ilyrrates,
            &ilyrratesSpec,      0);
    OPTENT3(0, "progression",  OPT_STRING, &progressionOpt,
            &progressionSpec,    0);
    OPTENT3(0, "numrlvls",     OPT_UINT,   &cmdlineP->numrlvls,
            &numrlvlsSpec,       0);
    OPTENT3(0, "numgbits",     OPT_UINT,   &cmdlineP->numgbits,
            &numgbitsSpec,       0);
    OPTENT3(0, "nomct",        OPT_FLAG,   NULL, 
            &cmdlineP->nomct,    0);
    OPTENT3(0, "sop",          OPT_FLAG,   NULL, 
            &cmdlineP->sop,      0);
    OPTENT3(0, "eph",          OPT_FLAG,   NULL, 
            &cmdlineP->eph,      0);
    OPTENT3(0, "lazy",         OPT_FLAG,   NULL, 
            &cmdlineP->lazy,     0);
    OPTENT3(0, "termall",      OPT_FLAG,   NULL, 
            &cmdlineP->termall,  0);
    OPTENT3(0, "segsym",       OPT_FLAG,   NULL, 
            &cmdlineP->segsym,    0);
    OPTENT3(0, "vcausal",      OPT_FLAG,   NULL, 
            &cmdlineP->vcausal,   0);
    OPTENT3(0, "pterm",        OPT_FLAG,   NULL, 
            &cmdlineP->pterm,     0);
    OPTENT3(0, "resetprob",    OPT_FLAG,   NULL, 
            &cmdlineP->resetprob, 0);
    OPTENT3(0, "verbose",      OPT_FLAG,   NULL, 
            &cmdlineP->verbose,   0);
    OPTENT3(0, "debuglevel",   OPT_UINT,   &cmdlineP->debuglevel,
            &debuglevelSpec,      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);

    if (!imgareatlxSpec)
        cmdlineP->imgareatlx = 0;
    if (!imgareatlySpec)
        cmdlineP->imgareatly = 0;
    if (!tilegrdtlxSpec)
        cmdlineP->tilegrdtlx = 0;
    if (!tilegrdtlySpec)
        cmdlineP->tilegrdtly = 0;
    if (!tilewidthSpec)
        cmdlineP->tilewidth = 0;
    if (!tileheightSpec)
        cmdlineP->tileheight = 0;
    if (!prcwidthSpec)
        cmdlineP->prcwidth = 32768;
    if (!prcheightSpec)
        cmdlineP->prcheight = 32768;
    if (!cblkwidthSpec)
        cmdlineP->cblkwidth = 64;
    if (!cblkheightSpec)
        cmdlineP->cblkheight = 64;
    if (modeSpec) {
        if (strcmp(modeOpt, "integer") == 0 || strcmp(modeOpt, "int") == 0)
            cmdlineP->compmode = COMPMODE_INTEGER;
        else if (strcmp(modeOpt, "real") == 0)
            cmdlineP->compmode = COMPMODE_REAL;
        else
            pm_error("Invalid value for 'mode' option: '%s'.  "
                     "valid values are 'INTEGER' and 'REAL'", modeOpt);
    } else
        cmdlineP->compmode = COMPMODE_INTEGER;
    if (!ilyrratesSpec)
        cmdlineP->ilyrrates = (char*) "";
    if (progressionSpec) {
        if (strcmp(progressionOpt, "lrcp") == 0)
            cmdlineP->progression = PROG_LRCP;
        if (strcmp(progressionOpt, "rlcp") == 0)
            cmdlineP->progression = PROG_RLCP;
        if (strcmp(progressionOpt, "rpcl") == 0)
            cmdlineP->progression = PROG_RPCL;
        if (strcmp(progressionOpt, "pcrl") == 0)
            cmdlineP->progression = PROG_PCRL;
        if (strcmp(progressionOpt, "cprl") == 0)
            cmdlineP->progression = PROG_CPRL;
        else
            pm_error("Invalid value for -progression: '%s'.  "
                     "Valid values are lrcp, rlcp, rpcl, pcrl, and cprl.",
                     progressionOpt);
    } else
        cmdlineP->progression = PROG_LRCP;
    if (!numrlvlsSpec)
        cmdlineP->numrlvls = 6;
    if (!numgbitsSpec)
        cmdlineP->numgbits = 2;
    if (!debuglevelSpec)
        cmdlineP->debuglevel = 0;

    if (argc - 1 == 0)
        cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
    else if (argc - 1 == 1)
        cmdlineP->inputFilename = strdup(argv[1]);
    else 
        pm_error("Too many arguments.  The only argument accepted\n"
                 "is the input file specification");

}



static void
createJasperRaster(struct pam *  const inpamP, 
                   jas_image_t * const jasperP) {
/*----------------------------------------------------------------------------
   Create the raster in the *jasperP object, reading the raster from the
   input file described by *inpamP, which is positioned to the raster.
-----------------------------------------------------------------------------*/
    jas_matrix_t ** matrix;  /* malloc'ed */
        /* matrix[X] is the data for Plane X of the current row */
    unsigned int plane;
    unsigned int row;
    tuple * tuplerow;
    bool oddMaxval;
    sample jasperMaxval;

    MALLOCARRAY_NOFAIL(matrix, inpamP->depth);

    for (plane = 0; plane < inpamP->depth; ++plane) {
        matrix[plane] = jas_matrix_create(1, inpamP->width);

        if (matrix[plane] == NULL)
            pm_error("Unable to create matrix for plane %u.  "
                     "jas_matrix_create() failed.", plane);
    }   
    tuplerow = pnm_allocpamrow(inpamP);

    jasperMaxval = pm_bitstomaxval(pm_maxvaltobits(inpamP->maxval));
    oddMaxval = jasperMaxval != inpamP->maxval;

    for (row = 0; row < inpamP->height; ++row) {
        unsigned int col;

        pnm_readpamrow(inpamP, tuplerow);

        for (col = 0; col < inpamP->width; ++col) {
            unsigned int plane;
            for (plane = 0; plane < inpamP->depth; ++plane) {
                unsigned int jasperSample;

                if (oddMaxval)
                    jasperSample = tuplerow[col][plane] * 
                        jasperMaxval / inpamP->maxval;
                else
                    jasperSample = tuplerow[col][plane];

                jas_matrix_set(matrix[plane], 0, col, jasperSample);
            }
        }
        { 
            unsigned int plane;

            for (plane = 0; plane < inpamP->depth; ++plane) {
                int rc;
                rc = jas_image_writecmpt(jasperP, plane, 0, row, 
                                         inpamP->width, 1,
                                         matrix[plane]);
                if (rc != 0)
                    pm_error("jas_image_writecmpt() of plane %u failed.", 
                             plane);
            }
        }
    }

    pnm_freepamrow(tuplerow);
    for (plane = 0; plane < inpamP->depth; ++plane)
        jas_matrix_destroy(matrix[plane]);
    
    free(matrix);
}



static void
createJasperImage(struct pam *   const inpamP, 
                  jas_image_t ** const jasperPP) {

	jas_image_cmptparm_t * cmptparms;
    unsigned int plane;

    MALLOCARRAY_NOFAIL(cmptparms, inpamP->depth);

    for (plane = 0; plane < inpamP->depth; ++plane) {
        cmptparms[plane].tlx = 0;
        cmptparms[plane].tly = 0;
        cmptparms[plane].hstep = 1;
        cmptparms[plane].vstep = 1;
        cmptparms[plane].width = inpamP->width;
        cmptparms[plane].height = inpamP->height;
        cmptparms[plane].prec = pm_maxvaltobits(inpamP->maxval);
        cmptparms[plane].sgnd = 0;
    }
    *jasperPP = 
        jas_image_create(inpamP->depth, cmptparms, JAS_CLRSPC_UNKNOWN);
    if (*jasperPP == NULL)
        pm_error("Unable to create jasper image structure.  "
                 "jas_image_create() failed.");

    free(cmptparms);
}



static void
convertToJasperImage(struct pam *   const inpamP,
                     jas_image_t ** const jasperPP) {

    jas_image_t * jasperP;

    createJasperImage(inpamP, &jasperP);

    if (strncmp(inpamP->tuple_type, "RGB", 3) == 0) {
        if (inpamP->depth < 3)
            pm_error("Input tuple type is RGB*, but depth is only %d.  "
                     "It should be at least 3.", inpamP->depth);
        else {
            jas_image_setclrspc(jasperP, JAS_CLRSPC_GENRGB);
            jas_image_setcmpttype(jasperP, 0,
                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R));
            jas_image_setcmpttype(jasperP, 1,
                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G));
            jas_image_setcmpttype(jasperP, 2,
                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B));
        }
    } else {
        if (strncmp(inpamP->tuple_type, "GRAYSCALE", 9 == 0) ||
            strncmp(inpamP->tuple_type, "BLACKANDWHITE", 13) == 0) {
            jas_image_setclrspc(jasperP, JAS_CLRSPC_GENGRAY);
            jas_image_setcmpttype(jasperP, 0,
                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
        }
    }

    createJasperRaster(inpamP, jasperP);

    *jasperPP = jasperP;
}



static void
writeJpc(jas_image_t *      const jasperP, 
         struct cmdlineInfo const cmdline,
         FILE *             const ofP) {

    jas_stream_t * outStreamP;
    const char * options;
    const char * ilyrratesOpt;
    const char * prgValue;
    char rateOpt[20+1];

    /* Note: ilyrrates is a hack because we're too lazy to properly parse
       command line options to get the information and then compose
       a proper input to Jasper.  So the user can screw things up by 
       specifying garbage for the -ilyrrates option 
    */
    if (strlen(cmdline.ilyrrates) > 0)
        pm_asprintf(&ilyrratesOpt, "ilyrrates=%s", cmdline.ilyrrates);
    else
        ilyrratesOpt = strdup("");

    switch(cmdline.progression) {
    case PROG_LRCP: prgValue = "lrcp"; break;
    case PROG_RLCP: prgValue = "rlcp"; break;
    case PROG_RPCL: prgValue = "rcpc"; break;
    case PROG_PCRL: prgValue = "pcrl"; break;
    case PROG_CPRL: prgValue = "cprl"; break;
    }

    /* Note that asprintfN() doesn't understand %f, but sprintf() does */

    if (cmdline.compressionSpec)
        sprintf(rateOpt, "rate=%1.9f", 1.0/cmdline.compression);
    else {
        /* No 'rate' option.  This means there is no constraint on the image
           size, so the encoder will compress losslessly.  Note that the
           image may get larger, because of metadata.
        */
        rateOpt[0] = '\0';
    }
    pm_asprintf(&options, 
                "imgareatlx=%u "
                "imgareatly=%u "
                "tilegrdtlx=%u "
                "tilegrdtly=%u "
                "tilewidth=%u "
                "tileheight=%u "
                "prcwidth=%u "
                "prcheight=%u "
                "cblkwidth=%u "
                "cblkheight=%u "
                "mode=%s "
                "%s "    /* rate */
                "%s "    /* ilyrrates */
                "prg=%s "
                "numrlvls=%u "
                "numgbits=%u "
                "%s %s %s %s %s %s %s %s %s",
                
                cmdline.imgareatlx,
                cmdline.imgareatly,
                cmdline.tilegrdtlx,
                cmdline.tilegrdtlx,
                cmdline.tilewidth,
                cmdline.tileheight,
                cmdline.prcwidth,
                cmdline.prcheight,
                cmdline.cblkwidth,
                cmdline.cblkheight,
                cmdline.compmode == COMPMODE_INTEGER ? "int" : "real",
                rateOpt,
                ilyrratesOpt,
                prgValue,
                cmdline.numrlvls,
                cmdline.numgbits,
                cmdline.nomct     ? "nomct"     : "",
                cmdline.sop       ? "sop"       : "",
                cmdline.eph       ? "eph"       : "",
                cmdline.lazy      ? "lazy"      : "",
                cmdline.termall   ? "termall"   : "",
                cmdline.segsym    ? "segsym"    : "",
                cmdline.vcausal   ? "vcausal"   : "",
                cmdline.pterm     ? "pterm"     : "",
                cmdline.resetprob ? "resetprob" : ""
        );

    pm_strfree(ilyrratesOpt);

    /* Open the output image file (Standard Output) */
    outStreamP = jas_stream_fdopen(fileno(ofP), "w+b");
    if (outStreamP == NULL)
        pm_error("Unable to open output stream.  jas_stream_fdopen() "
                 "failed");

    {
        int rc;

        if (cmdline.verbose)
            pm_message("Using Jasper to encode to 'jpc' format with options "
                       "'%s'", options);

        rc = jas_image_encode(jasperP, outStreamP, 
                              jas_image_strtofmt((char*)"jpc"), 
                              (char *)options);
        if (rc != 0)
            pm_error("jas_image_encode() failed to encode the JPEG 2000 "
                     "image.  Rc=%d", rc);
    }
	jas_stream_flush(outStreamP);

    {
        int rc;

        rc = jas_stream_close(outStreamP);
            
        if (rc != 0)
            pm_error("Failed to close output stream, "
                     "jas_stream_close() rc = %d", rc);
    }                     

	jas_image_clearfmts();

    pm_strfree(options);
}



int
main(int argc, char **argv)
{
    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct pam inpam;
    jas_image_t * jasperP;

    pnm_init(&argc, argv);
    
    parseCommandLine(argc, argv, &cmdline);
    
    { 
        int rc;
        
        rc = jas_init();
        if ( rc != 0 )
            pm_error("Failed to initialize Jasper library.  "
                     "jas_init() returns rc %d", rc );
    }
    
    jas_setdbglevel(cmdline.debuglevel);
    
    ifP = pm_openr(cmdline.inputFilename);
    
    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
    
    convertToJasperImage(&inpam, &jasperP);
    
    writeJpc(jasperP, cmdline, stdout);
    
	jas_image_destroy(jasperP);

    pm_close(ifP);

    pm_close(stdout);
    
    return 0;
}
