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

#include "ppImage.h"

# define ESCAPE(MESSAGE) { \
  psError(PS_ERR_UNKNOWN, false, MESSAGE); \
  psFree (view); \
  return false; \
}

bool ppImageLoop (pmConfig *config, ppImageOptions *options)
{
    bool status;
    pmChip *chip;
    pmCell *cell;
    pmReadout *readout;

    // measure the total elapsed time in ppImageLoop.
    psTimerStart ("ppImageLoop");

    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
    if (!status) {
        psErrorStackPrint(stderr, "Can't find input data!\n");
        ppImageCleanup(config, options);
        exit(PS_EXIT_PROG_ERROR);
    }

    {
        pmConfigCamerasCull(config, NULL);
        pmConfigRecipesCull(config, "PPIMAGE,PPSTATS,PSPHOT,MASKS,PSASTRO");

        const char *outroot = psMetadataLookupStr(NULL, config->arguments, "OUTPUT"); // Output root name
        psAssert(outroot, "Should be there, we put it there!");
        pmConfigDump(config, input->fpa, outroot);
    }

    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
    pmHDU *lastHDU = NULL;              // Last HDU that was updated

    // files associated with the science image
    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for FPA");

    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
        if (!chip->process || !chip->file_exists) {
            continue;
        }
        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Chip");

        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
            psLogMsg ("ppImageLoop", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
            if (!cell->process || !cell->file_exists) {
                continue;
            }
            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Cell");

            // Put version information into the header
            pmHDU *hdu = pmHDUFromCell(cell);
            if (hdu && hdu != lastHDU) {
                ppImageVersionMetadata(hdu->header);
                lastHDU = hdu;
            }

            // XXX for now, skip the video cells (cell->readouts->n > 1)
            if (cell->readouts->n > 1) {
              psWarning ("Skipping Video Cell for ppImageDetrendReadout");
              continue;
            }

            // process each of the readouts
            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Readout");
                if (!readout->data_exists) {
                    continue;
                }

                // XXX set the options->*Mask values here (after the mask images have been loaded
                // and before any of the value are used)
                if (!ppImageSetMaskBits (config, options))
                    ESCAPE ("Unable to set bit masks");

                // perform the detrend analysis
                if (!ppImageDetrendReadout(config, options, view))
                    ESCAPE ("Unable to detrend readout");
            }
        }

        // Apply the fringe correction
        if (options->doFringe) {
            if (!ppImageDetrendFringeApply (config, chip, view, options))
                ESCAPE ("Unable to defringe");
        }

        // measure various statistics for this image
        if (!ppImagePixelStats (config, options, view))
            ESCAPE ("Unable to measures stats for image");

        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT"))
            ESCAPE ("Unable to mosaic chip");

        // we perform photometry on the readouts of this chip in the output
        if (options->doPhotom) {
            if (!ppImagePhotom(config, view)) ESCAPE ("error running photometry.");
        }

        // replace the masked pixels with the background level
        if (options->replaceMasked) {
            if (!ppImageReplaceBackground (config, view, options))
                ESCAPE ("Unable to replace masked pixels with background level");
        }

        // binning (used for display) must take place after the background is replaced, if desired
        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN1"))
            ESCAPE ("Unable to bin chip (level 1).");

        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN2"))
            ESCAPE ("Unable to bin chip (level 2).");

        // Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
        view->cell = -1;
        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
            if (!cell->process || !cell->file_exists) {
                continue;
            }
            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Cell");
        }

        // Close chip
        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Chip");
    }

    // generate the full-scale FPA mosaic
    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) ESCAPE ("failure in FPA Mosaic (level 1)");
    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) ESCAPE ("failure in FPA Mosaic (level 2)");

    // we perform astrometry on all chips after sources have been detected
    // this also performs the psastro file IO
    if (options->doAstromChip || options->doAstromMosaic) {
        if (!ppImageAstrom(config)) ESCAPE ("error running astrometry.");
    }

    if (psTraceGetLevel("ppImage") >= 3) {
        ppImageFileCheck (config);
    }

    // Write out summary statistics
    if (!ppImageMetadataStats (config, options)) ESCAPE ("Unable to write statistics file.");

    // Write out summary statistics
    if (!ppImageStatsOutput (config, options)) ESCAPE ("Unable to write statistics file.");

    // Output and Close FPA
    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for FPA");

    psFree(view);
    return true;
}
