#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->doMaskSat       = false;   // mask saturated pixels
    options->doMaskLow       = false;   // mask low pixels
    options->doVarianceBuild = false;   // Build internal variance
    options->doMask          = false;   // Mask bad pixels
    options->doNonLin        = false;   // Non-linearity correction
    options->doOverscan      = false;   // Overscan subtraction
    options->doNoiseMap      = false;   // Apply Read Noise Map
    options->doBias          = false;   // Bias subtraction
    options->doDark          = false;   // Dark subtraction
    options->doRemnance      = false;   // Remnance masking
    options->doShutter       = false;   // Shutter correction
    options->doFlat          = false;   // Flat-field normalisation
    options->doPattern       = false;   // Pattern noise subtraction
    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
    options->checkCTE        = false;   // Measure pixel-based variance
    options->applyParity     = false;   // Apply Cell parities

    // output files requested
    options->BaseFITS        = false;   // create output image
    options->BaseMaskFITS    = false;   // create output mask image
    options->BaseVarianceFITS  = false;   // create output variance image

    options->ChipFITS        = false;   // create output chip-mosaic image
    options->ChipMaskFITS    = false;   // create output chip-mosaic mask image
    options->ChipVarianceFITS  = false;   // create output chip-mosaic variance 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 (used to skip / ignore pixels)
    options->satMask         = 0x00;    // Saturated pixels (supplied to pmReadoutGenerateMask)
    options->lowMask         = 0x00;    // out-of-bounds (low) pixels (supplied to pmReadoutGenerateMask)
    options->flatMask        = 0x00;    // Bad flat pixels (supplied to pmFlatField)
    options->darkMask        = 0x00;    // Bad dark pixels (supplied to pmDarkApply)
    options->blankMask       = 0x00;    // Blank (no data, cell gap) pixels (supplied to pmChipMosaic, pmFPAMosaic)
    options->markValue       = 0x00;    // A safe bit for internal marking

    // crosstalk options
    options->doCrosstalkMeasure = false;   // measure crosstalk
    options->doCrosstalkCorrect = false;   // correct crosstalk

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

    // Pattern noise values

    options->patternOrder    = 0;       // Polynomial order
    options->patternIter     = 0;       // Clipping iterations
    options->patternRej      = NAN;     // Clipping rejection threshold
    options->patternThresh   = NAN;     // Threshold for ignoring pixels
    options->patternMean     = PS_STAT_NONE; // Statistic for mean
    options->patternStdev    = PS_STAT_NONE; // Statistic for standard deviation

    // Remnance values
    options->remnanceSize    = 30;      // Size for remnance detection
    options->remnanceThresh  = 25.0;    // Threshold for remnance detection

    // per-class normalization source
    options->normClass       = NULL;    // per-class normalizations refer to this class

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

        options->overscan->constant = psMetadataLookupBool(NULL, recipe, "OVERSCAN.CONSTANT");
        options->overscan->value = psMetadataLookupF32(NULL, recipe, "OVERSCAN.VALUE");

        psFree(overscanStats);
    }

    // for these images, even if not required otherwise
    options->doMaskBuild = psMetadataLookupBool(NULL, recipe, "MASK.BUILD");
    options->doMaskSat   = psMetadataLookupBool(NULL, recipe, "MASK.SATURATED");
    options->doMaskLow   = psMetadataLookupBool(NULL, recipe, "MASK.LOW");
    options->doVarianceBuild = psMetadataLookupBool(NULL, recipe, "VARIANCE.BUILD");

    // Mask recipe options (note that mask bit values are set in ppImageSetMaskBits.c)
    options->doMask = psMetadataLookupBool(NULL, recipe, "MASK");

    // Mask recipe options (note that mask bit values are set in ppImageSetMaskBits.c)
    options->doCrosstalkMeasure = psMetadataLookupBool(NULL, recipe, "CROSSTALK.MEASURE");
    options->doCrosstalkCorrect = psMetadataLookupBool(NULL, recipe, "CROSSTALK.CORRECT");

    options->doNoiseMap = psMetadataLookupBool(NULL, recipe, "NOISEMAP");
    options->doBias = psMetadataLookupBool(NULL, recipe, "BIAS");
    options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
    options->doRemnance = psMetadataLookupBool(NULL, recipe, "REMNANCE");
    options->doFlat = psMetadataLookupBool(NULL, recipe, "FLAT");
    options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
    options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");
    options->doPattern = psMetadataLookupBool(NULL, recipe, "PATTERN");

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

    options->applyParity = psMetadataLookupBool(NULL, recipe, "APPLY.CELL.PARITY");

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

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

    options->ChipFITS       = psMetadataLookupBool(NULL, recipe, "CHIP.FITS");
    options->ChipMaskFITS   = psMetadataLookupBool(NULL, recipe, "CHIP.MASK.FITS");
    options->ChipVarianceFITS = psMetadataLookupBool(NULL, recipe, "CHIP.VARIANCE.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");
    options->doBG           = psMetadataLookupBool(NULL, recipe, "BACKGROUND");

    options->checkCTE       = psMetadataLookupBool(NULL, recipe, "CHECK.CTE");

    // even if not requested explicitly, if any of these are set, build an internal mask and variance:
    if (options->doNoiseMap || options->doBias || options->doOverscan || options->doDark || options->doShutter || options->doFlat ||
        options->doPhotom) {
        options->doMaskBuild = true;
        options->doVarianceBuild = true;
    } else if (options->doMask || options->doBG) {
        options->doMaskBuild = true;
    }

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

    // Pattern noise
    options->patternOrder = psMetadataLookupS32(NULL, recipe, "PATTERN.ORDER");
    options->patternIter = psMetadataLookupS32(NULL, recipe, "PATTERN.ITER");
    options->patternRej = psMetadataLookupF32(NULL, recipe, "PATTERN.REJ");
    options->patternThresh = psMetadataLookupF32(NULL, recipe, "PATTERN.THRESH");
    options->patternMean = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.MEAN"));
    options->patternStdev = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.STDEV"));

    // Remnance options
    options->remnanceSize = psMetadataLookupF32(NULL, recipe, "REMNANCE.SIZE");
    options->remnanceThresh = psMetadataLookupS32(NULL, recipe, "REMNANCE.THRESH");

    // per-class normalization source (just a reference; don't free)
    options->normClass = psMetadataLookupStr(NULL, recipe, "NORM.CLASS");

    return options;
}
