/*
 * warptool.c
 *
 * Copyright (C) 2006  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 "warptool.h"
#include "pxwarp.h"

static psS64 definerunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
static bool expMode(pxConfig *config);
static bool imfileMode(pxConfig *config);
static bool tooverlapMode(pxConfig *config);
static bool addoverlapMode(pxConfig *config);
static bool scmapMode(pxConfig *config);
static bool towarpedMode(pxConfig *config);
static bool addwarpedMode(pxConfig *config);
static bool warpedMode(pxConfig *config);
static bool revertwarpedMode(pxConfig *config);
static bool blockMode(pxConfig *config);
static bool maskedMode(pxConfig *config);
static bool unblockMode(pxConfig *config);

static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile);
static bool isValidMode(pxConfig *config, const char *mode);
bool warpCompletedRuns(pxConfig *config);

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

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

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

    switch (config->mode) {
        MODECASE(WARPTOOL_MODE_DEFINERUN,         definerunMode);
        MODECASE(WARPTOOL_MODE_UPDATERUN,         updaterunMode);
        MODECASE(WARPTOOL_MODE_EXP,               expMode);
        MODECASE(WARPTOOL_MODE_IMFILE,            imfileMode);
        MODECASE(WARPTOOL_MODE_TOOVERLAP,         tooverlapMode);
        MODECASE(WARPTOOL_MODE_ADDOVERLAP,        addoverlapMode);
        MODECASE(WARPTOOL_MODE_SCMAP,             scmapMode);
        MODECASE(WARPTOOL_MODE_TOWARPED,          towarpedMode);
        MODECASE(WARPTOOL_MODE_ADDWARPED,         addwarpedMode);
        MODECASE(WARPTOOL_MODE_WARPED,            warpedMode);
        MODECASE(WARPTOOL_MODE_REVERTWARPED,      revertwarpedMode);
        MODECASE(WARPTOOL_MODE_BLOCK,             blockMode);
        MODECASE(WARPTOOL_MODE_MASKED,            maskedMode);
        MODECASE(WARPTOOL_MODE_UNBLOCK,           unblockMode);

        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 psS64 definerunMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    // required options
    PXOPT_LOOKUP_STR(cam_id, config->args, "-cam_id", true, false);
    PXOPT_LOOKUP_STR(mode, config->args, "-mode", true, false);
    // check mode
    if (mode && !isValidMode(config, mode)) {
        psError(PS_ERR_UNKNOWN, false, "invalud mode");
        return false;
    }
    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);

    // optional
    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", false, false);
    PXOPT_LOOKUP_STR(end_stage, config->args, "-end_stage", false, false);

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

    warpRunRow *warpRun = warpRunRowAlloc(
            0,          // ID
            (psS64)atoll(cam_id),
            mode,
            "run",      // state
            workdir,
            "dirty",    // workdir_state
            label,
            dvodb,
            tess_id,
            end_stage,
            registered,
            magiced
    );
    if (!warpRun) {
        psError(PS_ERR_UNKNOWN, false, "failed to alloc warpRun object");
        return true;
    }
    if (!warpRunInsertObject(config->dbh, warpRun)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        psFree(warpRun);
        return true;
    }

    // get the assigned warp_id
    psS64 warp_id = psDBLastInsertID(config->dbh);
    warpRun->warp_id = warp_id;

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

    psFree(warpRun);

    return warp_id;
}


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

    PXOPT_LOOKUP_STR(warp_id, config->args, "-warp_id", false, false);
    PXOPT_LOOKUP_STR(cam_id, config->args, "-cam_id", false, false);
    PXOPT_LOOKUP_STR(chip_id, config->args, "-chip_id", false, false);
    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", false, false);

    psMetadata *where = psMetadataAlloc();
    // convert warp_id into a psS64
    if (warp_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "warp_id", 0, "==", (psS64)atoll(warp_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
            psFree(where);
            return false;
        }
    }

    // convert cam_id into a psS64
    if (cam_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "cam_id", 0, "==", (psS64)atoll(cam_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
            psFree(where);
            return false;
        }
    }

    // convert chip_id into a psS64
    if (chip_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "chip_id", 0, "==", (psS64)atoll(chip_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
            psFree(where);
            return false;
        }
    }

    // convert exp_id into a psS64
    if (exp_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "exp_id", 0, "==", (psS64)atoll(exp_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
            psFree(where);
            return false;
        }
    }

    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");

    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");

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

    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);

    if ((!state) && (!label)) {
        psError(PXTOOLS_ERR_DATA, false, "parameters are required");
        psFree(where);
        return false;
    }

    if (state) {
        // set chipRun.state to state
        if (!pxwarpRunSetStateByQuery(config, where, state)) {
            psFree(where);
            return false;
        }
    }

    if (label) {
        // set chipRun.label to label
        if (!pxwarpRunSetLabelByQuery(config, where, label)) {
            psFree(where);
            return false;
        }
    }

    psFree(where);

    return true;
}


static bool expMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_exp.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


static bool imfileMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_imfile.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


static bool tooverlapMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_tooverlap.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


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

    PXOPT_LOOKUP_STR(mapfile, config->args, "-mapfile", false, false);

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

    if (!parseAndInsertSkyCellMap(config, mapfile)) {
        psError(PS_ERR_UNKNOWN, false, "failed to inject mapfile: %s into the database", mapfile);
        // rollback
        if (!psDBRollback(config->dbh)) {
            psError(PS_ERR_UNKNOWN, false, "database error");
        }
        return false;
    }

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

    return true;
}


static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile)
{
    unsigned int nFail = 0;
    psMetadata *skycells = psMetadataConfigRead(NULL, &nFail, mapfile, false);
    if (!skycells) {
        psError(PS_ERR_UNKNOWN, false, "failed to parse mapfile: %s", mapfile);
        return false;
    }
    if (nFail) {
        psError(PS_ERR_UNKNOWN, false, "there were %d errors parsing mapfile: %s", nFail, mapfile);
        psFree(skycells);
        return false;
    }

    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc(skycells, 0, NULL);
    while ((item = psMetadataGetAndIncrement(iter))) {
        if (item->type != PS_DATA_METADATA) {
            psError(PS_ERR_UNKNOWN, false, "mapfile: %s is in the wrong format", mapfile);
            psFree(iter);
            psFree(skycells);
            return false;
        }

        psMetadata *sc = item->data.md;
        // this conversion isn't strictly nessicary but it's an easy way of
        // validating the format
        warpSkyCellMapRow *row = warpSkyCellMapObjectFromMetadata(sc);
        if (!row) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert mapfile: %s metdata entry into a warpSkyCellMap object", mapfile);
            psFree(iter);
            psFree(skycells);
            return false;
        }

        if (!warpSkyCellMapInsertObject(config->dbh, row)) {
            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");
            }
            psFree(row);
            psFree(iter);
            psFree(skycells);
            return false;
        }

        psFree(row);
    }
    psFree(iter);
    psFree(skycells);

    return true;
}


static bool scmapMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_scmap.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


static bool towarpedMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_towarped.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


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

    PXOPT_LOOKUP_STR(warp_id, config->args, "-warp_id", true, false);
    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_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_warp, config->args, "-dtime_warp", false, false);
    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);
    PXOPT_LOOKUP_BOOL(ignore, config->args, "-ignore", false);

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

    // we don't want to insert the last skyfile in a run but then not mark the
    // run as 'stop'
    if (!psDBTransaction(config->dbh)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    // XXX need to validate that this coresponds to an warpInputImfile
    if (!warpSkyfileInsert(config->dbh,
                           (psS64)atoll(warp_id),
                           skycell_id,
                           tess_id,
                           uri,
                           path_base,
                           bg,
                           bg_stdev,
                           dtime_warp,
                           hostname,
                           good_frac,
                           ignore,
                           code
        )) {
        if (!psDBRollback(config->dbh)) {
            psError(PS_ERR_UNKNOWN, false, "database error");
        }
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

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

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

    return true;
}

bool warpCompletedRuns(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    // XXX this SQL has not been broken out to into seperate files as the MYSQL
    // < 5 & MYSQL 5 versions need to be kept in sync

#undef MYSQL5
#if MYSQL5
    // XXX at MySQL 4.1.21 (probably all of 4.1.x) chokes and dies on this
    // statement as it thinks it is trying to select from the table being
    // updated. The 4.1 manual says that nested sub-queries are explicited
    // allowed to do this with update statements as a temporary table is
    // created so that you are not actually selecting from the table you are
    // modifying.
    char *query =
        "UPDATE warpRun\n"
        "   SET warpRun.state = 'stop'\n"
        " WHERE\n"
        "   warpRun.warp_id =\n"
        "   (SELECT DISTINCT\n"
        "       warp_id\n"
        "   FROM\n"
        "       (SELECT DISTINCT\n"
        "           warpRun.warp_id,\n"
        "           warpSkyCellMap.warp_id as foo,\n"
        "           warpSkyfile.warp_id as bar\n"
        "       FROM warpRun\n"
        "       JOIN warpSkyCellMap\n"
        "           USING(warp_id)\n"
        "       LEFT JOIN warpSkyfile\n"
        "           USING(warp_id, skycell_id, tess_id)\n"
        "       WHERE\n"
        "           warpRun.state = 'run'\n"
        "       GROUP BY\n"
        "           warpRun.warp_id\n"
        "       HAVING\n"
        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
        "       ) as Foo\n"
        "   )\n";

    if (!p_psDBRunQuery(config->dbh, query)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
#else // if MYSQL5
{
    char *query =
        "CREATE TEMPORARY TABLE finished\n"
        " (warp_id INT, PRIMARY KEY(warp_id)) ENGINE=MEMORY\n";

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

{
    char *query =
        "INSERT INTO finished\n"
        " SELECT\n"
        "   warp_id\n"
        " FROM\n"
        "   (SELECT DISTINCT\n"
        "       warpRun.warp_id,\n"
        "       warpSkyCellMap.warp_id as foo,\n"
        "       warpSkyfile.warp_id as bar\n"
        "   FROM warpRun\n"
        "   JOIN warpSkyCellMap\n"
        "       USING(warp_id)\n"
        "   LEFT JOIN warpSkyfile\n"
        "       USING(warp_id, skycell_id)\n"
        "   WHERE\n"
        "       warpRun.state = 'run'\n"
        "   GROUP BY\n"
        "       warpRun.warp_id\n"
        "   HAVING\n"
        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
        " ) as Foo \n";

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

{
    char *query =
        "UPDATE warpRun\n"
        "   SET warpRun.state = 'stop'\n"
        " WHERE\n"
        "   warpRun.warp_id =\n"
        "   (SELECT DISTINCT\n"
        "       warp_id\n"
        "   FROM finished\n"
        "   )\n";

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

    return true;
}

static bool warpedMode(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);

    // find all rawImfiles matching the default query
    psString query = pxDataGet("warptool_warped.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // 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("warptool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (psArrayLength(output)) {
        if (!convertIdToStr(output)) {
            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
            psFree(output);
            return false;
        }

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

    psFree(output);

    return true;
}


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

    PXOPT_LOOKUP_STR(warp_id, config->args, "-warp_id", false, false);
    PXOPT_LOOKUP_STR(cam_id, config->args, "-cam_id", false, false);
    PXOPT_LOOKUP_STR(chip_id, config->args, "-chip_id", false, false);
    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", false, false);

    psMetadata *where = psMetadataAlloc();
    // convert warp_id into a psS64
    if (warp_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "warp_id", 0, "==", (psS64)atoll(warp_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
            psFree(where);
            return false;
        }
    }

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

    // convert cam_id into a psS64
    if (cam_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "cam_id", 0, "==", (psS64)atoll(cam_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
            psFree(where);
            return false;
        }
    }

    // convert chip_id into a psS64
    if (chip_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "chip_id", 0, "==", (psS64)atoll(chip_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
            psFree(where);
            return false;
        }
    }

    // convert exp_id into a psS64
    if (exp_id) {
        if (!psMetadataAddS64(where, PS_LIST_TAIL, "exp_id", 0, "==", (psS64)atoll(exp_id))) {
            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
            psFree(where);
            return false;
        }
    }

    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");

    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
//    PXOPT_COPY_S16(config->args, where, "-code", "fault", "==");

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

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

    if (where && psListLength(where->list)) {
        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
        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);

    return true;
}


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

    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);

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

    return true;
}


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

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

    psString query = psStringCopy("SELECT * FROM warpMask");

    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("warpool", PS_LOG_INFO, "no rows found");
        psFree(output);
        return true;
    }

    if (!convertIdToStr(output)) {
        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
        psFree(output);
        return false;
    }

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

    psFree(output);

    return true;
}


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

    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);

    char *query = "DELETE FROM warpMask WHERE label = '%s'";

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

    return true;
}


static bool isValidMode(pxConfig *config, const char *mode)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    PS_ASSERT_PTR_NON_NULL(mode, false);

    // check that state is a valid string value
    if (!(
            (strncmp(mode, "warp", 5) == 0)
            || (strncmp(mode, "diff", 5) == 0)
            || (strncmp(mode, "stack", 6) == 0)
            || (strncmp(mode, "magic", 6) == 0)
        )
    ) {
        psError(PS_ERR_UNKNOWN, false,
                "invalid warpRun mode: %s", mode);
        return false;
    }

    return true;
}
