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

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

#include "ppStack.h"


#if 0
// Define an output convolved image file
static pmFPAfile *defineOutputConvolved(const char *name, // FPA file name
                                        pmFPA *fpa, // FPA to bind
                                        const pmConfig *config, // Configuration
                                        pmFPAfileType type // Expected type
    )
{
    pmFPAfile *file = pmFPAfileDefineOutput(config, fpa, name);
    if (!file) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define output convolved file %s", name);
        return NULL;
    }
    if (file->type != PM_FPA_FILE_IMAGE) {
        psError(PS_ERR_IO, true, "PPSTACK.OUTCONV is not of type %s", pmFPAfileStringFromType(type));
        return NULL;
    }

    return file;
}

// Define an input convolved image file, using the output as a basis
static pmFPAfile *defineInputConvolved(const char *inputName, // Input FPA file name
                                       pmFPAfile *outFile, // Corresponding output FPA file
                                       pmConfig *config, // Configuration
                                       pmFPAfileType type // Expected type
    )
{
    pmFPAview *view = pmFPAviewAlloc(0); // View into sky cells
    view->chip = view->cell = view->readout = 0;

    psString imageName = pmFPANameFromRule(outFile->filerule, outFile->fpa, view);
    psArray *imageNames = psArrayAlloc(1);
    imageNames->data[0] = imageName;
    psMetadataAddArray(config->arguments, PS_LIST_TAIL, "INCONV.FILENAMES", PS_META_REPLACE,
                       "Filenames of input convolved image files", imageNames);
    psFree(imageNames);
    bool found = false;                 // Found the file?
    pmFPAfile *imageFile = pmFPAfileDefineFromArgs(&found, config, "PPSTACK.INCONV",
                                                   "INCONV.FILENAMES");
    psMetadataRemoveKey(config->arguments, "INCONV.FILENAMES");
    if (!imageFile || !found) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define %s file", inputName);
        return NULL;
    }
    if (imageFile->type != type) {
        psError(PS_ERR_IO, true, "PPSTACK.INCONV is not of type %s",
                pmFPAfileStringFromType(type));
        return NULL;
    }

    return imageFile;
}
#endif



