#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);

    // actions which ppImage should perform
    options->doMaskBuild     = false;	// Build internal mask
    options->doWeightBuild   = false;	// Build internal weight
    options->doMask          = false;	// Mask bad pixels
    options->doNonLin        = false;	// Non-linearity correction
    options->doOverscan      = false;	// Overscan subtraction
    options->doBias          = false;	// Bias subtraction
    options->doDark          = false;	// Dark 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 (per-chip)
    options->doAstromMosaic  = false;	// Astrometry (full-mosaic)
    options->doStats         = false;	// Measure and save image statistics

    // output files requested
    options->BaseFITS        = false;	// create output image
    options->BaseMaskFITS    = false;	// create output mask image
    options->BaseWeightFITS  = false;	// create output weight image

    options->ChipFITS        = false;	// create output chip-mosaic image
    options->ChipMaskFITS    = false;	// create output chip-mosaic mask image
    options->ChipWeightFITS  = false;	// create output chip-mosaic weight image

    options->FPA1FITS        = false;	// create fpa-mosaic binned image (scale 1)
    options->FPA2FITS        = false;	// create fpa-mosaic binned image (scale 2)
    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)

    // default flags for various activities
    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

    // 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

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

    // binning parameters
    options->xBin1 	     = 16;	// x-binning, scale 1
    options->yBin1 	     = 16;	// y-binning, scale 1
    options->xBin2 	     = 16;	// x-binning, scale 2
    options->yBin2 	     = 16;	// y-binning, scale 2

    // Fringe defaults
    options->fringeRej       = NAN;	// Fringe rejection limit
    options->fringeIter      = 0;	// Fringe iterations
    options->fringeKeep      = 1.0;	// Fringe keep fraction

    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);
        }

        int boxcar = psMetadataLookupS32(NULL, recipe, "OVERSCAN.BOXCAR");
        float gauss = psMetadataLookupF32(NULL, recipe, "OVERSCAN.GAUSS");

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

    // for these images, even if not required otherwise
    options->doMaskBuild = psMetadataLookupBool(NULL, recipe, "MASK.BUILD");
    options->doWeightBuild = psMetadataLookupBool(NULL, recipe, "WEIGHT.BUILD");

    // 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);
    // XXX should it be an error for these to not exist?

    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");

    options->doStats = false;
    char *statsName = psMetadataLookupStr(&status, config->arguments, "STATS"); // Filename for statistics
    if (statsName) {
	options->doStats = true;
    }

    // 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->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;
    }

    // even if not requested explicitly, if any of these are set, build an internal mask and weight:
    if (options->doMask || options->doShutter || options->doFlat || options->doPhotom) {
	options->doMaskBuild = true;
	options->doWeightBuild = true;
    }

    options->BaseFITS       = psMetadataLookupBool(NULL, recipe, "BASE.FITS");
    options->BaseMaskFITS   = psMetadataLookupBool(NULL, recipe, "BASE.MASK.FITS");
    options->BaseWeightFITS = psMetadataLookupBool(NULL, recipe, "BASE.WEIGHT.FITS");

    options->ChipFITS       = psMetadataLookupBool(NULL, recipe, "CHIP.FITS");
    options->ChipMaskFITS   = psMetadataLookupBool(NULL, recipe, "CHIP.MASK.FITS");
    options->ChipWeightFITS = psMetadataLookupBool(NULL, recipe, "CHIP.WEIGHT.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);
    }

    // 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;
}
