// pstampparse  - read a fits table describing a postage stamp request and prepare the request
// for processing

#include <pslib.h>
#include <psmodules.h>
#include <string.h>
#include <strings.h>
#include "pstamp.h"
#include "pstampROI.h"

typedef struct {
    pmConfig    *config;
    psString    fileName;
    psString    outputDirectory;
    psMetadata  *md;
    psString    roiString;
    pspMode     mode;
    psS64       req_id;
    bool        verbose;
    bool        simple;
} pspOptions;


static void usage()
{
    // XXX: fill this out
    fprintf(stderr, "argument error\n");
    exit(1);
}

static pspMode parseMode(char *modeString)
{
    pspMode mode = PSP_MODE_UNKNOWN;

    if (!strcasecmp(modeString, "list_uri")) {
        mode = PSP_MODE_LIST_URI;
    } else if (!strcasecmp(modeString, "list_job")) {
        mode = PSP_MODE_LIST_JOB;
    } else if (!strcasecmp(modeString, "queue_job")) {
        mode = PSP_MODE_QUEUE_JOB;
    } else {
        fprintf(stderr, "unknown command mode: %s\n", modeString);
        exit(1);
    }
    return mode;
}


static pspOptions *parseArguments(int argc, char *argv[])
{
    int argnum;
    pspOptions *options = psAlloc(sizeof(pspOptions));

    memset(options, 0, sizeof(*options));

    // options to add
    //      mode:   -queuejobs -execute

    options->config = pmConfigRead(&argc, argv, NULL);
    if (!options->config) {
        psError(PS_ERR_UNKNOWN, false, "cannot find site file");
        psFree(options);
        return NULL;
    }

    if ((argnum = psArgumentGet(argc, argv, "-mode"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argnum == argc) {
            fprintf(stderr, "value required for mode\n");
            usage();
        }
        options->mode = parseMode(argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    }

    if ((argnum = psArgumentGet(argc, argv, "-req_id"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argnum == argc) {
            fprintf(stderr, "value required for req_id\n");
            usage();
        }
        options->req_id = atol(argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    }

    if ((argnum = psArgumentGet(argc, argv, "-verbose"))) {
        options->verbose = true;
        psArgumentRemove(argnum, &argc, argv);
    }

    if ((argnum = psArgumentGet(argc, argv, "-simple"))) {
        options->simple = true;
        psArgumentRemove(argnum, &argc, argv);
    }
    if ((argnum = psArgumentGet(argc, argv, "-out_dir"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argnum == argc) {
            fprintf(stderr, "value required for out_dir\n");
            usage();
        }
        options->outputDirectory = argv[argnum];
        psArgumentRemove(argnum, &argc, argv);
    }

    if (argc == 2) {
        options->fileName = psStringCopy(argv[1]);
    } else if (argc == 1) {
        fprintf(stderr, "input file name is required\n");
        usage();
    } else {
        fprintf(stderr, "unknown arguments supplied:");
        fprintf(stderr, " %s", argv[1]);
        for (int i=2; i<argc; i++) {
            fprintf(stderr, ", %s", argv[i]);
        }
        fprintf(stderr, "\n");
        usage();
    }

    return options;
}

static bool readRequestTable(pspOptions *options)
{
    psFits *fitsFile = psFitsOpen(options->fileName, "r");
    if (fitsFile == NULL) {
        psError(PS_ERR_IO, false, "failed to open %s for output", options->fileName);
        return false;
    }

    psArray *array = psFitsReadTable(fitsFile);

    psFitsClose(fitsFile);

    if (array == NULL) {
        psError(PS_ERR_IO, false, "psFitsReadTable failed for %s", options->fileName);
        return false;
    }

    options->md = array->data[0];

    return true;
}

static bool setupDB(pspOptions *options)
{
    // XXX: need to select the database name based on the project name specified in the input

    options->config->database = pmConfigDB(options->config);

    if (!options->config->database) {
        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
        return false;
    }

    return true;
}

// TODO: move this to a shared file
static pstampImageType getImageType(pspOptions *options)
{
    psString type = psMetadataLookupStr(NULL, options->md, "IMG_TYPE");
    pstampImageType itype;

    if (type == NULL) {
        psError(PS_ERR_UNKNOWN, true, "NULL image type");
        itype = PSTAMP_UNKNOWN;
        return itype;
    }

    if (strcmp(type, "raw") == 0) {
        itype = PSTAMP_RAW;
    } else if (strcmp(type, "chip") == 0) {
        itype = PSTAMP_CHIP;
    } else if (strcmp(type, "warp") == 0) {
        itype = PSTAMP_WARP;
    } else if (strcmp(type, "diff") == 0) {
        itype = PSTAMP_DIFF;
    } else if (strcmp(type, "stack") == 0) {
        itype = PSTAMP_STACK;
    } else {
        psError(PS_ERR_UNKNOWN, true, "unknown image type %s", type);
        itype = PSTAMP_UNKNOWN;
    }

    return itype;
}

// return a string with the contents of a unsigned 64 bit value
static psString idToStr(psU64 id)
{
    psString idstr = psStringAlloc(64);
    sprintf(idstr, "%lu", id);

    return idstr;
}


// runQuery
//
// run the given database query and return an array of strings containing the values of target
// the type for the values returned by the query is either a string or psU64.

static psArray *runQuery(pspOptions *options, psString query, psString target,
                psString key1, psString key2, bool targetIsString, psArray *output)
{
    // query string must be in the form
    //  "SELECT %s from sometable WHERE somecolumn = %s and someothercolumn = %s
    //        target                                key1                       key2
    //
    //  or if key2 is null
    //
    //  "SELECT %s from sometable WHERE somecolumn = %s"
    //         target                               key1
    //
    //
    // we return an array containing strins for the values

    if (!p_psDBRunQuery(options->config->database, query, target, key1, key2)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return NULL;
    }

    psArray *queryOutput = p_psDBFetchResult(options->config->database);
    if (!queryOutput) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return NULL;
    }

    psU64 len = psArrayLength(queryOutput);
    if (!len) {
        psTrace("pstampparse", PS_LOG_INFO, "no rows found");
        psFree(queryOutput);
        return NULL;
    }

    if (output == NULL) {
        output = psArrayAlloc(0);
    }

    for (int i=0; i < len; i++) {
        psMetadata *row = queryOutput->data[i];
        if (targetIsString) {
            psString val = psMetadataLookupStr(NULL, row, target);
            if (val != 0) {
                output = psArrayAdd(output, len - i, val);
            }
        } else {
            psU64 val = psMetadataLookupU64(NULL, row, target);
            if (val != 0) {
                output = psArrayAdd(output, len - i, idToStr(val));
            }
        }
    }

    psFree(queryOutput);

    return output;
}

static psArray *findURIs(pspOptions *options, psString table, psString id_type, psString id, psString class_id)
{
    psString query = NULL;

    psStringAppend(&query, "SELECT %%s from %s WHERE %s = %%s", table, id_type);
    // append class_id unless it is set to the special value "all"
    if (class_id && strcmp("all", class_id)) {
        psStringAppend(&query, "%s", " and class_id = '%s'" );
    }

    psArray *results = runQuery(options, query, "uri", id, class_id, true, NULL);

    psFree(query);

    return results;
}

// given an array of strings return the last entry

static psString lastID(psArray *array)
{
    if (array == NULL) {
        return 0;
    }
    psU64 len = psArrayLength(array);

    psString id = NULL;
    if (len > 0) {
        id = (psString) array->data[len-1];
        psMemIncrRefCounter(id);
    }

    // XXX: Hmm if we're doing lastID(runQuery(....)) we need to free the
    // result array, however freeing this in here seems like a potentially dangerous API
    // but hey we're a static function

    psFree(array);

    return id;
}



#define chipIDForExp(_opt, _id) \
            lastID(runQuery((_opt), "SELECT %s from chipRun  WHERE exp_id = %s", "chip_id", \
                            (_id), NULL, false, NULL))

#define camIDForChip(_opt, _id) \
    lastID(runQuery((_opt), "SELECT %s from camRun  WHERE chip_id = %s", "cam_id", \
                        (_id), NULL, false, NULL))

#define warpIDForCam(_opt, _id) \
        lastID(runQuery((_opt), "SELECT %s from warpInputExp  WHERE cam_id = %s", "warp_id", \
                            (_id), NULL, false, NULL))



static psArray *findChipURIs(pspOptions *options, psString exp_id, psString class_id)
{
    psString chip_id = chipIDForExp(options, exp_id);

    if (chip_id == NULL)
        return NULL;

    psArray *chip_files = runQuery(options,
                        "SELECT %s from chipProcessedImfile WHERE chip_id = %s AND class_id = '%s'",
                        "uri", chip_id, class_id, true, NULL);
    psFree(chip_id);

    return chip_files;
}

static psArray *findWarpURIs(pspOptions *options, psString exp_id)
{
    psString chip_id = chipIDForExp(options, exp_id);

    if (chip_id == NULL)
        return NULL;

    psString cam_id = camIDForChip(options, chip_id);
    if (cam_id == NULL)
        return NULL;

    psString warp_id = warpIDForCam(options, cam_id);

    psArray *warp_files = runQuery(options,
                        "SELECT %s from warpSkyfile WHERE warp_id = %s"
                        " AND uri IS NOT NULL and (fault = 0 or fault is NULL)",
                           "uri",   warp_id, NULL, true, NULL);

    psFree(warp_id);
    psFree(cam_id);
    psFree(chip_id);

    return warp_files;
}

static psArray *findStackURIs(pspOptions *options, psString exp_id)
{
    psString chip_id = chipIDForExp(options, exp_id);

    if (chip_id == NULL)
        return NULL;

    psString cam_id = camIDForChip(options, chip_id);
    if (cam_id == NULL)
        return NULL;

    psString warp_id = warpIDForCam(options, cam_id);
    if (warp_id == NULL)
        return NULL;

    psArray *stackIDs = runQuery(options, "SELECT %s FROM stackInputSkyfile WHERE warp_id = %s",
                            "stack_id", warp_id, NULL, false, NULL);

    psFree(warp_id);
    psFree(cam_id);
    psFree(chip_id);

    if (stackIDs == NULL) {
        return NULL;
    }

    psArray *uris = psArrayAlloc(0);

    psU64 len = psArrayLength(stackIDs);

    // loop over the stack's for this exposure and add the images
    for (int i = 0; i < len ; i++ ) {
        psString stack_id = (psString) stackIDs->data[i];
        uris = runQuery(options, "SELECT %s FROM stackSumSkyfile where stack_id = %s",
                    "uri", stack_id, NULL, true, uris);
    }
    psFree(stackIDs);

    return uris;
}

static psArray *findDiffURIs(pspOptions *options, psString exp_id)
{
    psString chip_id = chipIDForExp(options, exp_id);

    if (chip_id == NULL)
        return NULL;

    psString cam_id = camIDForChip(options, chip_id);
    if (cam_id == NULL)
        return NULL;

    psString warp_id = warpIDForCam(options, cam_id);

    psArray *diffIDs = runQuery(options,
        "SELECT %s FROM diffInputSkyfile WHERE warp_id = %s",
            "diff_id", warp_id, NULL, false, NULL);

    psFree(warp_id);
    psFree(cam_id);
    psFree(chip_id);

    if (diffIDs == NULL) {
        return NULL;
    }

    psArray *uris = psArrayAlloc(0);

    psU64 len = psArrayLength(diffIDs);

    for (int i = 0; i < len ; i++ ) {
        psString diff_id = (psString) diffIDs->data[i];
        uris = runQuery(options, "SELECT %s FROM diffSkyfile where diff_id = %s",
                    "uri", diff_id, NULL, true, uris);
    }

    psFree(diffIDs);

    return uris;
}

static psArray * parseByID(pspOptions *options)
{
    bool status = false;
    pstampImageType itype = getImageType(options);
    psString class_id = psMetadataLookupStr(&status, options->md, "CLASS_ID");
    psString id = psMetadataLookupStr(NULL, options->md, "ID");
    psArray *uris = NULL;

    if (id == NULL) {
        fprintf(stderr, "parseByID: need id\n");
        return NULL;
    }

    switch (itype) {
    case PSTAMP_RAW:
        uris = findURIs(options, "rawImfile", "exp_id", id, class_id);
        break;
    case PSTAMP_CHIP:
        uris = findURIs(options, "chipProcessedImfile", "chip_id", id, class_id);
        break;
    case PSTAMP_WARP:
        uris = findURIs(options, "warpSkyfile", "warp_id", id, NULL);
        break;
    case PSTAMP_DIFF:
        uris = findURIs(options, "diffSkyfile", "diff_id", id, NULL);
        break;
    case PSTAMP_STACK:
        uris = findURIs(options, "stackSumSkyfile", "stack_id", id, NULL);
        break;
    default:
        // note: getImageType prints a message
        return NULL;
    }

#ifdef VERBOSE
        for (int i=0; i<psArrayLength(uris); i++) {
            psString uri = (psString) uris->data[i];
            printf("uri: %s\n", uri ? (psString) uri : "NULL uri");
        }
#endif


    return uris;
}

psString exposureNameToID(pspOptions *options, psString exposure)
{
    psArray *results;

    results = runQuery(options, "SELECT %s from rawExp WHERE exp_name = '%s'",
                        "exp_id", exposure, NULL, false, NULL);

    return lastID(results);
}

static psArray * parseByExp(pspOptions *options)
{
    // psError(PS_ERR_UNKNOWN, true, "REQ_TYPE: byexp not implemented yet");
    bool status = false;
    pstampImageType itype = getImageType(options);
    psString class_id = psMetadataLookupStr(&status, options->md, "CLASS_ID");
    psString exposure = psMetadataLookupStr(NULL, options->md, "EXPOSURE");
    psArray *uris = NULL;

    if (exposure == NULL) {
        psError(PS_ERR_UNKNOWN, true, "parseByExp: need exposure name");
        return NULL;
    }

    psString exp_id = exposureNameToID(options, exposure);
    if (exp_id == 0) {
        return NULL;
    }

    switch (itype) {
    case PSTAMP_RAW:
        if (class_id == NULL) {
            // XXX: we could relax this requirement if roi is specified in sky coords and
            // process the whole fpa. That might even be a useful feature.
            psError(PS_ERR_UNKNOWN, true, "stamps from raw images require CLASS_ID");
            return NULL;
        }
        uris = findURIs(options, "rawImfile", "exp_id", exp_id, class_id);
        break;
    case PSTAMP_CHIP:
        if (class_id == NULL) {
            // XXX: we could relax this requirement if roi is specified in sky coords and
            // process the whole fpa. That might even be a useful feature.
            psError(PS_ERR_UNKNOWN, true, "stamps from chip processed images require CLASS_ID");
            return NULL;
        }
        uris = findChipURIs(options, exp_id, class_id);
        break;
    case PSTAMP_WARP:
        uris = findWarpURIs(options, exp_id);
        break;
    case PSTAMP_DIFF:
        uris = findDiffURIs(options, exp_id);
        break;
    case PSTAMP_STACK:
        uris = findStackURIs(options, exp_id);
        break;
    default:
        // note: getImageType prints an error message
        return NULL;
    }

    psFree(exp_id);

    return uris;
}

static psArray * parseByCoord(pspOptions *options)
{
    psError(PSTAMP_ERR_NOT_IMPLEMENTED, true, "REQ_TYPE: bycoord not implemented yet");
    return NULL;
}

static psString parseROI(pspOptions *options)
{
    bool status = false;;
    psString roiString = NULL;
    psString ra = psMetadataLookupStr(&status, options->md, "RA");

    if (ra != NULL) {
        psString dec =  psMetadataLookupStr(&status, options->md, "DEC");
        if (dec == NULL) {
            psError(PS_ERR_UNKNOWN, true, "bad request file: RA specified without DEC");
            return NULL;
        }
        psStringAppend(&roiString, "-skycenter %s %s", ra, dec);
    } else {
        psString x = psMetadataLookupStr(&status, options->md, "CENTER_X");
        if (x == NULL) {
            psError(PS_ERR_UNKNOWN, true, "bad request file: center not specified");
            return NULL;
        }
        psString y = psMetadataLookupStr(&status, options->md, "CENTER_Y");
        psStringAppend(&roiString, "-pixcenter %s %s", x, y);
    }

    psString d_ra = psMetadataLookupStr(&status, options->md, "D_RA");
    if (d_ra != NULL) {
        psString d_dec =  psMetadataLookupStr(&status, options->md, "D_DEC");
        if (d_dec == NULL) {
            psError(PS_ERR_UNKNOWN, true, "bad request file: D_RA specified without D_DEC");
            return NULL;
        }
        psStringAppend(&roiString, " -arcrange %s %s", d_ra, d_dec);
    } else {
        psString w = psMetadataLookupStr(&status, options->md, "WIDTH");
        if (w == NULL) {
            psError(PS_ERR_UNKNOWN, true, "bad request file: range not specified");
            return NULL;
        }
        psString h = psMetadataLookupStr(&status, options->md, "HEIGHT");
        psStringAppend(&roiString, " -pixrange %s %s", w, h);
    }

    return roiString;
}

static bool
queueOneJob (pspOptions *options, psString job_type, psString uri, psString outputBase, psString commandArgs)
{
    psString cmd = NULL;

    psStringAppend(&cmd, "pstamptool -addjob -req_id %ld -job_type %s -uri %s ",
        options->req_id, job_type, uri);

    if (outputBase != NULL) {
        psStringAppend(&cmd, "-outputBase ");

        if (options->outputDirectory) {
            psStringAppend(&cmd, "%s/", options->outputDirectory);
        }
        psStringAppend(&cmd, "%s", outputBase);
    }
    if (commandArgs) {
        psStringAppend(&cmd, " -args '%s'", commandArgs);
    }
    psStringAppend(&cmd, " > /dev/null");

    if (options->verbose) {
        fprintf(stderr, "excuting system(%s)\n", cmd);
    }

    int rstatus = system(cmd);

    if (rstatus) {
        fprintf(stderr, "%s failed with status: %d\n", cmd, rstatus);
    }
    psFree(cmd);

    // TODO: should we be returning the exit status so that it can be reported?
    return !rstatus;
}

static bool
queueJobs (pspOptions *options, psString job_type, psArray *uris, psString user_tag, psString commandArgs)
{
    int numURIs = psArrayLength(uris);
    bool status = false;

    if (numURIs == 1) {
        // use the user tag as the outputBase
        status = queueOneJob(options, job_type, uris->data[0], user_tag, commandArgs);
    } else {
        for (int i = 0; i< numURIs; i++) {
            psString outputBase = NULL;

            // append an integer to the user_tag to get a uniqueu outputBase
            psStringAppend(&outputBase, "%s_%d", user_tag, i);

            status = queueOneJob(options, job_type, uris->data[i], outputBase, commandArgs);
            psFree(outputBase);

            if (!status) {
                break;
            }
        }
    }

    return status;
}

static bool
turnOffResultsFile(pspOptions *options)
{
    // turn off the creation of a results file for this request
    char * query = "UPDATE pstampRequest SET resultsFile = 0 WHERE req_id = %" PRId64;
    return p_psDBRunQuery(options->config->database, query, options->req_id);
}

static bool
queueGetImageJob(pspOptions *options, psArray *uris, psString req_type)
{
    FILE        *listFile;
    psString    fileName = NULL;
    psString    img_type = psMetadataLookupStr(NULL, options->md, "IMG_TYPE");

    if (! turnOffResultsFile(options)) {
        fprintf(stderr, "failed to update resultsFile for request %" PRId64 "\n", options->req_id);
        return false;
    }

    if (options->outputDirectory == NULL) {
        fprintf(stderr, "outputDirectory is required\n");
        return false;
    }
    psStringAppend(&fileName, "%s/filelist", options->outputDirectory);

    listFile = fopen(fileName, "w");
    if (listFile == NULL) {
        psError(PS_ERR_UNKNOWN, true, "failed to open get_image file list: %s\n", fileName);
        return false;
    }

    for (int i=0; i<psArrayLength(uris); i++) {
        fprintf(listFile, "%s|%s\n", (psString) uris->data[i], img_type);
    }
    fclose(listFile);

    psString cmd = NULL;
    psStringAppend(&cmd, "pstamptool -addjob -req_id %ld -job_type get_image -uri %s -outputBase %s",
        options->req_id, fileName, options->outputDirectory);

    int rstatus = system(cmd);
    if (rstatus) {
        fprintf(stderr, "%s failed with status: %d\n", cmd, rstatus);
    }

    return rstatus == 0;
}


static bool
parseRequestTable(pspOptions *options)
{
    bool status;
    psString job_type = psMetadataLookupStr(&status, options->md, "JOB_TYPE");

    if (job_type == NULL) {
        // XXX: compatiabilty hack change to error at some point
        job_type = "stamp";
    }

    psString cmd_mode = psMetadataLookupStr(&status, options->md, "CMD_MODE");

    // if cmd_mode is set on the command line ignore the value in the request file
    if (options->mode == PSP_MODE_UNKNOWN) {
        if (cmd_mode != NULL) {
            options->mode = parseMode(cmd_mode);
        } else {
            // default to listing the parameters of the job's that the request would queue
            options->mode = PSP_MODE_LIST_JOB;
        }
    }

    if ((options->mode == PSP_MODE_QUEUE_JOB) && (options->req_id <= 0)) {
        fprintf(stderr, "need request id in order to queue jobs\n");
        return false;
    }

    // how are we to select the images ?
    psString req_type = psMetadataLookupStr(&status, options->md, "REQ_TYPE");

    if ( !req_type) {
        psError(PS_ERR_UNKNOWN, true, "request file has no REQ_TYPE");
        return false;
    }

    psString roiString = parseROI(options);

    psArray *uris = NULL;

    if (!strcmp(req_type, "byid")) {
        // directly by exp_id, chip_id, warp_id etc

        uris = parseByID(options);
    } else if (!strcmp(req_type, "byexp")) {
        // images that resulted from a given exposure

        uris = parseByExp(options);
    } else if (!strcmp(req_type, "bycoord")) {
        // images from a particular region of the sky

        if (!roiString) {
            fprintf(stderr, "Region of interest required with req_type: bycoord\n");
            return false;
        }
        uris = parseByCoord(options);
    } else {

        psError(PS_ERR_UNKNOWN, true, "unknown REQ_TYPE: %s", req_type);
        return false;
    }

    if (uris == NULL) {
        // may need a finer grained status code: did something go wrong with the system
        // or are there simply no images matching the request
        fprintf(stderr, "No matching images found.\n");
        return false;
    }

    int numURIs = psArrayLength(uris);
    if (options->mode == PSP_MODE_LIST_URI) {
        for (int i=0; i<numURIs; i++) {
            fprintf(stdout, "%s\n", (psString) uris->data[i]);
        }
    } else {
        psString commandArgs = roiString;
        psString class_id = psMetadataLookupStr(&status, options->md, "CLASS_ID");
        if (class_id) {
            // TODO: the ppstamp argument needs to change to class_id
            // perhaps allow chip as a synonym
            psStringAppend(&commandArgs, " -chip %s", class_id);
        }
        psString user_tag = psMetadataLookupStr(&status, options->md, "USER_TAG");

        if (options->mode == PSP_MODE_LIST_JOB) {
            if (numURIs == 1) {
                printf("%s %s %s %s\n", job_type, (psString) uris->data[0],
                    user_tag ? user_tag : "", commandArgs ? commandArgs : "") ;
            } else {
                // add an integer to the user_tag
                for (int i = 0; i < numURIs; i++) {
                    printf("%s %s %s_%d %s\n", job_type, (psString) uris->data[i], user_tag, i,
                        commandArgs ? commandArgs :"");
                }
            }
        } else if (options->mode == PSP_MODE_QUEUE_JOB) {

            if (!strcmp(job_type, "stamp")) {
                // Postage stamp job
                if (user_tag == NULL) {
                    psError(PS_ERR_UNKNOWN, true, "no user tag in request file");
                    return false;
                }

                if (!queueJobs(options, job_type, uris, user_tag, commandArgs)) {
                    return false;
                }
            } else {

                return queueGetImageJob(options, uris, req_type);
            }

        } else {
            // this error is actually caught by parseMode but we might as well check
            fprintf(stderr, "unknown command mode found: %d\n", options->mode);
            exit(1);
        }
    }

    return true;
}

int main(int argc, char *argv[])
{
    pspOptions *options = parseArguments(argc, argv);

    // XXX: create a set of status codes to return so the
    // scripts can distinguish between different errors.
    // For example:
    //      If we can't connect to the DB it's a configuration problem.
    //      If a particular image doesn't contain coordinates specified that's a user "error"
    //      if there are no images of a particular ROI outside of a date range, that tells us
    //      something

    if (!options) {
        return 1;
    }

    if (!readRequestTable(options)) {
        return 1;
    }

    if (!setupDB(options)) {
        return 1;
    }

    if (options->verbose) {
        psMetadataPrint(stderr, options->md, 0);
    }

    if (!parseRequestTable(options)) {
        // psErrorStackPrint(stderr, "unable to parse request table");
        return 1;
    }

    return 0;
}