bool ppStackCamera(pmConfig *config)
{
    bool haveWeights = false;           // Do we have weight maps?
    bool havePSFs = false;              // Do we have PSFs?
    bool haveSources = (bool)psMetadataLookup(config->arguments, "PPSTACK.SOURCES"); // Have global sources?

    psMetadata *inputs = psMetadataLookupMetadata(NULL, config->arguments, "INPUTS"); // The inputs info
    psMetadataIterator *iter = psMetadataIteratorAlloc(inputs, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *item;               // Item from iteration
    int i = 0;                          // Counter
    while ((item = psMetadataGetAndIncrement(iter))) {
        if (item->type != PS_DATA_METADATA) {
            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                    "Component %s of the input metadata is not of type METADATA", item->name);
            psFree(iter);
            return false;
        }

        psMetadata *input = item->data.md; // The input metadata of interest

        psString image = psMetadataLookupStr(NULL, input, "IMAGE"); // Name of image
        if (!image || strlen(image) == 0) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Component %s lacks IMAGE of type STR", item->name);
            psFree(iter);
            return false;
        }

        bool mdok;
        psString mask = psMetadataLookupStr(&mdok, input, "MASK"); // Name of mask
        psString weight = psMetadataLookupStr(&mdok, input, "WEIGHT"); // Name of weight map
        psString psf = psMetadataLookupStr(&mdok, input, "PSF"); // Name of PSF
        psString sources = psMetadataLookupStr(&mdok, input, "SOURCES"); // Name of sources

        float weighting = psMetadataLookupF32(&mdok, input, "WEIGHTING"); // Relative weighting
        if (!mdok || isnan(weighting) || weighting == 0.0) {
            psWarning("Component %s lacks WEIGHTING of type F32 --- assuming 1.0", item->name);
            weighting = 1.0;
        }

        // Add the image file
        psArray *imageFiles = psArrayAlloc(1); // Array of filenames for this FPA
        imageFiles->data[0] = psMemIncrRefCounter(image);
        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "IMAGE.FILENAMES", PS_META_REPLACE,
                           "Filenames of image files", imageFiles);
        psFree(imageFiles);

        bool found = false;             // Found the file?
        pmFPAfile *imageFile = pmFPAfileDefineFromArgs(&found, config, "PPSTACK.INPUT", "IMAGE.FILENAMES");
        if (!imageFile || !found) {
            psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, image);
            return false;
        }
        if (imageFile->type != PM_FPA_FILE_IMAGE) {
            psError(PS_ERR_IO, true, "PPSTACK.INPUT is not of type IMAGE");
            return false;
        }

        // Optionally add the mask file
        if (mask && strlen(mask) > 0) {
            psArray *maskFiles = psArrayAlloc(1); // Array of filenames for this FPA
            maskFiles->data[0] = psMemIncrRefCounter(mask);
            psMetadataAddArray(config->arguments, PS_LIST_TAIL, "MASK.FILENAMES", PS_META_REPLACE,
                               "Filenames of mask files", maskFiles);
            psFree(maskFiles);

            bool status;
            pmFPAfile *maskFile = pmFPAfileBindFromArgs(&status, imageFile, config, "PPSTACK.INPUT.MASK",
                                                        "MASK.FILENAMES");
            if (!status) {
                psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
                return false;
            }
            if (maskFile->type != PM_FPA_FILE_MASK) {
                psError(PS_ERR_IO, true, "PPSTACK.INPUT.MASK is not of type MASK");
                return false;
            }
        }

        // Optionally add the weight file
        if (weight && strlen(weight) > 0) {
            haveWeights = true;
            psArray *weightFiles = psArrayAlloc(1); // Array of filenames for this FPA
            weightFiles->data[0] = psMemIncrRefCounter(weight);
            psMetadataAddArray(config->arguments, PS_LIST_TAIL, "WEIGHT.FILENAMES", PS_META_REPLACE,
                               "Filenames of weight files", weightFiles);
            psFree(weightFiles);

            bool status;
            pmFPAfile *weightFile = pmFPAfileBindFromArgs(&status, imageFile, config, "PPSTACK.INPUT.WEIGHT",
                                                          "WEIGHT.FILENAMES");
            if (!status) {
                psError(PS_ERR_UNKNOWN, false, "Unable to define file from weight %d (%s)", i, weight);
                return false;
            }
            if (weightFile->type != PM_FPA_FILE_WEIGHT) {
                psError(PS_ERR_IO, true, "PPSTACK.INPUT.WEIGHT is not of type WEIGHT");
                return false;
            }
        }

        // Add the psf file
        if (!psf || strlen(psf) == 0) {
            if (havePSFs) {
                psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PSF %d", i);
                return false;
            }
        } else {
            if (!havePSFs && i != 0) {
                psWarning("PSF not provided for all inputs --- ignoring.");
            } else {
                psArray *psfFiles = psArrayAlloc(1); // Array of filenames for this FPA
                psfFiles->data[0] = psMemIncrRefCounter(psf);
                psMetadataAddArray(config->arguments, PS_LIST_TAIL, "PSF.FILENAMES", PS_META_REPLACE,
                                   "Filenames of PSF files", psfFiles);
                psFree(psfFiles);

                bool status;
                pmFPAfile *psfFile = pmFPAfileBindFromArgs(&status, imageFile, config, "PPSTACK.INPUT.PSF",
                                                           "PSF.FILENAMES");
                if (!status) {
                    psError(PS_ERR_UNKNOWN, false, "Unable to define file from psf %d (%s)", i, psf);
                    return false;
                }
                if (psfFile->type != PM_FPA_FILE_PSF) {
                    psError(PS_ERR_IO, true, "PPSTACK.INPUT.PSF is not of type PSF");
                    return false;
                }
                havePSFs = true;
            }
        }

        // Add the sources file
        if (!haveSources) {
            if (!sources || strlen(sources) == 0) {
                if (havePSFs) {
                    psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find SOURCES %d", i);
                    return false;
                }
            } else {
                if (!havePSFs) {
                    psWarning("SOURCES provided without PSF --- ignoring.");
                } else {
                    psArray *sourcesFiles = psArrayAlloc(1); // Array of filenames for this FPA
                    sourcesFiles->data[0] = psMemIncrRefCounter(sources);
                    psMetadataAddArray(config->arguments, PS_LIST_TAIL, "SOURCES.FILENAMES", PS_META_REPLACE,
                                       "Filenames of SOURCES files", sourcesFiles);
                    psFree(sourcesFiles);

                    bool status;
                    pmFPAfile *sourcesFile = pmFPAfileBindFromArgs(&status, imageFile, config,
                                                                   "PPSTACK.INPUT.SOURCES",
                                                                   "SOURCES.FILENAMES");
                    if (!status) {
                        psError(PS_ERR_UNKNOWN, false, "Unable to define file from sources %d (%s)",
                                i, sources);
                        return false;
                    }
                    if (sourcesFile->type != PM_FPA_FILE_CMF) {
                        psError(PS_ERR_IO, true, "PPSTACK.INPUT.SOURCES is not of type CMF");
                        return false;
                    }
                }
            }
        }

