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

# include "ppImage.h"

// XXX clean up error checks: return NULL, not psAbort
ppImageOptions *ppImageParseCamera (pmConfig *config) {

    bool status = false;

    // the input image defines the camera, and all recipes and options the follow
    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT");
    if (!status || !input) {
        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT");
        return NULL;
    }

#if 0
    // the input image defines the camera, and all recipes and options the follow
    pmFPAfile *inputMask = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT.MASK", "INPUT.MASK");
    if (!status || !inputMask) {
        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT.MASK");
        return NULL;
    }

    // the input image defines the camera, and all recipes and options the follow
    pmFPAfile *inputWeight = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT.WEIGHT", "INPUT.WEIGHT");
    if (!status || !inputWeight) {
        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT.WEIGHT");
        return NULL;
    }
#endif

    // add recipe options supplied on command line
    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, RECIPE_NAME);

    // parse the options from the metadata format to the ppImageOptions structure
    ppImageOptions *options = ppImageOptionsParse (config);

    // the following are defined from the argument list, if given,
    // otherwise they revert to the config information
    // not all input or output images are used in a given recipe
    if (options->doBias) {
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.BIAS", "BIAS");
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.BIAS");
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.BIAS", input->fpa, PM_DETREND_TYPE_BIAS);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a bias image source");
            return NULL;
        }
    }
    if (options->doDark) {
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.DARK", "DARK");
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.DARK");
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.DARK", input->fpa, PM_DETREND_TYPE_DARK);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a dark image source");
            return NULL;
        }
    }
    if (options->doMask) {
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.MASK", "MASK");
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.MASK");
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.MASK", input->fpa, PM_DETREND_TYPE_MASK);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a mask image source");
            return NULL;
        }
    }
    if (options->doShutter) {
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.SHUTTER", "SHUTTER");
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.SHUTTER");
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.SHUTTER", input->fpa, PM_DETREND_TYPE_SHUTTER);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a shutter image source");
            return NULL;
        }
    }

    if (options->doFlat) {
        // if we fail to find the item in Args, we try again in Conf, then DetDB
        // we need to handle the errors and catch serious errors, as opposed to
        // data-not-found errors
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.FLAT", "FLAT");
        psErrorClear();
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.FLAT");
        psErrorClear();
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.FLAT", input->fpa, PM_DETREND_TYPE_FLAT);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a flat image source");
            return NULL;
        }
    }

    // fringe frame are only applied for a subset of the filters.
    // if the filter is one of those identified by a FRINGE.FILTERS metadata entry
    // in ppImage.config, apply the fringe frame
    if (options->doFringe) {
        // determine filter from the concepts
        const char *filter = psMetadataLookupStr(&status, input->fpa->concepts, "FPA.FILTER");
        if (!status || !filter || strlen(filter) == 0) {
            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FPA.FILTER.\n");
            return NULL;
        }

        // select FRINGE.FILTERS from the recipe and test if the filter matches
        // is the mdi saved with psList or string?
        psMetadataItem *mdi = psMetadataLookup (recipe, "FRINGE.FILTERS");
        if (mdi == NULL) {
            // no valid filters for fringe data for this camera
            options->doFringe = false;
            goto skip_fringe;
        }
        // place entry on a list regardless of type
        psList *filters = NULL;
        if (mdi->type == PS_DATA_STRING) {
            filters = psListAlloc(NULL);
            psListAdd (filters, PS_LIST_HEAD, mdi);
        } else if (mdi->type == PS_DATA_METADATA_MULTI) {
            filters = psMemIncrRefCounter(mdi->data.list);
        } else {
            psAbort("invalid type for FRINGE.FILTERS");
        }

        // search through list to find the current filter
        psListIterator *iter = psListIteratorAlloc (filters, PS_LIST_HEAD, FALSE);
        options->doFringe = false;
        for (int i = 0; !options->doFringe && (i < filters->n); i++) {
            psMetadataItem *item = psListGetAndIncrement (iter);
            char *validFilter = item->data.V;
            if (strcmp (validFilter, filter)) continue;
            options->doFringe = true;
        }
        psFree(iter);
        psFree(filters);
    }
skip_fringe:
    if (options->doFringe) {
        bool status = false;
        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.FRINGE", "FRINGE");
        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.FRINGE");
        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.FRINGE", input->fpa, PM_DETREND_TYPE_FRINGE_IMAGE);
        if (!status) {
            psError (PS_ERR_IO, false, "can't find a fringe image source");
            return NULL;
        }
    }

    // the following files are output targets
    pmFPAfile *output = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT");
    if (!output) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *outMask = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.MASK");
    if (!outMask) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.MASK"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *outWeight = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.WEIGHT");
    if (!outWeight) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.WEIGHT"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *byChip = pmFPAfileDefineChipMosaic(config, input->fpa, "PPIMAGE.OUTPUT.CHIP");
    if (!byChip) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.CHIP"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *byFPA1 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA1");
    if (!byFPA1) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA1"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *byFPA2 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA2");
    if (!byFPA2) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA2"));
        psFree(options);
        return NULL;
    }

    // save any of these files?
    output->save = options->BaseFITS;
    byChip->save = options->ChipFITS;
    byFPA1->save = options->FPA1FITS;
    byFPA2->save = options->FPA2FITS;

    // output is used as a carrier: input to byChip
    output->freeLevel = PM_FPA_LEVEL_CHIP;

    // define the binned target files (which may just be carriers for some camera configurations)
    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
    if (!bin1) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN1"));
        psFree(options);
        return NULL;
    }

    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
    if (!bin2) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN2"));
        psFree(options);
        return NULL;
    }

    // bin1 and bin2 are used as carriers: input for byFPA1, byFPA2
    bin1->freeLevel = PM_FPA_LEVEL_FPA;
    bin2->freeLevel = PM_FPA_LEVEL_FPA;

    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
    if (!jpg1) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG1"));
        psFree(options);
        return NULL;
    }
    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
    if (!jpg2) {
        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG2"));
        psFree(options);
        return NULL;
    }

    // XXX we could potentially not define these pmFPAfiles if no output is requested...
    bin1->save = options->Bin1FITS;
    bin2->save = options->Bin2FITS;
    jpg1->save = options->Bin1JPEG;
    jpg2->save = options->Bin2JPEG;

    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
    psArray *chips = psStringSplitArray (chipLine, ",", false);
    if (chips->n > 0) {
        pmFPASelectChip (input->fpa, -1, true); // deselect all chips
        for (int i = 0; i < chips->n; i++) {
            int chipNum = atoi(chips->data[i]);
            if (! pmFPASelectChip(input->fpa, chipNum, false)) {
                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
                return false;
            }
        }
    }
    psFree (chips);

    if (psMetadataLookupBool(NULL, recipe, "INPUT_IS_FRINGE")) {
        // It's a fringe file, so change the file type
        input->type = PM_FPA_FILE_FRINGE;
        output->type = PM_FPA_FILE_FRINGE;
    }


#if 0
    // Get a look inside all the files.
    psMetadataIterator *filesIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *item;               // Item from iteration
    fprintf(stderr, "Files:\n");
    while ((item = psMetadataGetAndIncrement(filesIter))) {
        pmFPAfile *file = item->data.V; // File of interest
        fprintf(stderr, "%s: %p %p %p (%p) %p\n", file->name,
                file->src, file->fpa,
                file->camera, file->fpa->camera, file->format);
    }
    psFree(filesIter);
#endif

    return (options);
}
