/*
 * pztool.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 HAVE_CONFIG_H
#include <config.h>
#endif

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

#include "pxtools.h"
#include "pztool.h"

static bool seenMode(pxConfig *config);
static bool pendingExpMode(pxConfig *config);
static bool pendingImfileMode(pxConfig *config);
static bool copydoneMode(pxConfig *config);

static bool copydoneCompleteExp(pxConfig *config);

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


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

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

    switch (config->mode) {
        MODECASE(PZTOOL_MODE_SEEN, seenMode);
        MODECASE(PZTOOL_MODE_PENDINGEXP, pendingExpMode);
        MODECASE(PZTOOL_MODE_PENDINGIMFILE, pendingImfileMode);
        MODECASE(PZTOOL_MODE_COPYDONE, copydoneMode);
        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 seenMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

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

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

    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("pztool", 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;
        }
    }

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

    psFree(output);

    return true;
}

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

    psString query = psStringCopy(
            "SELECT"
            "   summitExp.*"
            " FROM pzPendingExp"
            " JOIN summitExp"
            "   USING(exp_name, camera, telescope)"
        );

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

    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("pztool", 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");
            psFree(output);
            return false;
        }
    }

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

    psFree(output);

    return true;
}

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

    psString query = psStringCopy(
            "SELECT"
            "   summitImfile.*"
            " FROM pzPendingImfile"
            " JOIN summitImfile"
            "   USING(exp_name, camera, telescope, class, class_id)"
        );

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

    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("pztool", 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");
            psFree(output);
            return false;
        }
    }

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

    psFree(output);

    return true;
}

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

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

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

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

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

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

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

    // need to know exp_name, camera, telescope, class, class_id (to find the
    // pzPendingImfile entry and the URI for the newImfile entry.

    // start a transaction so it's all rows or nothing
    if (!psDBTransaction(config->dbh)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }
  

    // cp the imfile into pzDoneImfile
    {
        char *query =
            "INSERT INTO pzDoneImfile"
            "   SElECT"
            "       pzPendingImfile.*,"
            "       '%s'" // uri of downloaded file
            "   FROM pzPendingImfile"
            "   WHERE"
            "       pzPendingImfile.exp_name = '%s'"
            "       AND pzPendingImfile.camera = '%s'"
            "       AND pzPendingImfile.telescope = '%s'"
            "       AND pzPendingImfile.class = '%s'"
            "       AND pzPendingImfile.class_id = '%s'";

        if (!p_psDBRunQuery(config->dbh, query, uri, exp_name, camera, telescope, class, class_id)) {
            // rollback
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "database error");
            return false;
        } 

        // sanity check: we should have inserted only one row
        psU64 affected = psDBAffectedRows(config->dbh);
        if (psDBAffectedRows(config->dbh) != 1) {
            // rollback
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
            return false;
        }
    }

    // removve the entry from pzPendingImfile
    {
        char *query =
            "DELETE FROM pzPendingImfile"
            "   WHERE"
            "       pzPendingImfile.exp_name = '%s'"
            "       AND pzPendingImfile.camera = '%s'"
            "       AND pzPendingImfile.telescope = '%s'"
            "       AND pzPendingImfile.class = '%s'"
            "       AND pzPendingImfile.class_id = '%s'";

        if (!p_psDBRunQuery(config->dbh, query, exp_name, camera, telescope, class, class_id)) {
            // rollback
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "database error");
            return false;
        } 

        // sanity check: we should have removed only one row
        psU64 affected = psDBAffectedRows(config->dbh);
        if (psDBAffectedRows(config->dbh) != 1) {
            // rollback
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
            return false;
        }
    }

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

    if (!copydoneCompleteExp(config)) {
        psError(PS_ERR_UNKNOWN, false, "copydoneCompleteExp() failed");
        return false;
    }

    return true;
}

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

    // find all exposures that have had all of their imfiles downloaded but do
    // not appear in newExp
    psString query = pxDataGet("pztool_find_completed_exp.sql");
    if (!query) {
        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
        return false;
    }

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

    // start a transaction so all newExp's can start off with a state of run
    if (!psDBTransaction(config->dbh)) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        psFree(output);
        return false;
    }


   for (long i = 0; i < psArrayLength(output); i++) {
        psMetadata *row = output->data[i];

        pzDoneExpRow *doneExp = pzDoneExpObjectFromMetadata(row);

        if (!newExpInsert(config->dbh,
                    0x0,                // exp_id
                    doneExp->exp_name,  // tmp_exp_name
                    doneExp->camera,    // tmp_camera
                    doneExp->telescope, // tmp_telescope
                    "run",              // state
                    NULL,               // workdir
                    "dirty",            // workdir state
                    NULL                // reduction class
                )
        ) {
            // rollback
            if (!psDBRollback(config->dbh)) {
                psError(PS_ERR_UNKNOWN, false, "database error");
            }
            psError(PS_ERR_UNKNOWN, false, "database error");
            psFree(doneExp);
            psFree(output);
            return false;
        }

        psS64 exp_id = psDBLastInsertID(config->dbh);

        // insert new pzDoneImfile into newImfile
        {
            char *query =
                "INSERT INTO newImfile"
                "   SElECT"
                "       %" PRId64 "," // exp_id
                "       pzDoneImfile.class_id," // tmp_class_id
                "       pzDoneImfile.uri" // uri
                "   FROM pzDoneImfile"
                "   WHERE"
                "       pzDoneImfile.exp_name = '%s'"
                "       AND pzDoneImfile.camera = '%s'"
                "       AND pzDoneImfile.telescope = '%s'";

            if (!p_psDBRunQuery(config->dbh, query, exp_id, doneExp->exp_name, doneExp->camera, doneExp->telescope)) {
                // rollback
                if (!psDBRollback(config->dbh)) {
                    psError(PS_ERR_UNKNOWN, false, "database error");
                }
                psError(PS_ERR_UNKNOWN, false, "database error");
                psFree(doneExp);
                psFree(output);
                return false;
            } 

            // sanity check: we should have inserted only one row
            psU64 affected = psDBAffectedRows(config->dbh);
            if (psDBAffectedRows(config->dbh) != 1) {
                // rollback
                if (!psDBRollback(config->dbh)) {
                    psError(PS_ERR_UNKNOWN, false, "database error");
                }
                psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
                psFree(doneExp);
                psFree(output);
                return false;
            }
        }

        psFree(doneExp);
    }

    psFree(output);

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

    return true;
}
