/*
 * stacktool.c
 *
 * Copyright (C) 2007-2008  Joshua Hoblitt
 *
 * 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 HAVB_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ippdb.h>

#include "pxtools.h"
#include "stacktool.h"

static bool definebyqueryMode(pxConfig *config);
static bool definerunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
static bool addinputskyfileMode(pxConfig *config);
static bool inputskyfileMode(pxConfig *config);
static bool tosumMode(pxConfig *config);
static bool addsumskyfileMode(pxConfig *config);
static bool sumskyfileMode(pxConfig *config);
static bool revertsumskyfileMode(pxConfig *config);

static bool setstackRunState(pxConfig *config, psS64 stack_id, const char *state);

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

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

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

    switch (config->mode) {
        MODECASE(STACKTOOL_MODE_DEFINEBYQUERY,              definebyqueryMode);
        MODECASE(STACKTOOL_MODE_DEFINERUN,          definerunMode);
        MODECASE(STACKTOOL_MODE_UPDATERUN,          updaterunMode);
        MODECASE(STACKTOOL_MODE_ADDINPUTSKYFILE,    addinputskyfileMode);
        MODECASE(STACKTOOL_MODE_INPUTSKYFILE,       inputskyfileMode);
        MODECASE(STACKTOOL_MODE_TOSUM,              tosumMode);
        MODECASE(STACKTOOL_MODE_ADDSUMSKYFILE,      addsumskyfileMode);
        MODECASE(STACKTOOL_MODE_SUMSKYFILE,         sumskyfileMode);
        MODECASE(STACKTOOL_MODE_REVERTSUMSKYFILE,   revertsumskyfileMode);
        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 definebyqueryMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    // required options
    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);

    psMetadata *whereRE = psMetadataAlloc(); // rawExp where
    psMetadata *whereWSF = psMetadataAlloc(); // warpSkyfile where
    psMetadata *having = psMetadataAlloc(); // HAVING clause
    // map -inst -> camera, etc
    PXOPT_COPY_STR(config->args, whereRE, "-select_inst", "camera", "==");
    PXOPT_COPY_STR(config->args, whereRE, "-select_telescope", "telescope", "==");
    PXOPT_COPY_STR(config->args, whereRE, "-select_filter", "filter", "==");
    PXOPT_COPY_STR(config->args, whereRE, "-select_uri", "uri", "==");
    PXOPT_COPY_TIME(config->args, whereRE, "-select_dateobs_begin", "dateobs", ">=");
    PXOPT_COPY_TIME(config->args, whereRE, "-select_dateobs_end", "dateobs", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_airmass_min", "airmass", ">=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_airmass_max", "airmass", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_sat_pixel_frac_max", "sat_pixel_frac", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_exp_time_min", "exp_time", ">=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_exp_time_max", "exp_time", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_ccd_temp_min", "ccd_temp", ">=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_ccd_temp_max", "ccd_temp", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_posang_min", "posang", ">=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_posang_max", "posang", "<=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_solang_min", "solang", ">=");
    PXOPT_COPY_F32(config->args, whereRE, "-select_solang_max", "solang", "<=");

    PXOPT_COPY_STR(config->args, having, "-select_skycell_id", "skycell_id", "==");
    PXOPT_COPY_S32(config->args, having, "-min_num", "num_warp", ">=");

    PXOPT_LOOKUP_S32(randomLimit, config->args, "-random", false, false);

    PXOPT_LOOKUP_S32(min_new, config->args, "-min_new", false, false);
    PXOPT_LOOKUP_F32(min_frac, config->args, "-min_frac", false, false);

    if (!psListLength(whereRE->list) && !psListLength(whereWSF->list) &&
        !psMetadataLookupBool(NULL, config->args, "-all")) {
        psFree(whereRE);
        psFree(whereWSF);
        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
        return false;
    }

    // default
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
    PXOPT_COPY_STR(config->args, whereRE, "-select_exp_type", "exp_type", "==");
    PXOPT_COPY_F32(config->args, whereWSF, "-select_good_frac_min", "good_frac", ">=");

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

    if (psListLength(whereRE->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(whereRE, "rawExp");
        psStringAppend(&select, " AND %s", whereClause);
        psFree(whereClause);
    }
    psFree(whereRE);

    if (psListLength(whereWSF->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(whereWSF, "warpSkyfile");
        psStringAppend(&select, " AND %s", whereClause);
        psFree(whereClause);
    }
    psFree(whereWSF);

    psString groupby = pxDataGet("stacktool_definebyquery_part2.sql");
    if (!groupby) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }
    psStringAppend(&select, " %s", groupby);
    psFree(groupby);

    // Restriction on aggregated quantities using HAVING
    {
        psString havingClause = NULL;   // HAVING string
        if (psListLength(having->list)) {
            havingClause = psDBGenerateWhereConditionSQL(having, NULL);
        }

        if (min_new > 0) {
            if (havingClause) {
                psStringAppend(&havingClause, " AND");
            }
            psStringAppend(&havingClause,
                           " (num_warp - num_stack >= %d OR (num_warp >= %d AND num_stack IS NULL))",
                           min_new, min_new);
        }
        if (isfinite(min_frac)) {
            if (havingClause) {
                psStringAppend(&havingClause, " AND");
            }
            // Avoiding division by zero
            psStringAppend(&havingClause, " (num_warp >= %f * num_stack OR num_stack IS NULL)",
                           (double)min_frac);
        }
        if (havingClause) {
            psStringAppend(&select, " HAVING %s", havingClause);
            psFree(havingClause);
        }
    }
    psFree(having);


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

    psArray *output = p_psDBFetchResult(config->dbh);
    if (!output) {
        psErrorCode err = psErrorCodeLast();
        switch (err) {
            case PS_ERR_DB_CLIENT:
                psError(PXTOOLS_ERR_SYS, false, "database error");
            case PS_ERR_DB_SERVER:
                psError(PXTOOLS_ERR_PROG, false, "database error");
            default:
                psError(PXTOOLS_ERR_PROG, false, "unknown error");
        }

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

    psString insert = NULL;             // Insertion query
    if (randomLimit > 0) {
        insert = pxDataGet("stacktool_definebyquery_insert_random.sql");
    } else {
        insert = pxDataGet("stacktool_definebyquery_insert.sql");
    }
    if (!insert) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    psArray *list = psArrayAllocEmpty(16); // List of runs, to print
    for (long i = 0; i < output->n; i++) {
        psMetadata *row = output->data[i]; // Row from select
        bool status;

        // pull out the skycell_id, tess_id, filter
        psString skycell_id = psMetadataLookupStr(&status, row, "skycell_id");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup skycell_id");
            psFree(output);
            psFree(insert);
            psFree(list);
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

        psString tess_id = psMetadataLookupStr(&status, row, "tess_id");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup tess_id");
            psFree(output);
            psFree(insert);
            psFree(list);
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

        psString filter = psMetadataLookupStr(&status, row, "filter");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup filter");
            psFree(output);
            psFree(insert);
            psFree(list);
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

        // create a new stackRun for this stack
        stackRunRow *run = stackRunRowAlloc(0, "run", workdir, NULL, registered, skycell_id, tess_id, filter);
        if (!stackRunInsertObject(config->dbh, run)) {
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "database error");
            psFree(output);
            psFree(run);
            psFree(insert);
            psFree(list);
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

        // figure out the new stack_id
        psS64 stack_id = psDBLastInsertID(config->dbh);
        run->stack_id = stack_id;

        psArrayAdd(list, list->n, run);
        psFree(run);

        // Create a suitable insertion query for this run
        psString thisInsert = psStringCopy(insert);
        psString idString = NULL;
        psStringAppend(&idString, "%" PRId64, stack_id);
        psStringSubstitute(&thisInsert, idString, "@STACK_ID@");
        psFree(idString);

        if ((randomLimit > 0 && !p_psDBRunQuery(config->dbh, thisInsert, skycell_id, tess_id, filter,
                                                randomLimit)) ||
            (randomLimit <= 0 && !p_psDBRunQuery(config->dbh, thisInsert, skycell_id, tess_id, filter))) {
            psError(PS_ERR_UNKNOWN, false, "database error");
            psFree(thisInsert);
            psFree(insert);
            psFree(output);
            psFree(list);
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }
        psFree(thisInsert);
    }
    psFree(output);

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

    if (!stackRunPrintObjects(stdout, list, !simple)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print object");
        return false;
    }

    psFree(list);

    return true;
}


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

    // required options
    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", true, false);
    PXOPT_LOOKUP_STR(filter, config->args, "-filter", true, false);

    // default
    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);

    // we have to support multipe exp_ids
    psMetadataItem *warp_ids = psMetadataLookup(config->args, "-warp_id");
    if (!warp_ids) {
        // this shouldn't actually happen when using psArgs
        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
        return false;
    }

    stackRunRow *run = stackRunRowAlloc(0, "run", workdir, NULL, registered, skycell_id, tess_id, filter);
    if (!run) {
        psError(PS_ERR_UNKNOWN, false, "failed to alloc stackRun object");
        return true;
    }

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

    if (!stackRunInsertObject(config->dbh, run)) {
        if (!psDBRollback(config->dbh)) {
            psError(PS_ERR_UNKNOWN, false, "database error");
        }
        psError(PS_ERR_UNKNOWN, false, "database error");
        psFree(run);
        return true;
    }

    // get the assigned warp_id
    run->stack_id = psDBLastInsertID(config->dbh);

    // insert the stackInputSkyfile rows
    psListIterator *iter = psListIteratorAlloc(warp_ids->data.list, 0, false);
    psMetadataItem *item = NULL;
    while ((item = psListGetAndIncrement(iter))) {
        // if the value is NULL this is probably the first pass through the
        // loop and -warp_id was not specified at all
        if (!item->data.V) {
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
            return false;
        }
        if (!stackInputSkyfileInsert(config->dbh, run->stack_id, (psS64)atoll((char *)item->data.V))) {
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "failed to insert stackInputSkyfile rows");
            return false;
        }
    }
    psFree(iter);

    // point of no return
    if (!psDBCommit(config->dbh)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    if (!stackRunPrintObject(stdout, run, !simple)) {
            psError(PS_ERR_UNKNOWN, false, "failed to print object");
            psFree(run);
            return false;
    }

    psFree(run);

    return true;
}


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

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

    if (state) {
        // set detRun.state to state
        return setstackRunState(config, stack_id, state);
    }

    return true;
}


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

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

    // XXX need to validate the warp_id here
    // XXX instead of validiting it here we should just use forgein key
    // constrants
    if (!stackInputSkyfileInsert(config->dbh,
            stack_id,
            warp_id
        )) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}


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

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

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

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

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackInputSkyfile");
        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) {
        psErrorCode err = psErrorCodeLast();
        switch (err) {
            case PS_ERR_DB_CLIENT:
                psError(PXTOOLS_ERR_SYS, false, "database error");
            case PS_ERR_DB_SERVER:
                psError(PXTOOLS_ERR_PROG, false, "database error");
            default:
                psError(PXTOOLS_ERR_PROG, false, "unknown error");
        }

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

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

    psFree(output);

    return true;
}


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

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

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

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

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackSumSkyfile");
        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) {
        psErrorCode err = psErrorCodeLast();
        switch (err) {
            case PS_ERR_DB_CLIENT:
                psError(PXTOOLS_ERR_SYS, false, "database error");
            case PS_ERR_DB_SERVER:
                psError(PXTOOLS_ERR_PROG, false, "database error");
            default:
                psError(PXTOOLS_ERR_PROG, false, "unknown error");
        }

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

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

    psFree(output);

    return true;
}


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

    // required
    PXOPT_LOOKUP_S64(stack_id, config->args, "-stack_id", true, false);

    // optional
    PXOPT_LOOKUP_STR(uri, config->args, "-uri", false, false);
    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
    PXOPT_LOOKUP_F32(dtime_stack, config->args, "-dtime_stack", false, false);
    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);

    // default values
    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);

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

    // XXX need to validate the stack_id here
    // XXX instead of validiting it here we should just use forgein key
    // constrants
    if (!stackSumSkyfileInsert(config->dbh,
                               stack_id,
                               uri,
                               path_base,
                               bg,
                               bg_stdev,
                               dtime_stack,
                               hostname,
                               good_frac,
                               code
          )) {
        if (!psDBRollback(config->dbh)) {
            psError(PS_ERR_UNKNOWN, false, "database error");
        }
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    if (!setstackRunState(config, stack_id, "stop")) {
        if (!psDBRollback(config->dbh)) {
            psError(PS_ERR_UNKNOWN, false, "database error");
        }
        psError(PS_ERR_UNKNOWN, false, "failed to change stackRun's state");
        return false;
    }

    // point of no return
    if (!psDBCommit(config->dbh)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}


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

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

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

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

    if (psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackSumSkyfile");
        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) {
        psErrorCode err = psErrorCodeLast();
        switch (err) {
            case PS_ERR_DB_CLIENT:
                psError(PXTOOLS_ERR_SYS, false, "database error");
            case PS_ERR_DB_SERVER:
                psError(PXTOOLS_ERR_PROG, false, "database error");
            default:
                psError(PXTOOLS_ERR_PROG, false, "unknown error");
        }

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

    if (psArrayLength(output)) {
        if (!ippdbPrintMetadatas(stdout, output, "stackSumSkyfile", !simple)) {
            psError(PS_ERR_UNKNOWN, false, "failed to print array");
            psFree(output);
            return false;
        }
    }

    psFree(output);

    return true;
}


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

    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-stack_id", "stack_id", "==");
    PXOPT_COPY_S16(config->args, where, "-code", "fault", "==");

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

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

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

    if (psDBAffectedRows(config->dbh) < 1) {
        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
        return false;
    }

    return true;
}


static bool setstackRunState(pxConfig *config, psS64 stack_id, const char *state)
{
    PS_ASSERT_PTR_NON_NULL(state, false);

    // check that state is a valid string value
    if (!(
            (strncmp(state, "run", 4) == 0)
            || (strncmp(state, "stop", 5) == 0)
            || (strncmp(state, "reg", 4) == 0)
        )
    ) {
        psError(PS_ERR_UNKNOWN, false,
                "invalid warpRun state: %s", state);
        return false;
    }

    char *query = "UPDATE stackRun SET state = '%s' WHERE stack_id = %"PRId64;
    if (!p_psDBRunQuery(config->dbh, query, state, stack_id)) {
        psError(PS_ERR_UNKNOWN, false,
                "failed to change state for stack_id %"PRId64, stack_id);
        return false;
    }

    return true;
}
