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

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

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

#include "pxtools.h"
#include "pxdata.h"
#include "disttool.h"

static bool definebyqueryMode(pxConfig *config);
static bool definerunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
static bool revertrunMode(pxConfig *config);
static bool pendingcomponentMode(pxConfig *config);
static bool addprocessedcomponentMode(pxConfig *config);
static bool processedcomponentMode(pxConfig *config);
static bool toadvanceMode(pxConfig *config);

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


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

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

    switch (config->mode) {
        MODECASE(DISTTOOL_MODE_DEFINEBYQUERY, definebyqueryMode);
        MODECASE(DISTTOOL_MODE_DEFINERUN, definerunMode);
        MODECASE(DISTTOOL_MODE_UPDATERUN, updaterunMode);
        MODECASE(DISTTOOL_MODE_REVERTRUN, revertrunMode);
        MODECASE(DISTTOOL_MODE_PENDINGCOMPONENT, pendingcomponentMode);
        MODECASE(DISTTOOL_MODE_ADDPROCESSEDCOMPONENT, addprocessedcomponentMode);
        MODECASE(DISTTOOL_MODE_PROCESSEDCOMPONENT, processedcomponentMode);
        MODECASE(DISTTOOL_MODE_TOADVANCE, toadvanceMode);
        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);

    PXOPT_LOOKUP_STR(stage, config->args, "-stage", true, false);
    PXOPT_LOOKUP_S64(stage_id, config->args, "-stage_id",  true, false);
    PXOPT_LOOKUP_STR(outroot, config->args, "-outroot", true, false);
    PXOPT_LOOKUP_BOOL(clean, config->args, "-clean", false);
    PXOPT_LOOKUP_STR(set_label, config->args, "-set_label", false, false);

    bool require_chip_id = false;
    if (!strcmp(stage, "raw")) {
        // need to know the chip id for raw stage so that we can find the masks
        require_chip_id = true;
    }
    PXOPT_LOOKUP_S64(chip_id, config->args, "-chip_id",  require_chip_id, false);

    // TODO: check that stage has an expected value
    // TODO: check that stage_id actually exists for stage
    // in magicdstool we queue off of a magic_id so the stage_id, exp_id, and cam_id get looked up 
    // when the run is queued
    // Should we also check here that the run is full or cleaned and that all of the images
    // are magicked. We need to do that at run time as well since the run and magic state can
    // change between the time that it is queued.

    if (!distRunInsert(config->dbh, 0, stage, stage_id, chip_id, set_label, outroot, clean, "new", 0)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}

static bool definebyqueryMode(pxConfig *config)
{
    psError(PS_ERR_PROGRAMMING, true, "-definebyquery is not implemented yet");
    return false;
}

static bool updaterunMode(pxConfig *config)
{
    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-dist_id", "dist_id", "==");
    PXOPT_COPY_STR(config->args, where, "-stage", "stage", "==");;
    PXOPT_COPY_STR(config->args, where, "-state", "state", "==");
    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");

    if (!psListLength(where->list)) {
        psFree(where);
        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
        return false;
    }

    PXOPT_LOOKUP_STR(state, config->args, "-set_state", false, false);
    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);

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

    psString query = psStringCopy("UPDATE distRun");

    if (state) {
        psStringAppend(&query, " SET state = '%s'", state);
    }

    if (label) {
        psStringAppend(&query, " SET label = '%s'", label);
    }

    if (code) {
        psStringAppend(&query, " SET fault = %d", code);
    }

    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringAppend(&query, " WHERE %s", whereClause);
    psFree(whereClause);
    psFree(where);

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

    return true;
}

static bool revertrunMode(pxConfig *config)
{
    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-dist_id", "distRun.dist_id", "==");
    PXOPT_COPY_STR(config->args, where, "-stage", "stage", "==");;
    PXOPT_COPY_S64(config->args, where, "-stage_id", "stage_id", "==");
    PXOPT_COPY_STR(config->args, where, "-state", "state", "==");
    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
    PXOPT_COPY_S16(config->args, where, "-code", "distComponent.fault", "==");

    // It might be useful to be able to query by the parameters of the underlying runs

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

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

    // Update state to 'new'
    int numUpdated;                     // Number updated
    {
        psString query = pxDataGet("disttool_revertrun_update.sql");
        if (!query) {
            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

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

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

        numUpdated = psDBAffectedRows(config->dbh);

#ifdef notdef
        // don't need this. distRun.state may still be in 'new' state
        if (numUpdated < 1) {
            psError(PS_ERR_UNKNOWN, false, "should have affected at least 1 row");
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }
#endif
    }

    psLogMsg("disttool", PS_LOG_INFO, "Updated %d dist runs", numUpdated);

    // Delete product
    int numDeleted;                     // Number deleted
    {
        psString query = pxDataGet("disttool_revertrun_delete.sql");
        if (!query) {
            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            return false;
        }

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

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

        numDeleted = psDBAffectedRows(config->dbh);
    }

    psLogMsg("disttool", PS_LOG_INFO, "Deleted %d distComponents", numDeleted);

    psFree(where);

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

    return true;
}

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

    psMetadata *where = psMetadataAlloc();
    PXOPT_COPY_S64(config->args, where, "-dist_id", "dist_id", "==");
    PXOPT_COPY_STR(config->args, where, "-stage", "stage", "==");
    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");

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

    // look for "inputs" that need to processed
    psString query = pxDataGet("disttool_pendingcomponent.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

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

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

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

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

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

    psFree(output);

    return true;
}

static bool addprocessedcomponentMode(pxConfig *config)
{

    // required values
    PXOPT_LOOKUP_S64(dist_id, config->args, "-dist_id", true, false);
    PXOPT_LOOKUP_STR(component, config->args, "-component", true, false);

    // unless fault code is set require filename, bytes, and md5sum
    bool require_fileinfo = false;
    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
    if (!code) {
        require_fileinfo = true;
    }
    PXOPT_LOOKUP_S32(bytes, config->args, "-bytes", require_fileinfo, false);
    PXOPT_LOOKUP_STR(md5sum, config->args, "-md5sum", require_fileinfo, false);
    PXOPT_LOOKUP_STR(name, config->args, "-name", require_fileinfo, false);

    if (!distComponentInsert(config->dbh, dist_id, component, bytes, md5sum, "full", name, code)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    return true;
}

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

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

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

    // look for "inputs" that need to processed
    psString query = pxDataGet("disttool_toadvance.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

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

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

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

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

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

    psFree(output);

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

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

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

    // look for "inputs" that need to processed
    psString query = pxDataGet("disttool_processedcomponent.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

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

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

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

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

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

    psFree(output);

    return true;
}
