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

#include <stdio.h>
#include <string.h>
#include <pslib.h>
#include "pmConfig.h"
#include "pmConfigCommand.h"
#include "pmFPA.h"
#include "pmFPALevel.h"
#include "pmDetrendDB.h"
#include "psPipe.h"
#include "psIOBuffer.h"

// ************* detrend select functions **************

//
static void pmDetrendSelectOptionsFree (pmDetrendSelectOptions *options)
{

    if (!options)
        return;

    psFree (options->camera);

    return;
}

// define basic options for a new detrend database query
pmDetrendSelectOptions *pmDetrendSelectOptionsAlloc(const char *camera, psTime time, pmDetrendType type)
{

    pmDetrendSelectOptions *options = psAlloc(sizeof(pmDetrendSelectOptions));
    psMemSetDeallocator(options, (psFreeFunc) pmDetrendSelectOptionsFree);

    // basic options required by every query
    options->camera = psStringCopy (camera);
    options->time   = time;
    options->type   = type;

    // these other options depend on the type of detrend data
    options->filter = NULL;  //
    options->exptime = -1.0; // the undefined value (safe since exptime >= 0)
    options->airmass = -1.0; // the undefined value (safe since airmass >= 1)

    return options;
}

static void pmDetrendSelectResultsFree (pmDetrendSelectResults *results)
{

    if (!results)
        return;

    psFree (results->detID);

    return;
}

pmDetrendSelectResults *pmDetrendSelectResultsAlloc ()
{

    pmDetrendSelectResults *results = psAlloc(sizeof(pmDetrendSelectResults));
    psMemSetDeallocator(results, (psFreeFunc) pmDetrendSelectResultsFree);

    results->detID = NULL;
    results->level = PM_FPA_LEVEL_NONE;

    return results;
}

psString pmDetrendTypeToString (pmDetrendType type)
{

    # define DETREND_STRING_CASE(TTT) \
case PM_DETREND_TYPE_##TTT: \
    return psStringCopy (#TTT);

    switch (type) {
        DETREND_STRING_CASE (MASK);
        DETREND_STRING_CASE (BIAS);
        DETREND_STRING_CASE (DARK);
        DETREND_STRING_CASE (FLAT);
        DETREND_STRING_CASE (FLAT_CORRECTION);
        DETREND_STRING_CASE (SHUTTER);
        DETREND_STRING_CASE (FRINGE_IMAGE);
        DETREND_STRING_CASE (FRINGE_TABLE);
    default:
        return NULL;
    }
    return NULL;
}

// detselect -camera (camera) -time (time) -type (type) [others]
// returns: (type) (class) (exp_flag) DONE
pmDetrendSelectResults *pmDetrendSelect (const pmDetrendSelectOptions *options,
        const pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(options, NULL);
    PS_ASSERT_PTR_NON_NULL(config, NULL);
    PS_ASSERT_PTR_NON_NULL(config->site, NULL);

    int status, exit_status;
    psString line = NULL;
    char *time = psTimeToISO (&options->time);
    char *type = pmDetrendTypeToString (options->type);
    unsigned int nFail;

    pmDetrendSelectResults *results = pmDetrendSelectResultsAlloc();
    psStringAppend(&line, "detselect -search -inst %s -det_type %s -time %s", options->camera, type, time);

    if (options->filter) {
        psStringAppend(&line, " -filter %s", options->filter);
    }
    if (options->exptime > -1.0) {
        psStringAppend(&line, " -exp_time %f", options->exptime);
    }
    if (options->airmass > -1.0) {
        psStringAppend(&line, " -airmass %f", options->airmass);
    }

    pmConfigDatabaseCommand(&line, config);
    pmConfigTraceCommand(&line);
    psTrace("psModules.detrend", 5, "running %s", line);

    // use psPipe to exec the command, wait for response
    psIOBuffer *buffer = psIOBufferAlloc (512);
    psPipe *pipe = psPipeOpen (line);
    if (!pipe) {
        psError (PS_ERR_IO, false, "error calling command %s", line);
        goto failure;
    }

    status = psIOBufferReadEmpty (buffer, 100, pipe->fd_stdout);
    if (!status) {
        psError (PS_ERR_IO, false, "detselect is not responding");
        goto failure;
    }
    exit_status = psPipeClose (pipe);
    if (exit_status) {
        psError (PS_ERR_IO, false, "error running detselect");
        goto failure;
    }

    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);

    nFail = 0;
    psMetadata *answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
    if (!answer) {
        psError(PS_ERR_IO, false, "failed to parse response from detselect\n");
        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
        goto failure;
    }

    psMetadata *md = psMetadataLookupPtr (NULL, answer, "detExp");
    if (!md) {
        psError(PS_ERR_IO, false, "detselect response is missing 'detExp' Metadata\n");
        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
        goto failure;
    }

    bool mdstatus;
    results->level = 0;
    int detID = psMetadataLookupS32 (&mdstatus, md, "det_id");
    int iteration  = psMetadataLookupS32 (&mdstatus, md, "iteration");

    results->detID = NULL; // it should be NULL already from the Alloc above
    psStringAppend (&results->detID, " -det_id %d -iteration %d ", detID, iteration);

    psTrace("psModules.detrend", 5, "generated detID %s\n", results->detID);

    psFree (answer);
    psFree (buffer);
    psFree (pipe);
    psFree (line);
    psFree (time);
    psFree (type);
    return results;

failure:
    psFree (results);
    psFree (pipe);
    psFree (buffer);
    psFree (line);
    psFree (type);
    psFree (time);
    return NULL;
}

