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

#include "ppImage.h"

static void imageOptionsFree(ppImageOptions *options)
{
    psFree(options->overscan);
    psFree(options->nonLinearData);
    psFree(options->nonLinearSource);
}

ppImageOptions *ppImageOptionsAlloc(void)
{
    ppImageOptions *options = psAlloc(sizeof(ppImageOptions));
    psMemSetDeallocator(options, (psFreeFunc)imageOptionsFree);

    // Initialise options
    options->overscan = NULL;
    options->nonLinearData = NULL;
    options->nonLinearSource = NULL;

    // default flags for various activities
    options->doMask     = false;        // Mask bad pixels
    options->maskValue  = 0x00;         // Default mask value
    options->satMask    = 0x00;         // Saturated pixels
    options->badMask    = 0x00;         // Bad pixels
    options->flatMask   = 0x00;         // Bad flat pixels
    options->blankMask  = 0x00;         // Blank (no data, cell gap) pixels

    options->doNonLin   = false;        // Non-linearity correction
    options->doBias     = false;        // Bias subtraction
    options->doDark     = false;        // Dark subtraction
    options->doOverscan = false;        // Overscan subtraction
    options->doShutter  = false;        // Shutter correction
    options->doFlat     = false;        // Flat-field normalisation
    options->doFringe   = false;        // Fringe subtraction
    options->doPhotom   = false;        // Source identification and photometry
    options->doAstromChip = false;      // Astrometry
    options->doAstromMosaic = false;    // Astrometry

    options->BaseFITS   = false;        // create binned image (scale 1)
    options->ChipFITS   = false;        // create binned image (scale 1)
    options->FPA1FITS   = false;        // create binned image (scale 1)
    options->FPA2FITS   = false;        // create binned image (scale 1)

    options->Bin1FITS   = false;        // create binned image (scale 1)
    options->Bin2FITS   = false;        // create binned image (scale 2)
    options->Bin1JPEG   = false;        // create jpeg of binned image (scale 1)
    options->Bin2JPEG   = false;        // create jpeg of binned image (scale 2)

    // Overscan defaults
    options->overscan      = NULL;      // Overscan options

    // Fringe defaults
    options->fringeRej = NAN;
    options->fringeIter = 0;
    options->fringeKeep = 1.0;

    // Non-linearity default options
    options->nonLinearType   = 0;       // Type of non-linearity data (vector, string or metadata)
    options->nonLinearData   = NULL;    // The non-linearity data
    options->nonLinearSource = NULL;    // If the non-linearity data is a menu, this provides the key

    return options;
}

