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

#include <stdlib.h>
#include <ippdb.h>
#include <string.h>

#include "pxtools.h"
#include "pxcam.h"

bool pxcamSetSearchArgs (psMetadata *md) {

    psMetadataAddS64(md,  PS_LIST_TAIL, "-chip_id",            0, "search by chip_id", 0);
    psMetadataAddS64(md,  PS_LIST_TAIL, "-exp_id",             0, "search by exp_id", 0);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_name",           0, "search by exp_name", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-inst",               0, "search for camera", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-telescope",          0, "search for telescope", NULL);
    psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_begin",      0, "search for exposures by time (>=)", NULL);
    psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_end",        0, "search for exposures by time (<)", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_tag",            0, "search by exp_tag", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_type",           0, "search by exp_type", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-comment",            0, "search by comment", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-filelevel",          0, "search by filelevel", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-reduction",          0, "search by reduction class", NULL);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-filter",             0, "search for filter", NULL);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_min",        0, "define min airmass", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_max",        0, "define max airmass", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_min",             0, "define min", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_max",             0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_min",           0, "define min", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_max",           0, "define max", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_min",       0, "define min", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_max",       0, "define max", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_min", 0, "define max fraction of saturated pixels", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_max", 0, "define min fraction of saturated pixels", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_min",             0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_max",             0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_min",       0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_max",       0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_min",  0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_max",  0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_min",            0, "define min", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_max",            0, "define max", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-az_min",             0, "define min", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-az_max",             0, "define max", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_min",       0, "define min ccd tempature", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_max",       0, "define max ccd tempature", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_min",         0, "define min rotator position angle", NAN);
    psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_max",         0, "define max rotator position angle", NAN);
    psMetadataAddStr(md,  PS_LIST_TAIL, "-object",             0, "search by exposure object", NULL);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-solang_min",         0, "define min solar angle", NAN);
    psMetadataAddF32(md,  PS_LIST_TAIL, "-solang_max",         0, "define max solar angle", NAN);

    return true;
}

bool pxcamGetSearchArgs (pxConfig *config, psMetadata *where) {

    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
    PXOPT_COPY_S64(config->args, where, "-exp_id", "rawExp.exp_id", "==");
    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, "-comment", "rawExp.comment", "LIKE");
    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", "<");

    return true;
}

bool pxcamRunSetState(pxConfig *config, psS64 cam_id, const char *state)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    PS_ASSERT_PTR_NON_NULL(state, false);

    // check that state is a valid string value
    if (!pxIsValidState(state)) {
        psError(PS_ERR_UNKNOWN, false,
                "invalid camRun state: %s", state);
        return false;
    }

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

    return true;
}


bool pxcamRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    PS_ASSERT_PTR_NON_NULL(state, false);

    // check that state is a valid string value
    if (!pxIsValidState(state)) {
        psError(PS_ERR_UNKNOWN, false,
                "invalid chipRun state: %s", state);
        return false;
    }

    psString query = psStringCopy("UPDATE camRun JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET camRun.state = '%s'");

    if (where) {
        psString whereClause = psDBGenerateWhereSQL(where, NULL);
        psStringAppend(&query, " %s", whereClause);
        psFree(whereClause);
    }

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

    psFree(query);

    return true;
}


bool pxcamRunSetLabel(pxConfig *config, psS64 cam_id, const char *label)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    // note label == NULL should be explicitly allowed

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

    return true;
}

bool pxcamRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    // note label == NULL should be explicitly allowed

    psString query = psStringCopy("UPDATE camRun JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET camRun.label = '%s'");

    if (where) {
        psString whereClause = psDBGenerateWhereSQL(where, NULL);
        psStringAppend(&query, " %s", whereClause);
        psFree(whereClause);
    }

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

    psFree(query);

    return true;
}


bool pxcamQueueByChipID(pxConfig *config,
                    psS64 chip_id,
                    char *workdir,
                    char *label,
                    char *recipe,
                    char *expgroup,
                    char *dvodb,
                    char *tess_id,
                    char *end_stage)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    // load the SQL to enqueue our exp_ids from disk once
    static psString query = NULL;
    if (!query) {
        query = pxDataGet("camtool_queue_chip_id.sql");
        psMemSetPersistent(query, true);
        if (!query) {
            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
            return false;
        }
    }

    // queue the exp
    // XXX chip_id is being cast here work around psS64 have a different type
    // different on 32/64
    if (!p_psDBRunQuery(config->dbh, query,
                "new", // state
                workdir  ? workdir  : "NULL",
                "dirty", //workdir_state
                label    ? label    : "NULL",
                recipe   ? recipe   : "NULL",
                expgroup ? expgroup : "NULL",
                dvodb    ? dvodb    : "NULL",
                tess_id  ? tess_id  : "NULL",
                end_stage ? end_stage : "NULL",
                (long long)chip_id
    )) {
        psError(PS_ERR_UNKNOWN, false, "database error");
        return false;
    }

    // just to be safe, we should have changed at least one row
    if (psDBAffectedRows(config->dbh) < 1) {
        psError(PS_ERR_UNKNOWN, false,
                "no rows affected - should have changed at least one row");
        return false;
    }

    return true;
}