#if 0
        // Output convolved files
        pmFPAfile *outconvImage  = defineOutputConvolved("PPSTACK.OUTCONV", imageFile->fpa, config,
                                                         PM_FPA_FILE_IMAGE);
        pmFPAfile *outconvMask   = defineOutputConvolved("PPSTACK.OUTCONV.MASK", imageFile->fpa, config,
                                                         PM_FPA_FILE_MASK);
        pmFPAfile *outconvWeight = defineOutputConvolved("PPSTACK.OUTCONV.WEIGHT", imageFile->fpa, config,
                                                         PM_FPA_FILE_WEIGHT);
        if (!outconvImage || !outconvMask || !outconvWeight) {
            return false;
        }

        // Input convolved files
        pmFPAfile *inconvImage  = defineInputConvolved("PPSTACK.INCONV", outconvImage, config,
                                                       PM_FPA_FILE_IMAGE);
        pmFPAfile *inconvMask   = defineInputConvolved("PPSTACK.INCONV.MASK", outconvMask, config,
                                                       PM_FPA_FILE_MASK);
        pmFPAfile *inconvWeight = defineInputConvolved("PPSTACK.INCONV.WEIGHT", outconvWeight, config,
                                                       PM_FPA_FILE_WEIGHT);
        if (!inconvImage || !inconvMask || !inconvWeight) {
            return false;
        }
