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

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

#include "ppMerge.h"
#include "ppMergeOptions.h"

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ppMergeOptions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Free function
static void mergeOptionsFree(ppMergeOptions *options // Options to free
    )
{
    psFree(options->format);
    psFree(options->combine);
}

// Allocator
ppMergeOptions *ppMergeOptionsAlloc(void)
{
    ppMergeOptions *options = psAlloc(sizeof(ppMergeOptions)); // The options, to return
    psMemSetDeallocator(options, (psFreeFunc)mergeOptionsFree);

    options->format = NULL;
    options->rows = 0;
    options->minElectrons = NAN;
    options->zero = false;
    options->scale = false;
    options->darktime = false;
    options->fringe = false;
    options->shutter = false;
    options->mask = false;
    options->sample = 1;
    options->mean = PS_STAT_SAMPLE_MEDIAN;
    options->stdev = PS_STAT_SAMPLE_STDEV;
    options->fringeNum = 100;
    options->fringeSize = 10;
    options->fringeSmoothX = 5;
    options->fringeSmoothY = 5;
    options->shutterSize = 10;
    options->shutterIter = 2;
    options->shutterRej = 3.0;
    options->maskSuspect = 5.0;
    options->maskBad = 10.0;
    options->onOff = 0;
    options->combine = pmCombineParamsAlloc(PS_STAT_SAMPLE_MEAN);
    options->combine->rej = 3.0;
    options->combine->iter = 1;
    options->combine->fracHigh = 0.0;
    options->combine->fracLow = 0.0;
    options->combine->nKeep = 1;
    options->combine->maskVal = 0xff;
    options->combine->weights = false;
    options->satMask = 0x00;
    options->badMask = 0x00;
    return options;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ppMergeOptionsParse
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Parse a recipe option according to its type
#define OPTION_PARSE(OPTION,MD,NAME,TYPE) \
{ \
    psMetadataItem *item = psMetadataLookup(MD, NAME); \
    if (item) { \
        OPTION = psMetadataItemParse##TYPE(item); \
    } else { \
        psWarning("Recipe option %s isn't specified; using default.\n", NAME); \
    } \
}

// Parse a statistic
static psStatsOptions parseStat(psMetadata *source, // Source of the statistics option
                                const char *name // Name of the statistics option
    )
{
    bool mdok = true;                   // Status of MD lookup
    const char *stat = psMetadataLookupStr(&mdok, source, name);  // The statistic string
    if (!mdok || !stat || strlen(stat) == 0) {
        return 0;
    }
    return psStatsOptionFromString(stat);
}

// Parse the options
ppMergeOptions *ppMergeOptionsParse(pmConfig *config // Configuration
    )
{
    ppMergeOptions *options = ppMergeOptionsAlloc(); // The merge options

    // We need to work out the camera before we can get the recipe.  Take the first input and inspect it.
    if (!config->camera) {
        psArray *filenames = psMetadataLookupPtr(NULL, config->arguments, "INPUT"); // The input file names
        psString resolved = pmConfigConvertFilename(filenames->data[0], config, false); // Resolved file name
        psFits *inFile = psFitsOpen(resolved, "r"); // The FITS file to read
        if (!inFile) {
            psError(PS_ERR_IO, false, "Unable to open input file %s to determine camera.\n", resolved);
            psFree(resolved);
            psFree(config);
            exit(EXIT_FAILURE);
        }
        psFree(resolved);
        psMetadata *header = psFitsReadHeader(NULL, inFile); // The FITS (primary) header
        psFitsClose(inFile);

        options->format = pmConfigCameraFormatFromHeader(config, header, true);
        psFree(header);
        if (!options->format) {
            psLogMsg(__func__, PS_LOG_WARN, "Unable to identify camera format for input file %s\n",
                     (char *)filenames->data[0]);
            exit(EXIT_FAILURE);
        }
    }

    // we must have the recipes by this point
    assert (config->recipes);

    // Now we can read the recipe
    bool mdok = true;                   // Status of MD lookup
    psMetadata *recipe = psMetadataLookupMetadata(&mdok, config->recipes, PPMERGE_RECIPE); // Recipe information
    if (!mdok || !recipe) {
        psError(PS_ERR_IO, true, "Unable to find recipe %s", PPMERGE_RECIPE);
        exit(EXIT_FAILURE);
    }

    // First, deal with the recipe.  These are parameters that will typically be constant for a camera.
    OPTION_PARSE(options->rows,              recipe, "ROWS",           S32);
    OPTION_PARSE(options->minElectrons,      recipe, "ELECTRONS",      F32);
    OPTION_PARSE(options->sample,            recipe, "SAMPLE",         S32);
    OPTION_PARSE(options->combine->rej,      recipe, "REJ",            F32);
    OPTION_PARSE(options->combine->iter,     recipe, "ITER",           S32);
    OPTION_PARSE(options->combine->fracHigh, recipe, "FRACHIGH",       F32);
    OPTION_PARSE(options->combine->fracLow,  recipe, "FRACLOW",        F32);
    OPTION_PARSE(options->combine->nKeep,    recipe, "NKEEP",          S32);
    OPTION_PARSE(options->combine->weights,  recipe, "WEIGHTS",        Bool);
    OPTION_PARSE(options->fringeNum,         recipe, "FRINGE.NUM",     S32);
    OPTION_PARSE(options->fringeSize,        recipe, "FRINGE.SIZE",    S32);
    OPTION_PARSE(options->fringeSmoothX,     recipe, "FRINGE.XSMOOTH", S32);
    OPTION_PARSE(options->fringeSmoothY,     recipe, "FRINGE.YSMOOTH", S32);
    OPTION_PARSE(options->shutterSize,       recipe, "SHUTTER.SIZE",   S32);
    OPTION_PARSE(options->shutterIter,       recipe, "SHUTTER.ITER",   S32);
    OPTION_PARSE(options->shutterRej,        recipe, "SHUTTER.REJECT", F32);
    OPTION_PARSE(options->maskSuspect,       recipe, "MASK.SUSPECT",   F32);
    OPTION_PARSE(options->maskBad,           recipe, "MASK.BAD",       F32);

    const char *masks = psMetadataLookupStr(NULL, recipe, "MASKVAL");
    options->combine->maskVal = pmConfigMask(masks, config);

    options->combine->combine = parseStat(recipe, "COMBINE");
    options->mean             = parseStat(recipe, "MEAN");
    options->stdev            = parseStat(recipe, "STDEV");

    // Now the command-line options.  These are parameters that depend on what type of frame is being combined

    // Set options based on the type of calibration frame
    const char *type = psMetadataLookupStr(NULL, config->arguments, "-type"); // The type of calibration frame
    if (strlen(type) > 0) {
        if (strcasecmp(type, "BIAS") == 0) {
            options->zero = false;
            options->scale = false;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "DARK") == 0) {
            options->zero = false;
            options->scale = false;
            options->darktime = true;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "FLAT") == 0) {
            options->zero = false;
            options->scale = true;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "SKYFLAT") == 0) {
            options->zero = false;
            options->scale = true;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "DOMEFLAT") == 0) {
            options->zero = false;
            options->scale = true;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "FRINGE") == 0) {
            options->zero = true;
            options->scale = true;
            options->darktime = false;
            options->fringe = true;
            options->shutter = false;
            options->mask = false;
        } else if (strcasecmp(type, "SHUTTER") == 0) {
            options->zero = false;
            options->scale = false;
            options->darktime = false;
            options->fringe = false;
            options->shutter = true;
            options->mask = false;
        } else if (strcasecmp(type, "MASK") == 0) {
            options->zero = false;
            options->scale = false;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = true;
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "Unrecognised image type: %s --- assuming BIAS.\n", type);
            options->zero = false;
            options->scale = false;
            options->darktime = false;
            options->fringe = false;
            options->shutter = false;
            options->mask = false;
        }
    } else {
        psLogMsg(__func__, PS_LOG_WARN, "No calibration type specified; assuming BIAS.\n");
        options->zero = false;
        options->scale = false;
        options->darktime = false;
        options->fringe = false;
        options->shutter = false;
        options->mask = false;
    }

#if 0
    // Or you can set them individually
    OPTION_PARSE(options->zero,     config->arguments, "-zero",    Bool);
    OPTION_PARSE(options->scale,    config->arguments, "-scale",   Bool);
    OPTION_PARSE(options->darktime, config->arguments, "-exptime", Bool);
#endif

    // Number of on/off images
    OPTION_PARSE(options->onOff, config->arguments, "-onoff", S32);

    // Masking options
    options->satMask = pmConfigMask("SAT", config);
    options->badMask = pmConfigMask("BAD", config);

    return options;
}