ppImageOptions *ppImageOptionsParse(pmConfig *config)
{
    bool status;
    ppImageOptions *options = ppImageOptionsAlloc ();

    // select the recipe for this analysis
    bool mdStatus = false;              // Result of MD lookup
    psMetadata *recipe = psMetadataLookupMetadata(&mdStatus, config->recipes, RECIPE_NAME);
    if (! mdStatus || !recipe) {
        psLogMsg("ppImage", PS_LOG_ERROR, "Can't find recipe %s in the RECIPES.\n", RECIPE_NAME);
        exit(EXIT_FAILURE);
    }

    // Non-linearity recipe options
    if (psMetadataLookupBool(NULL, recipe, "NONLIN")) {
        psMetadataItem *dataItem = psMetadataLookup(recipe, "NONLIN.DATA");
        if (! dataItem) {
            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
                     "find NONLIN.DATA in recipe %s.", RECIPE_NAME);
            exit(EXIT_FAILURE);
        }

        options->doNonLin = true;
        options->nonLinearType = dataItem->type;
        options->nonLinearData = dataItem;

        switch (dataItem->type) {
            // No immediate action required
          case PS_DATA_VECTOR:
          case PS_DATA_STRING:
            break;

            // This is a menu; we need the key
          case PS_DATA_METADATA:
            {
                bool status;
                options->nonLinearSource = psMetadataLookupStr(&status, recipe, "NONLIN.SOURCE");
                if (! status || ! options->nonLinearSource) {
                    psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
                            "find NONLIN.SOURCE in recipe %s.", RECIPE_NAME);
                    exit(EXIT_FAILURE);
                }
            }
            break;
          default:
            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but "
                    "NONLIN.DATA is of invalid type in recipe %s.", RECIPE_NAME);
            exit(EXIT_FAILURE);
        }
    }

    // XXX PAP: The overscan stuff needs to be updated following the reworked API

    // Overscan recipe options
    // XXX EAM : we should abort on invalid options. default options?
    if (psMetadataLookupBool(NULL, recipe, "OVERSCAN")) {
        options->doOverscan = true;

        // Do the overscan as a single value?
        bool overscanSingle = psMetadataLookupBool(NULL, recipe, "OVERSCAN.SINGLE");

        // How do we fit it?
        pmFit overscanFit = PM_FIT_NONE; // Fit type for overscan
        int overscanOrder = 0;          // Order for overscan fit
        psString fit = psMetadataLookupStr(NULL, recipe, "OVERSCAN.FIT");
        if (! strcasecmp(fit, "POLYNOMIAL")) {
            overscanFit = PM_FIT_POLY_ORD;
            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
        } else if (! strcasecmp(fit, "CHEBYSHEV")) {
            overscanFit = PM_FIT_POLY_CHEBY;
            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
        } else if (! strcasecmp(fit, "SPLINE")) {
            overscanFit = PM_FIT_SPLINE;
        } else if (strcasecmp(fit, "NONE")) {
            psLogMsg(__func__, PS_LOG_WARN,
                     "OVERSCAN.FIT (%s) in recipe %s is not one of NONE, POLYNOMIAL, or SPLINE",
                     fit, RECIPE_NAME);
            exit(EXIT_FAILURE);
        }

        // What method do we use to measure the overscan statistics?
        // XXX allow user to specify psStats types by name
        psStats *overscanStats = NULL;  // Statistics for overscan
        psString stat = psMetadataLookupStr(NULL, recipe, "OVERSCAN.STAT");
        if (! strcasecmp(stat, "MEAN")) {
            // overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
        } else if (! strcasecmp(stat, "MEDIAN")) {
            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
        } else {
            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) in recipe %s is not one of MEAN or MEDIAN",
                              stat, RECIPE_NAME);
            exit(EXIT_FAILURE);
        }

        // Fill in the options
        options->overscan = pmOverscanOptionsAlloc(overscanSingle, overscanFit, overscanOrder, overscanStats);
        psFree(overscanStats);
    }

    // Mask recipe options
    options->doMask = psMetadataLookupBool(NULL, recipe, "MASK");
    const char *masks = psMetadataLookupStr(&status, recipe, "MASK.VALUE");
    if (status) {
        options->maskValue = pmConfigMask(masks, config);
    }
    options->satMask = pmConfigMask("SAT", config);
    options->badMask = pmConfigMask("BAD", config);
    options->flatMask = pmConfigMask("FLAT", config);
    options->blankMask = pmConfigMask("BLANK", config);

    options->doBias = psMetadataLookupBool(NULL, recipe, "BIAS");
    options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
    options->doFlat = psMetadataLookupBool(NULL, recipe, "FLAT");
    options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
    options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");

    // binned image options
    options->xBin1 = psMetadataLookupS32(&status, recipe, "BIN1.XBIN");
    if (!status) {
        psWarning("BIN1.XBIN not found in recipe: setting to default value.\n");
        options->xBin1 = 16;
    }
    options->yBin1 = psMetadataLookupS32(&status, recipe, "BIN1.YBIN");
    if (!status) {
        psWarning("BIN1.YBIN not found in recipe: setting to default value.\n");
        options->yBin1 = 16;
    }

    options->xBin2 = psMetadataLookupS32(&status, recipe, "BIN2.XBIN");
    if (!status) {
        psWarning("BIN2.XBIN not found in recipe: setting to default value.\n");
       options->xBin1 = 16;
    }
    options->yBin2 = psMetadataLookupS32(&status, recipe, "BIN2.YBIN");
    if (!status) {
        psWarning("BIN2.YBIN not found in recipe: setting to default value.\n");
        options->yBin1 = 16;
    }

    options->BaseFITS = psMetadataLookupBool(NULL, recipe, "BASE.FITS");
    options->ChipFITS = psMetadataLookupBool(NULL, recipe, "CHIP.FITS");
    options->FPA1FITS = psMetadataLookupBool(NULL, recipe, "FPA1.FITS");
    options->FPA2FITS = psMetadataLookupBool(NULL, recipe, "FPA2.FITS");

    options->Bin1FITS = psMetadataLookupBool(NULL, recipe, "BIN1.FITS");
    options->Bin1JPEG = psMetadataLookupBool(NULL, recipe, "BIN1.JPEG");
    options->Bin2FITS = psMetadataLookupBool(NULL, recipe, "BIN2.FITS");
    options->Bin2JPEG = psMetadataLookupBool(NULL, recipe, "BIN2.JPEG");

    options->doPhotom = psMetadataLookupBool(NULL, recipe, "PHOTOM");
    options->doAstromChip = psMetadataLookupBool(NULL, recipe, "ASTROM.CHIP");
    options->doAstromMosaic = psMetadataLookupBool(NULL, recipe, "ASTROM.MOSAIC");
    if ((options->doAstromChip || options->doAstromMosaic) && !options->doPhotom) {
        psLogMsg(__func__, PS_LOG_ERROR, "Invalid PPIMAGE options: cannot do ASTROMetry without PHOTOMetry");
        exit(EXIT_FAILURE);
    }

    options->doAddstar = psMetadataLookupBool(NULL, recipe, "ADDSTAR");
    if (options->doAddstar && !(options->doAstromChip || options->doAstromMosaic)) {
        psLogMsg(__func__, PS_LOG_ERROR, "Invalid PPIMAGE options: cannot Addstar without Astrometry");
        exit(EXIT_FAILURE);
    }

    // Fringe options
    options->fringeRej = psMetadataLookupF32(NULL, recipe, "FRINGE.REJ");
    options->fringeIter = psMetadataLookupS32(NULL, recipe, "FRINGE.ITER");
    options->fringeKeep = psMetadataLookupF32(NULL, recipe, "FRINGE.KEEP");

    return options;
}
