/*
 * 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"

static bool definerunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
static bool addinputexpMode(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 parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile);
static bool setwarpRunState(pxConfig *config, const char *warp_id, const char *state);
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_ADDINPUTEXP,       addinputexpMode);
        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);
        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 definerunMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    // required options
    bool status = false;
    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -survey_mode");
        return false;
    }
    if (!mode) {
        psError(PS_ERR_UNKNOWN, true, "-mode is required");
        return false;
    }
    // check mode
    if (mode && !isValidMode(config, mode)) {
        psError(PS_ERR_UNKNOWN, false, "invalud mode");
        return false;
    }

    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
        return false;
    }
    if (!workdir) {
        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
        return false;
    }

    psTime *registered = NULL;
    {
        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
            return false;
        }
        // pass through NULL as this is an optional field
        if (registeredStr) {
            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
        } else {
            registered = NULL;
        }
    }

    warpRunRow *warpRun = warpRunRowAlloc(
            0,          // ID
            mode,
            "reg",      // state
            workdir,
            NULL,       // dvodb
            registered
    );
    psFree(registered);
    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
    warpRun->warp_id = psDBLastInsertID(config->dbh);

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            psFree(warpRun);
            return false;
        }
    }

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

    psFree(warpRun);

    return true;
}


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

    bool status = false;
    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warpt_id");
        return false;
    }
    if (!warp_id) {
        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
        return false;
    }

    psString state = psMetadataLookupStr(&status, config->args, "-state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
        return false;
    }
    if (!state) {
        psError(PS_ERR_UNKNOWN, true, "-state is required");
        return false;
    }

    if (state) {
        // set detRun.state to state
        return setwarpRunState(config, warp_id, state);
    }

    return true;
}


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

    bool status = false;
    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
        return false;
    }
    if (!warp_id) {
        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
        return false;
    }

    psString cam_id = psMetadataLookupStr(&status, config->args, "-cam_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -cam_id");
        return false;
    }
    if (!cam_id) {
        psError(PS_ERR_UNKNOWN, true, "-cam_id is required");
        return false;
    }

    // defaults to false
    bool magiced = psMetadataLookupBool(&status, config->args, "-magiced");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -magiced");
        return false;
    }

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

    return true;
}


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

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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, "warpInputExp");
        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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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, "warpInputExp", !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);

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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, "warpInputExp");
        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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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);

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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, "warpInputExp");
        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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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, "warpInputExp", !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);

    bool status = false;
    psString mapfile = psMetadataLookupStr(&status, config->args, "-mapfile");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mapfile");
        return false;
    }
    if (!mapfile) {
        psError(PS_ERR_UNKNOWN, true, "-mapfile is required");
        return 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);

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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);

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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);

    bool status = false;
    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
        return false;
    }
    if (!warp_id) {
        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
        return false;
    }

    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
        return false;
    }
    if (!skycell_id) {
        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
        return false;
    }

    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
        return false;
    }
    if (!tess_id) {
        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
        return false;
    }

    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
        return false;
    }
    if (!uri) {
        psError(PS_ERR_UNKNOWN, true, "-uri is required");
        return false;
    }

    // optional
    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
        return false;
    }

    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
        return 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,
            bg,
            bg_stdev
        )) {
        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
#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, 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";

    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);

    bool status = false;
    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
    if (!status) {
        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
        return 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;
    }

    bool simple = false;
    {
        bool status = false;
        simple = psMetadataLookupBool(&status, config->args, "-simple");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
            return false;
        }
    }

    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 setwarpRunState(pxConfig *config, const char *warp_id, const char *state)
{
    PS_ASSERT_PTR_NON_NULL(warp_id, false);
    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 warpRun SET state = '%s' WHERE warp_id = '%s'";
    if (!p_psDBRunQuery(config->dbh, query, state, warp_id)) {
        psError(PS_ERR_UNKNOWN, false,
                "failed to change state for warp_id %s", warp_id);
        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 detRun mode: %s", mode);
        return false;
    }

    return true;
}
