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

#include <stdio.h>
#include <string.h>
#include <pslib.h>

#include "pmConfig.h"
#include "pmConfigCommand.h"
#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPALevel.h"
#include "pmConfigCamera.h"
#include "pmDetrendDB.h"
#include "psPipe.h"
#include "psIOBuffer.h"

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

//
static void pmDetrendSelectOptionsFree(pmDetrendSelectOptions *options)
{

    if (!options) {
        return;
    }

    psFree(options->camera);
    psFree(options->filter);
    psFree(options->dettype);
    psFree(options->version);

    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->version  = NULL;
    options->dettype  = NULL;
    options->exptime  = 0.0;
    options->airmass  = 0.0;
    options->dettemp  = 0.0;
    options->twilight = 0.0;

    options->exptimeSet  = false; // not selected
    options->airmassSet  = false; // not selected
    options->dettempSet  = false; // not selected
    options->twilightSet = false; // not selected

    return options;
}

static void pmDetrendSelectResultsFree(pmDetrendSelectResults *results)
{

    if (!results) {
        return;
    }

    psFree(results->detID);
    psFree(results->level);

    return;
}

pmDetrendSelectResults *pmDetrendSelectResultsAlloc(void)
{

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

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

    return results;
}

psString pmDetrendTypeToString(pmDetrendType type)
{

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

    switch (type) {
        DETREND_STRING_CASE(NONE);
        DETREND_STRING_CASE(MASK);
        DETREND_STRING_CASE(BIAS);
        DETREND_STRING_CASE(DARK);
        DETREND_STRING_CASE(FLAT);
        DETREND_STRING_CASE(FLATCORR);
        DETREND_STRING_CASE(SHUTTER);
        DETREND_STRING_CASE(FRINGE);
        DETREND_STRING_CASE(ASTROM);
    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);

    int status, exit_status;
    psString line = NULL;
    psString time = psTimeToISO (&options->time);

    char *type = NULL;
    if (options->dettype) {
        type = psMemIncrRefCounter (options->dettype);
    } else {
        type = pmDetrendTypeToString (options->type);
    }
    unsigned int nFail;

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

    if (options->filter) {
        psStringAppend(&line, " -filter %s", options->filter);
    }
    if (options->version) {
        psStringAppend(&line, " -version %s", options->version);
    }
    if (options->exptimeSet) {
        psStringAppend(&line, " -exp_time %f", options->exptime);
    }
    if (options->airmassSet) {
        psStringAppend(&line, " -airmass %f", options->airmass);
    }
    if (options->dettempSet) {
        psStringAppend(&line, " -airmass %f", options->dettemp);
    }
    if (options->twilightSet) {
        psStringAppend(&line, " -airmass %f", options->twilight);
    }

    psIOBuffer *buffer = NULL;
    psPipe *pipe = NULL;
    psMetadata *answer = NULL;

    if (!pmConfigDatabaseCommand(&line, config)) {
        psError (PS_ERR_IO, false, "error building detrend command %s", line);
        goto failure;
    }

    if (!pmConfigTraceCommand(&line)) {
        psError (PS_ERR_IO, false, "error building detrend command %s", line);
        goto failure;
    }

    psTrace("psModules.detrend", 5, "running %s", line);

    // use psPipe to exec the command, wait for response
    buffer = psIOBufferAlloc (512);
    pipe = psPipeOpen (line);
    if (!pipe) {
        psError (PS_ERR_IO, false, "error calling command %s", line);
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
        goto failure;
    }

    status = psIOBufferReadEmpty (buffer, 2000, pipe->fd_stdout);
    if (!status) {
        psError (PS_ERR_IO, false, "detselect is not responding");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
        goto failure;
    }
    exit_status = psPipeClose (pipe);
    if (exit_status) {
        psError (PS_ERR_IO, false, "error running detselect");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
        goto failure;
    }

    if (!buffer->data || strlen(buffer->data) == 0) {
        psError(PS_ERR_IO, true, "Unable to find suitable detrend");
        psLogMsg("psModules.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
        goto failure;
    }

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

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

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

    bool mdstatus;
    int detID = psMetadataLookupS32 (&mdstatus, md, "det_id");
    if (!mdstatus) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find det_id in output from detselect.");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
        goto failure;
    }
    int iteration  = psMetadataLookupS32 (&mdstatus, md, "iteration");
    if (!mdstatus) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find iteration in output from detselect.");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
        goto failure;
    }
    psString fileLevel = psMetadataLookupStr(&mdstatus, md, "filelevel");
    if (!mdstatus || !fileLevel || strlen(fileLevel) == 0) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find filelevel in output from detselect.");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
        goto failure;
    }

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

    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 (answer);
    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);

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

    // generate the detselect command
    psStringAppend (&line, "detselect -select %s", detID);
    if (classID && strlen(classID) > 0) {
        psStringAppend(&line, " -class_id %s", 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;
    }

    // timeout somewhat longer than 2sec.  this could still be too short....
    status = psIOBufferReadEmpty (buffer, 2000, 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);

    if (!buffer->n) {
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
        psError(PS_ERR_IO, true, "no matching detrend data in database\n");
        goto failure;
    }

    psMetadata *answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
    if (!answer) {
        psError(PS_ERR_IO, false, "failed to parse response from detselect\n");
        psLogMsg ("psModules.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
        goto failure;
    }
    psMetadataItem *item = psMetadataLookup (answer, "detNormalizedImfile");
    if ((item->type == PS_DATA_METADATA_MULTI) && (item->data.list->n > 1)) {
        psError(PS_ERR_IO, false, "detselect returned too many files\n");
        goto failure;
    }

    psMetadata *md = psMetadataLookupPtr (NULL, answer, "detNormalizedImfile");
    if (!md) {
        psError(PS_ERR_IO, false, "detselect response is missing 'detNormalizedImfile' Metadata\n");
        psLogMsg ("psModules.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;
}
