/*
 * detselect.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 <stdlib.h>

#include "pxtools.h"
#include "detselect.h"

static bool searchMode(pxConfig *config);
static bool selectMode(pxConfig *config);

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

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

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

    switch (config->mode) {
        MODECASE(DETSELECT_MODE_SEARCH,        searchMode);
        MODECASE(DETSELECT_MODE_SELECT,        selectMode);
        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 searchMode(pxConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    psMetadata *where = psMetadataAlloc();

    // airmass_min  < airmass  < airmass_max
    {
        bool status = false;
        psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
            psFree(where);
            return false;
        }

        if (!isnan(airmass)) {
            psMetadataAddF32(where, PS_LIST_TAIL, "airmass_min", 0, "<=", airmass);
            psMetadataAddF32(where, PS_LIST_TAIL, "airmass_max", 0, ">=", airmass);
        }
    }

    // exp_time_min < exp_time < exp_time_max
    {
        bool status = false;
        psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
            psFree(where);
            return false;
        }

        if (!isnan(exp_time)) {
            psMetadataAddF32(where, PS_LIST_TAIL, "exp_time_min", 0, "<=", exp_time);
            psMetadataAddF32(where, PS_LIST_TAIL, "exp_time_max", 0, ">=", exp_time);
        }
    }

    // ccd_temp_min < ccd_temp < ccd_temp_max
    {
        bool status = false;
        psF32 ccd_temp = psMetadataLookupF32(&status, config->args, "-ccd_temp");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp");
            psFree(where);
            return false;
        }

        if (!isnan(ccd_temp)) {
            psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp_min", 0, "<=", ccd_temp);
            psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp_max", 0, ">=", ccd_temp);
        }
    }
    
    {
        bool status = false;
        psF64 posang = psMetadataLookupF64(&status, config->args, "-posang");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang");
            psFree(where);
            return false;
        }

        if (!isnan(posang)) {
            psMetadataAddF32(where, PS_LIST_TAIL, "posang_min", 0, "<=", posang);
            psMetadataAddF32(where, PS_LIST_TAIL, "posang_max", 0, ">=", posang);
        }
    }

    // time_begin    < time     < time_end
    {
        bool status = false;
        psString timeStr = psMetadataLookupStr(&status, config->args, "-time");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time");
            psFree(where);
            return false;
        }

        if (timeStr) {
            psTime *time = psTimeFromISO(timeStr, PS_TIME_UTC);
            if (!time) {
                psError(PS_ERR_UNKNOWN, false, "error in time format %s", timeStr);
                psFree(where);
                return false;
            }

            // the == NULL tests invokes some psDB magic to make an OR
            // conditional query
            psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", 0, "<=", time);
            psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", PS_META_DUPLICATE_OK, "==", NULL);
            psMetadataAddTime(where, PS_LIST_TAIL, "time_end", 0, ">=", time);
            psMetadataAddTime(where, PS_LIST_TAIL, "time_end", PS_META_DUPLICATE_OK, "==", NULL);
            psFree(time);
        }
    }

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

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

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

    // XXX this needs to be more controlled: we should specifically choose the 
    // single detrend image which matches all criteria and has the latest 
    // insertion date
    psStringAppend(&query, " ORDER BY detRun.registered DESC LIMIT 1");

    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)) {
        // XXX check psError here
        psError(PS_ERR_UNKNOWN, false, "no detrend exposures 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;
        }
    }

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

    psFree(output);

    return true;
}

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

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


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

    psFree(output);

    return true;
}
