// pstamprequestfinish  - Look for postage stamp requests that have completed and
// create the results file

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

// XXX what do I have in mind for the list mode?
typedef enum {
    PSRF_MODE_UNKNOWN,
    PSRF_MODE_FINISH,
    PSRF_MODE_LIST
} psrfMode;

typedef struct {
    pmConfig    *config;
    psMetadata  *md;
    psrfMode     mode;
    bool        verbose;
    bool        simple;
    int         limit;
    psString    dsRoot;
    psString    dsProduct;
    int         exitStatus;
} psrfOptions;


#define RESULTS_FILE_NAME "results.fits"
#define PARSE_ERROR_FILE_NAME "parse_error"


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

static psrfMode parseMode(char *modeString)
{
    psrfMode mode = PSRF_MODE_UNKNOWN;

    if (!strcasecmp(modeString, "finish")) {
        mode = PSRF_MODE_FINISH;
    } else if (!strcasecmp(modeString, "list")) {
        mode = PSRF_MODE_LIST;
    } else {
        fprintf(stderr, "unknown command mode: %s\n", modeString);
        usage();
    }
    return mode;
}


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

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

    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, "-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, "-limit"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argnum == argc) {
            fprintf(stderr, "value required for limit\n");
            usage();
        }
        options->limit = atoi(argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    }

    if (argc != 1) {
        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 setupDB(psrfOptions *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;
}

static bool writeTable(psString fileName, psArray *table)
{
    psFits *fitsFile = psFitsOpen(fileName, "w");
    if (fitsFile == NULL) {
        psError(PS_ERR_IO, true, "failed to open %s for output\n", fileName);
        return false;
    }

    if (!psFitsWriteTable(fitsFile, NULL, table, STAMP_RESULTS_EXTNAME)) {
        psError(PS_ERR_UNKNOWN, false, "failed to write fits table");
        return false;
    }

    if (!psFitsClose(fitsFile)) {
        psError(PS_ERR_UNKNOWN, false, "failed to close fits file");
        return false;
    }

    return true;
}

static psString getBaseName(psString path, bool addDotFits)
{
    psString str = psStringCopy(path);

    psString base = basename(str);

    if (addDotFits) {
        psString newBase = NULL;
        psStringAppend(&newBase, "%s.fits", base);
        base = newBase;
    }
    return base;
}

static bool setRequestStopped(psrfOptions *options, pstampRequestRow *pReq)
{
    psString query = "UPDATE pstampRequest SET state = 'stop' WHERE req_id = %" PRId64;

    if (!p_psDBRunQuery(options->config->database, query, pReq->req_id)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    psU64 affected = psDBAffectedRows(options->config->database);
    if (affected != 1) {
        psError(PS_ERR_UNKNOWN, false, "should have affected one row but %" 
                                        PRIu64 " rows were modified", affected);
        return false;
    }

    return true;

}

static bool buildResultsFile(psrfOptions *options, pstampRequestRow *pReq, psArray *stoppedJobs)
{
    // build results file in dsRoot/pReq->outFileset
    psString filesetDir = NULL;
    psStringAppend(&filesetDir, "%s/%s", options->dsRoot, pReq->outFileset);

    psString resultsFile = NULL;
    psStringAppend(&resultsFile, "%s/%s", filesetDir, RESULTS_FILE_NAME);

    if (options->verbose) {
        fprintf(stderr, "results file: %s\n", resultsFile);
    }

    // loop over jobs and find the list of job result and stamp files
    // add the data to a list.


    psArray *table = psArrayAlloc(0);
    psArray *stamps = psArrayAlloc(0);

    long numStamps = 0;
    if (stoppedJobs) {
        long numJobs = psArrayLength(stoppedJobs);
        for (int i = 0; i < numJobs; i++) {
            pstampJobRow *pJob = psArrayGet(stoppedJobs, i);

            psMetadata *jobmd = psMetadataAlloc();
            psArrayAdd(table, 1, jobmd);

            psMetadataAddS32(jobmd, PS_LIST_TAIL, "RESULT", 0, "", pJob->result);

            if (pJob->result == 0) {
                ++numStamps;
                // XXX: should we check for existence?
                psString stampName = getBaseName(pJob->outputBase, true);
                psString inputName = getBaseName(pJob->uri, false);
                psArrayAdd(stamps, 1, stampName);
                // add basename of stamp and output uri
                psMetadataAddStr(jobmd, PS_LIST_TAIL, "STAMP_NAME", PS_DATA_STRING, "", stampName);
                psMetadataAddStr(jobmd, PS_LIST_TAIL, "INPUT_NAME", PS_DATA_STRING, "", inputName);
                psMetadataAddStr(jobmd, PS_LIST_TAIL, "COMMENT", PS_DATA_STRING, "",
                    "stamp made successfully");
                } else {
                // TODO: convert the result code into something useful to the user
                psMetadataAddStr(jobmd, PS_LIST_TAIL, "COMMENT", PS_DATA_STRING, "",
                    "add description of what happened here");
            }
        }
    } else {
        // request generated no jobs look for a parse error file
        // and add its contents as an ERROR
        bool    gotError = false;
        psMetadata *errmd = psMetadataAlloc();
        psArrayAdd(table, 1, errmd);

        psString parseErrorFile = NULL;
        psStringAppend(&parseErrorFile, "%s/%s", filesetDir, PARSE_ERROR_FILE_NAME);
        FILE *f = fopen(parseErrorFile, "r");
        if (f) {
            char buf[2048];
            bzero(buf, 2048);
            size_t n = fread(buf, 1, 2048, f);
            if (n > 0) {
                psMetadataAddStr(errmd, PS_LIST_TAIL, "ERROR", PS_DATA_STRING, "", buf);
                gotError = true;
            }
            fclose(f);
        }
        if (!gotError) {
            psMetadataAddStr(errmd, PS_LIST_TAIL, "ERROR", PS_DATA_STRING, "",
                "Request queued no jobs, reason unknown");
        }
    }

    if (!writeTable(resultsFile, table)) {
        psError(PS_ERR_UNKNOWN, false, "failed to write pstamp request results to %s", resultsFile);
        return false;
    }
    
    psString fileset_id = getBaseName(pReq->outFileset, false);
    psString command = NULL;

    psStringAppend(&command, "dsreg --add --type PSRESULTS --product %s --fileset %s", 
            options->dsProduct, fileset_id);

    printf("command is: %s\n", command);

    // open a pipe to dsreg and set up the output fileset
    // Each line of input describes one of the files
    #define DSREG_FORMAT_STRING "%s|%s\n"

    FILE *pipe = popen(command, "w");

    if (pipe == NULL) {
        psError(PS_ERR_UNKNOWN, true, "failed to open pipe to dsreg");
        return false;
    }

    // First the results table that we built above
    fprintf(pipe, DSREG_FORMAT_STRING, "results.fits", "psresults");

    // Next each of the stamps
    for (int i = 0; i < numStamps; i++) {
        fprintf(pipe, DSREG_FORMAT_STRING, (psString) psArrayGet(stamps, i), "pstamp");
    }

    int status = pclose(pipe);
    if (status) {
        psError(PS_ERR_UNKNOWN, true, "dsreg failed with status: %d", status);
        return false;
    }

    return true;
}
static bool finishRequest(psrfOptions *options, pstampRequestRow *pReq, psArray *stoppedJobs)
{
    if (pReq->resultsFile) {
        if (!buildResultsFile(options, pReq, stoppedJobs)) {
            return false;
        }
    }
    // update the database setting the request state to stop
    return setRequestStopped(options, pReq);
}

int main(int argc, char *argv[])
{
    psrfOptions *options =  parseArguments(argc, argv);
    int i;
    if (!options) {
        psErrorStackPrint(stderr, "Unable to ParseArguments");
        return PS_EXIT_UNKNOWN_ERROR;
    }

    options->dsRoot = psMetadataLookupStr(NULL, options->config->site, "PSTAMP_DATA_STORE_ROOT");
    if (options->dsRoot == NULL) {
        fprintf(stderr, "failed to find PSTAMP_DS_ROOT in site configuration\n");
        exit (PS_EXIT_CONFIG_ERROR);
    }
    options->dsProduct = psMetadataLookupStr(NULL, options->config->site, "PSTAMP_DATA_STORE_PRODUCT");
    if (options->dsProduct == NULL) {
        fprintf(stderr, "failed to find PSTAMP_DATA_STORE_PRODUCT in site configuration\n");
        exit (PS_EXIT_CONFIG_ERROR);
    }
    if (options->verbose) {
        fprintf(stderr, "data store root: %s\n", options->dsRoot);
    }

    if (!setupDB(options)) {
        return PS_EXIT_CONFIG_ERROR;
    }
    psDB *dbh =  options->config->database;

    psMetadata *whereRunning = psMetadataAlloc();
    psMetadataAddStr(whereRunning, PS_LIST_TAIL, "state", PS_META_DEFAULT, "==", "run");

    psMetadata *whereStopped = psMetadataAlloc();
    psMetadataAddStr(whereStopped, PS_LIST_TAIL, "state", PS_META_DEFAULT, "==", "stop");

    // get all requests in state run
    psArray *runningRequests = pstampRequestSelectRowObjects(dbh, whereRunning, options->limit);

    if (!runningRequests || (psArrayLength(runningRequests) == 0)) {
        if (options->verbose) {
            fprintf(stderr, "no pstampRequests with state == run\n");
        }
        exit (PS_EXIT_SUCCESS);
    }

    int numRequests = psArrayLength(runningRequests);

    for (i=0; i<numRequests; i++) {
        pstampRequestRow *pReq = (pstampRequestRow *) psArrayGet(runningRequests, i);

        if (options->verbose) {
            pstampRequestPrintObject(stderr, pReq, false);
        }

        psMetadataAddS64(whereRunning, PS_LIST_TAIL, "req_id", PS_META_REPLACE, "==", pReq->req_id);

        psArray *runningJobs = pstampJobSelectRowObjects(dbh, whereRunning, 0);
        long numJobs = psArrayLength(runningJobs);

        if (numJobs != 0) {
            if (options->verbose) {
                fprintf(stderr, "request %" PRId64 " has % " PRId64 " running jobs\n", pReq->req_id,
                    numJobs);
                psFree(runningJobs);
            }
            continue;
        }

        psMetadataAddS64(whereStopped, PS_LIST_TAIL, "req_id", PS_META_REPLACE, "==", pReq->req_id);

        psArray *stoppedJobs = pstampJobSelectRowObjects(dbh, whereStopped, 0);
        if (stoppedJobs) {
            numJobs = psArrayLength(stoppedJobs);

            if (numJobs == 0) {
                if (options->verbose) {
                    fprintf(stderr, "request %" PRId64 " has no stopped jobs\n", pReq->req_id);
                    psFree(stoppedJobs);
                    stoppedJobs = NULL;
                    // fall through, finishRequest knows what to do with null jobs array
                }
            }
        }

        if (options->verbose) {
            fprintf(stderr, "request %" PRId64 " has % " PRId64 " stopped jobs\n", pReq->req_id, numJobs);
            pstampJobPrintObjects(stderr, stoppedJobs, false);
        }

        if (!finishRequest(options, pReq, stoppedJobs)) {
            psErrorStackPrint(stderr, "Unable to finish request: %" PRId64, pReq->req_id);
            exit(PS_EXIT_UNKNOWN_ERROR);
        }
        psFree(stoppedJobs);
    }

    psFree(runningRequests);

    return 0;
}
