/*
 * pstamptool.c
 *
 * Copyright (C) 2008
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * program; see the file COPYING. If not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>

#include "pxtools.h"
#include "pxdata.h"
#include "pstamptool.h"

static bool adddatastoreMode(pxConfig *config);
static bool datastoreMode(pxConfig *config);
static bool moddatastoreMode(pxConfig *config);
static bool addreqMode(pxConfig *config);
static bool completedreqMode(pxConfig *config);
static bool listreqMode(pxConfig *config);
static bool pendingreqMode(pxConfig *config);
static bool updatereqMode(pxConfig *config);
static bool revertreqMode(pxConfig *config);
static bool addjobMode(pxConfig *config);
static bool listjobMode(pxConfig *config);
static bool pendingjobMode(pxConfig *config);
static bool updatejobMode(pxConfig *config);
static bool addprojectMode(pxConfig *config);
static bool projectMode(pxConfig *config);
static bool modprojectMode(pxConfig *config);

# define MODECASE(caseName, func) \
    case caseName: \
    if (!func(config)) { \
                goto FAIL; \
            } \
    break;


int main(int argc, char **argv)
{
    psLibInit(NULL);

    pxConfig *config = pstamptoolConfig(NULL, argc, argv);
    if (!config) {
        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
        goto FAIL;
    }

    switch (config->mode) {
        MODECASE(PSTAMPTOOL_MODE_ADDDATASTORE, adddatastoreMode);
        MODECASE(PSTAMPTOOL_MODE_DATASTORE, datastoreMode);
        MODECASE(PSTAMPTOOL_MODE_MODDATASTORE, moddatastoreMode);
        MODECASE(PSTAMPTOOL_MODE_ADDREQ, addreqMode);
        MODECASE(PSTAMPTOOL_MODE_COMPLETEDREQ, completedreqMode);
        MODECASE(PSTAMPTOOL_MODE_LISTREQ, listreqMode);
        MODECASE(PSTAMPTOOL_MODE_PENDINGREQ, pendingreqMode);
        MODECASE(PSTAMPTOOL_MODE_UPDATEREQ, updatereqMode);
        MODECASE(PSTAMPTOOL_MODE_REVERTREQ, revertreqMode);
        MODECASE(PSTAMPTOOL_MODE_ADDJOB, addjobMode);
        MODECASE(PSTAMPTOOL_MODE_LISTJOB, listjobMode);
        MODECASE(PSTAMPTOOL_MODE_PENDINGJOB, pendingjobMode);
        MODECASE(PSTAMPTOOL_MODE_UPDATEJOB, updatejobMode);
        MODECASE(PSTAMPTOOL_MODE_ADDPROJECT, addprojectMode);
        MODECASE(PSTAMPTOOL_MODE_MODPROJECT, modprojectMode);
        MODECASE(PSTAMPTOOL_MODE_PROJECT, projectMode);
        default:
            psAbort("invalid option (this should not happen)");
    }

    psFree(config);
    pmConfigDone();
    psLibFinalize();

    exit(EXIT_SUCCESS);

FAIL:
    psErrorStackPrint(stderr, "\n");
    int exit_status = pxerrorGetExitStatus();

    psFree(config);
    pmConfigDone();
    psLibFinalize();

    exit(exit_status);
}

static bool adddatastoreMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_STR(uri,         config->args, "-uri",           true, false);
    PXOPT_LOOKUP_STR(outProduct,  config->args, "-out_product",   true, false);
    PXOPT_LOOKUP_STR(lastFileset, config->args, "-last_fileset", false, false);
    PXOPT_LOOKUP_STR(state,       config->args, "-state",         false, false);

    if (!pstampDataStoreInsert(config->dbh,
            0,
            state,
            lastFileset,
            outProduct,
            uri
        )) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}

static bool datastoreMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-ds_id", "ds_id", "==");

    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = pxDataGet("pstamptool_datastore.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereSQL(where, NULL);
        psStringAppend(&query, " %s", whereClause);
        psFree(whereClause);
    }
    psFree(where);

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampDataStore", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}
static bool moddatastoreMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(ds_id,       config->args, "-ds_id",         true, false);
    PXOPT_LOOKUP_STR(lastFileset, config->args, "-last_fileset",  false, false);
    PXOPT_LOOKUP_STR(state,       config->args, "-state",         false, false);

    if (!state && !lastFileset) {
        psError(PS_ERR_UNKNOWN, true, "at least one of -last_fileset or -state is required");
        return false;
    }

    char *query = psStringCopy ("UPDATE pstampDataStore SET");
    bool needComma = false;
    
    if (lastFileset) {
        psStringAppend(&query, " lastFileset = '%s'", lastFileset);
        needComma = true;
    }

    if (state) {
        psStringAppend(&query, "%s state = '%s'", needComma ? "," : " ", state);
        needComma = true; // be ready in case we add another field
    }
                
    psStringAppend(&query, " WHERE ds_id = %" PRId64, ds_id);

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

    psU64 affected = psDBAffectedRows(config->dbh);
    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 addreqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_STR(uri,         config->args, "-uri",           true, false);
    // PXOPT_LOOKUP_STR(outFileset,  config->args, "-out_fileset",   true, false);
    PXOPT_LOOKUP_S64(ds_id,       config->args, "-ds_id",         false, false);

    char *query ="INSERT INTO pstampRequest"
                     " (state, uri, ds_id, fault)"
                     " VALUES( 'new', '%s', %" PRId64 ", 0 )";
    if (!p_psDBRunQueryF(config->dbh, query, uri, ds_id)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

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

    psS64 req_id = psDBLastInsertID(config->dbh);

    printf("%" PRId64 "\n", req_id);

    return true;
}

static bool pendingreqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-req_id", "req_id", "==");

    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = pxDataGet("pstamptool_pendingreq.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, "pstampRequest");
        psStringAppend(&query, " AND %s", whereClause);
        psFree(whereClause);
    }
    psFree(where);

    // treat limit == 0 as "no limit"
    if (limit) {
        psString limitString = psDBGenerateLimitSQL(limit);
        psStringAppend(&query, " %s", limitString);
        psFree(limitString);
    }

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static bool listreqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(req_id,  config->args, "-req_id", true, false);
    PXOPT_LOOKUP_U64(limit,   config->args, "-limit",  false, false);
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = NULL;
    psStringAppend(&query, "SELECT * from pstampRequest WHERE req_id = %" PRId64, req_id);
    
    // treat limit == 0 as "no limit"
    if (limit) {
        psString limitString = psDBGenerateLimitSQL(limit);
        psStringAppend(&query, " %s", limitString);
        psFree(limitString);
    }

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static bool completedreqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = 
        "SELECT * FROM pstampRequest WHERE state = 'run' AND "
            "(SELECT count(*) FROM pstampJob WHERE pstampJob.req_id = pstampRequest.req_id "
            " AND pstampJob.state != 'stop') = 0" ;

    // treat limit == 0 as "no limit"
    if (limit) {
        psString limitString = psDBGenerateLimitSQL(limit);
        psStringAppend(&query, " %s", limitString);
        psFree(limitString);
    }

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

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

    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static bool updatereqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(req_id,     config->args, "-req_id",     true, false);
    PXOPT_LOOKUP_STR(state,      config->args, "-state",      false, false);
    PXOPT_LOOKUP_STR(outProduct, config->args, "-outProduct", false, false);
    PXOPT_LOOKUP_STR(fault, 	 config->args, "-fault",      false, false);
    PXOPT_LOOKUP_STR(uri,   	 config->args, "-uri",        false, false);
    PXOPT_LOOKUP_STR(name,  	 config->args, "-name",       false, false);
    PXOPT_LOOKUP_STR(reqType,    config->args, "-reqType",    false, false);

    psString query = NULL;
    psStringAppend(&query, "UPDATE pstampRequest SET");

    char c = ' ';
    if (state) {
        psStringAppend(&query, "%c state = '%s'", c, state);
        c = ',';
    }
    if (outProduct) {
        psStringAppend(&query, "%c outProduct = '%s'", c, outProduct);
        c = ',';
    }
    if (fault) {
        psStringAppend(&query, "%c fault = %s", c, fault);
        c = ',';
    }
    if (uri) {
        psStringAppend(&query, "%c uri = '%s'", c, uri);
        c = ',';
    }
    if (name) {
        psStringAppend(&query, "%c name = '%s'", c, name);
        c = ',';
    }
    if (reqType) {
        psStringAppend(&query, "%c reqType = '%s'", c, reqType);
        c = ',';
    }
    if (!state && !outProduct && !fault && !uri && !name && !reqType) {
        psError(PS_ERR_UNKNOWN, true, "at least one of state, outProduct, fault, uri, name, and reqType must be specified");
        return false;
    }

    psStringAppend(&query, " WHERE req_id = %" PRId64, req_id);

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

    psU64 affected = psDBAffectedRows(config->dbh);
    // note zero is not an error
    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 revertreqMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(req_id,     config->args, "-req_id",     true, false);

    // printf("Revert request %" PRId64 "\n", req_id);
    
    if (!p_psDBRunQueryF(config->dbh, "DELETE FROM pstampJob where req_id = %" PRId64, req_id)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!p_psDBRunQueryF(config->dbh, 
        "UPDATE pstampRequest set state ='new', name=NULL, reqType=NULL, fault=0 where req_id = %" PRId64, req_id)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}

static bool addjobMode(pxConfig *config)
{
    bool    stampJob = false;
    char   *query = NULL;

    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(req_id,      config->args, "-req_id",     true, false);
    PXOPT_LOOKUP_STR(rownum,      config->args, "-rownum",     true, false);
    PXOPT_LOOKUP_STR(job_type,    config->args, "-job_type",   false, false);
    PXOPT_LOOKUP_STR(outputBase,  config->args, "-outputBase", true,  false);
    PXOPT_LOOKUP_STR(stateString, config->args, "-state",      false, false);
    PXOPT_LOOKUP_STR(fault,       config->args, "-fault",      false, false);
    PXOPT_LOOKUP_S64(exp_id,      config->args, "-exp_id",     false, false);

    // default value for job_type is defined in pstamptoolConfig.c
    if (!strcmp(job_type, "get_image") || !strcmp(job_type, "detect_query")) {
	stampJob = false;
    } else if (!strcmp(job_type, "stamp")) {
	stampJob = true;
    } else {
	psError(PS_ERR_UNKNOWN, false, "unknown value for -job_type: %s", job_type);
	return false;
    }
    if (stampJob) {
	query = pxDataGet("pstamptool_addjob_stampjob.sql");
    } else {
	query = pxDataGet("pstamptool_addjob_otherjob.sql");
    }
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    if (!p_psDBRunQueryF(config->dbh, query, req_id, rownum, stateString, job_type, exp_id, outputBase, fault)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

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

    psS64 job_id = psDBLastInsertID(config->dbh);
    printf("%" PRId64 "\n", job_id);

    return true;
}

static bool listjobMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(req_id, config->args, "-req_id", false, false);
    PXOPT_LOOKUP_S64(job_id, config->args, "-job_id", false, false);

    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = NULL;

    if (!req_id && !job_id) {
        fprintf(stderr, "either req_id or job_id must be specified\n");
        exit (1);
    }

    if (req_id) {
        psStringAppend(&query,
                "SELECT"
                " pstampJob.*, pstampRequest.name, pstampRequest.outProduct"
                " FROM pstampJob"
                " JOIN pstampRequest USING(req_id)"
                " WHERE req_id = %" PRId64, req_id
            );
    } else {
        psStringAppend(&query,
                "SELECT"
                " pstampJob.*, pstampRequest.name, pstampRequest.outProduct"
                " FROM pstampJob"
                " JOIN pstampRequest USING(req_id)"
                " WHERE job_id = %" PRId64, job_id
            );
    } 

    // treat limit == 0 as "no limit"
    if (limit) {
        psString limitString = psDBGenerateLimitSQL(limit);
        psStringAppend(&query, " %s", limitString);
        psFree(limitString);
    }

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampJob", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static bool pendingjobMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    psMetadata *where = psMetadataAlloc();

    PXOPT_COPY_S64(config->args, where, "-job_id", "job_id", "==");
    PXOPT_COPY_S64(config->args, where, "-req_id", "req_id", "==");
    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = pxDataGet("pstamptool_pendingjob.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, "pstampJob");
        psStringAppend(&query, " AND %s", whereClause);
        psFree(whereClause);
    }
    psFree(where);

    // treat limit == 0 as "no limit"
    if (limit) {
        psString limitString = psDBGenerateLimitSQL(limit);
        psStringAppend(&query, " %s", limitString);
        psFree(limitString);
    }

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampJob", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static bool updatejobMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(job_id,    config->args, "-job_id", true, false);
    PXOPT_LOOKUP_STR(state,     config->args, "-state",  true, false);
    PXOPT_LOOKUP_STR(fault,     config->args, "-fault",  false, false);

    psString faultStr = NULL;
    if (!fault) {
        faultStr = psStringCopy("");
    } else {
        psStringAppend(&faultStr, ", fault = '%s'", fault);
    }

    char *query ="UPDATE pstampJob"
	" SET state = '%s' %s"
	" WHERE job_id = %" PRId64;
    
    if (!p_psDBRunQueryF(config->dbh, query, state, faultStr, job_id)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        psFree(query);
        return false;
    }
    psFree(faultStr);

    psU64 affected = psDBAffectedRows(config->dbh);
    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 addprojectMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_STR(name,  config->args, "-name", true, false);
    PXOPT_LOOKUP_STR(state,  config->args, "-state", false, false);
    PXOPT_LOOKUP_STR(imagedb, config->args, "-imagedb", true, false);
    PXOPT_LOOKUP_STR(dvodb,  config->args, "-dvodb", false, false);
    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
    PXOPT_LOOKUP_BOOL(need_magic, config->args, "-need_magic", false);

    if (!pstampProjectInsert(config->dbh,
            0,
            name,
            state,
            imagedb,
            dvodb,
            camera,
            telescope,
            need_magic
        )) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}

static bool projectMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    psMetadata *where = psMetadataAlloc();

    PXOPT_COPY_STR(config->args, where, "-name", "name", "==");

    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);

    psString query = pxDataGet("pstamptool_project.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereSQL(where, NULL);
        psStringAppend(&query, " %s", whereClause);
        psFree(whereClause);
    }
    psFree(where);

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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
    if (!psArrayLength(output)) {
        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    // negative simple so the default is true
    if (!ippdbPrintMetadatas(stdout, output, "pstampDataStore", !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print array");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}
static bool modprojectMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    PXOPT_LOOKUP_S64(proj_id,    config->args, "-proj_id", true, false);
    PXOPT_LOOKUP_STR(state,  config->args, "-state", true, false);

    char *query = psStringCopy ("UPDATE pstampProject SET");
    
    psStringAppend(&query, " state = '%s'", state);
                
    psStringAppend(&query, " WHERE proj_id = %" PRId64, proj_id);

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

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

    return true;
}