// ************* detrend file functions **************

// detselect -select -detID (detID) -classID (classID)
// returns: (detID) (classID) (filename) DONE
char *pmDetrendFile (const char *detID, const char *classID, const pmConfig *config)
{
    unsigned int nFail;

    PS_ASSERT_PTR_NON_NULL(detID, NULL);
    PS_ASSERT_PTR_NON_NULL(classID, NULL);

    bool status;
    psString line = NULL;
    psArray *array = NULL;

    // generate the detselect command
    psStringAppend (&line, "detselect -select %s -class_id %s", detID, classID);
    pmConfigDatabaseCommand(&line, config);
    pmConfigTraceCommand(&line);
    psTrace("psModules.detrend", 5, "running %s", line);

    // use psPipe to exec the command, wait for response
    psIOBuffer *buffer = psIOBufferAlloc (512);
    psPipe *pipe = psPipeOpen (line);
    if (!pipe) {
        psError (PS_ERR_IO, false, "error calling command %s", line);
        goto failure;
    }
    status = psIOBufferReadEmpty (buffer, 100, pipe->fd_stdout);
    if (!status) {
        psError (PS_ERR_IO, false, "detselect is not responding");
        goto failure;
    }
    status = psPipeClose (pipe);
    if (status) {
        psError (PS_ERR_IO, false, "error running detselect");
        goto failure;
    }

    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);

    psMetadata *answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
    if (!answer) {
        psError(PS_ERR_IO, false, "failed to parse response from detselect\n");
        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
        goto failure;
    }
    psMetadata *md = psMetadataLookupPtr (NULL, answer, "detNormalizedImfile");
    if (!md) {
        psError(PS_ERR_IO, false, "detselect response is missing 'detNormalizedImfile' Metadata\n");
        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
        goto failure;
    }

    char *result = psStringCopy (psMetadataLookupStr (NULL, md, "uri"));
    psTrace("psModules.detrend", 5, "detrend file: %s\n", result);

    psFree (answer);
    psFree (pipe);
    psFree (buffer);
    psFree (line);
    return result;

failure:
    psFree (array);
    psFree (pipe);
    psFree (buffer);
    psFree (line);
    return NULL;
}
