/*
 * difftool.c
 *
 * Copyright (C) 2007  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 "difftool.h"

static bool definerunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
static bool addinputskyfileMode(pxConfig *config);
static bool inputskyfileMode(pxConfig *config);
static bool todiffskyfileMode(pxConfig *config);
static bool adddiffskyfileMode(pxConfig *config);
static bool diffskyfileMode(pxConfig *config);

static bool setdiffRunState(pxConfig *config, const char *warp_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 = difftoolConfig(NULL, argc, argv);
    if (!config) {
        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
        goto FAIL;
    }

    switch (config->mode) {
        MODECASE(DIFFTOOL_MODE_DEFINERUN,         definerunMode);
        MODECASE(DIFFTOOL_MODE_UPDATERUN,         updaterunMode);
        MODECASE(DIFFTOOL_MODE_ADDINPUTSKYFILE,    addinputskyfileMode);
        MODECASE(DIFFTOOL_MODE_INPUTSKYFILE,       inputskyfileMode);
        MODECASE(DIFFTOOL_MODE_TODIFFSKYFILE,      todiffskyfileMode);
        MODECASE(DIFFTOOL_MODE_ADDDIFFSKYFILE,     adddiffskyfileMode);
        MODECASE(DIFFTOOL_MODE_DIFFSKYFILE,        diffskyfileMode);
        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 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;
    }

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

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

    diffRunRow *run = diffRunRowAlloc(
            0,          // ID
            "reg",      // state
            workdir,
            NULL,       // dvodb
            registered,
            skycell_id,
            tess_id
    );
    psFree(registered);
    if (!run) {
        psError(PS_ERR_UNKNOWN, false, "failed to alloc diffRun object");
        return true;
    }
    if (!diffRunInsertObject(config->dbh, run)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        psFree(run);
        return true;
    }

    // get the assigned diff_id
    run->diff_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(run);
            return false;
        }
    }

    if (!diffRunPrintObject(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);

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

    return true;
}


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

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

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

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

    return true;
}


static bool inputskyfileMode(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("difftool_inputskyfile.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

    if (config->where) {
        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffInputSkyfile");
        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("difftool", 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, "diffInputSkyfile", !simple)) {
            psError(PS_ERR_UNKNOWN, false, "failed to print array");
            psFree(output);
            return false;
        }
    }

    psFree(output);

    return true;
}


static bool todiffskyfileMode(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;
    }

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

    if (config->where) {
        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffSkyfile");
        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("difftool", 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, "diffSkyfile", !simple)) {
            psError(PS_ERR_UNKNOWN, false, "failed to print array");
            psFree(output);
            return false;
        }
    }

    psFree(output);

    return true;
}


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

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

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

    if (!diffSkyfileInsert(config->dbh,
            (psS64)atoll(diff_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 (!setdiffRunState(config, diff_id, "stop")) {
        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;
}


static bool diffskyfileMode(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;
    }

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

    if (config->where) {
        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffSkyfile");
        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("difftool", 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, "diffSkyfile", !simple)) {
            psError(PS_ERR_UNKNOWN, false, "failed to print array");
            psFree(output);
            return false;
        }
    }

    psFree(output);

    return true;
}

static bool setdiffRunState(pxConfig *config, const char *diff_id, const char *state)
{
    PS_ASSERT_PTR_NON_NULL(diff_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 diffRun state: %s", state);
        return false;
    }

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

    return true;
}