#endif

        psMetadataAddF32(imageFile->fpa->analysis, PS_LIST_TAIL, "PPSTACK.WEIGHTING", 0,
                         "Relative weighting for image", weighting);

        i++;
    }
    psFree(iter);
    psMetadataRemoveKey(config->arguments, "IMAGE.FILENAMES");
    if (psMetadataLookup(config->arguments, "MASK.FILENAMES")) {
        psMetadataRemoveKey(config->arguments, "MASK.FILENAMES");
    }
    if (psMetadataLookup(config->arguments, "WEIGHT.FILENAMES")) {
        psMetadataRemoveKey(config->arguments, "WEIGHT.FILENAMES");
    }
    if (psMetadataLookup(config->arguments, "PSF.FILENAMES")) {
        psMetadataRemoveKey(config->arguments, "PSF.FILENAMES");
    }
    if (psMetadataLookup(config->arguments, "SOURCES.FILENAMES")) {
        psMetadataRemoveKey(config->arguments, "SOURCES.FILENAMES");
    }

    if (haveSources) {
        // Global list of sources for use as stamps
        bool status = false;            // Found the file?
        if (havePSFs) {
            pmFPAfile *sources = pmFPAfileDefineFromArgs(&status, config, "PPSTACK.INPUT.SOURCES",
                                                         "PPSTACK.SOURCES");
            if (!status) {
                psError(PS_ERR_IO, false, "Failed to load file definition PPSTACK.INPUT.SOURCES");
                return false;
            }
            if (sources && sources->type != PM_FPA_FILE_CMF) {
                psError(PS_ERR_IO, true, "PPSTACK.SOURCES is not of type CMF");
                return false;
            }
        }
    }

    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUTS.NUM", 0, "Number of input files", i);
    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "HAVE.PSF", 0, "Have PSFs available?", havePSFs);
    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "HAVE.SOURCES", 0, "Have global sources?",
                      haveSources);

    // Output image
    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName); // FPA to contain the output
    if (!fpa) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to construct an FPA from camera configuration.");
        return false;
    }
    pmFPAfile *output = pmFPAfileDefineOutput(config, fpa, "PPSTACK.OUTPUT");
    psFree(fpa);                        // Drop reference
    if (!output) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT"));
        return false;
    }
    if (output->type != PM_FPA_FILE_IMAGE) {
        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT is not of type IMAGE");
        return false;
    }
    output->save = true;

    if (!pmFPAAddSourceFromFormat(fpa, "Stack", output->format)) {
        psError(PS_ERR_UNKNOWN, false, "Unable to generate output FPA.");
        psFree(fpa);
        return false;
    }

    // Output mask
    pmFPAfile *outMask = pmFPAfileDefineOutput(config, output->fpa, "PPSTACK.OUTPUT.MASK");
    if (!outMask) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT.MASK"));
        return false;
    }
    if (outMask->type != PM_FPA_FILE_MASK) {
        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT.MASK is not of type MASK");
        return false;
    }
    outMask->save = true;

    // Output weight
    if (haveWeights) {
        pmFPAfile *outWeight = pmFPAfileDefineOutput(config, output->fpa, "PPSTACK.OUTPUT.WEIGHT");
        if (!outWeight) {
            psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT.WEIGHT"));
            return false;
        }
        if (outWeight->type != PM_FPA_FILE_WEIGHT) {
            psError(PS_ERR_IO, true, "PPSTACK.OUTPUT.WEIGHT is not of type WEIGHT");
            return false;
        }
        outWeight->save = true;
    }

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPSTACK_RECIPE); // Recipe for ppSim
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPSTACK_RECIPE);
        return false;
    }

    // For photometry, we operate on the chip-mosaicked image
    // we create a copy of the mosaicked image for psphot so we can write out a clean image
    bool mdok = false;
    bool doPhotom = psMetadataLookupBool(&mdok, recipe, "PHOTOMETRY") ||
        psMetadataLookupBool(&mdok, config->arguments, "PHOTOMETRY"); // perform photometry
    if (doPhotom) {
        // This file, PSPHOT.INPUT, is just used as a carrier; output files (eg, PSPHOT.RESID) are defined by
        // psphotDefineFiles
        pmFPAfile *psphotInput = pmFPAfileDefineFromFPA(config, output->fpa, 1, 1, "PSPHOT.INPUT");
        if (!psphotInput) {
            psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.INPUT"));
            return false;
        }

        // Define associated psphot input/output files
        if (!psphotDefineFiles(config, psphotInput)) {
            psError(PSPHOT_ERR_CONFIG, false,
                    "Trouble defining the additional input/output files for psphot");
            return false;
        }
    } else {
        // Output PSF --- only required if photometry is not being performed
        pmFPAfile *outPSF = pmFPAfileDefineOutputFromFile(config, output, "PSPHOT.PSF.SAVE");
        if (!outPSF) {
            psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.PSF.SAVE"));
            return false;
        }
        if (outPSF->type != PM_FPA_FILE_PSF) {
            psError(PS_ERR_IO, true, "PSPHOT.PSF.SAVE is not of type PSF");
            return false;
        }
        outPSF->save = true;
    }

    return true;
}
