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

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

#include "ppSub.h"

bool ppSubLoop(pmConfig *config)
{
    bool mdok;                          // Status of MD lookup
    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
    psMetadata *stats = NULL;           // Container for statistics
    FILE *statsFile = NULL;             // File stream for statistics
    if (statsName && strlen(statsName) > 0) {
        psString resolved = pmConfigConvertFilename(statsName, config, true, true); // Resolved filename
        statsFile = fopen(resolved, "w");
        if (!statsFile) {
            psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
            psFree(resolved);
            return false;
        } else {
            stats = psMetadataAlloc();
        }
        psFree(resolved);
    }

    if (!pmConfigMaskSetBits(NULL, NULL, config)) {
        psError(PS_ERR_UNKNOWN, false, "Unable to determine mask value.");
        return false;
    }

    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PPSUB.INPUT");
    if (!input) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find input data!\n");
        return false;
    }

    pmFPAfile *reference = psMetadataLookupPtr(NULL, config->files, "PPSUB.REF");
    if (!reference) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find reference data!\n");
        return false;
    }

    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PPSUB.OUTPUT");
    if (!output) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find output data!\n");
        return false;
    }

    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
    pmHDU *lastHDU = NULL;              // Last HDU that was updated

    // Iterate over the FPA hierarchy
    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
        return false;
    }

    pmChip *inChip;                    // Input chip of interest
    while ((inChip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
        pmChip *refChip = pmFPAviewThisChip(view, reference->fpa); // Reference chip of interest
        if ((!inChip->file_exists && refChip->file_exists) ||
            (inChip->file_exists && !refChip->file_exists)) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA format discrepency between input and reference");
            psFree(view);
            return false;
        }

        if (!inChip->file_exists) {
            continue;
        }

        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
            return false;
        }

        pmCell *inCell;                // Cell of interest
        while ((inCell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
            pmCell *refCell = pmFPAviewThisCell(view, reference->fpa); // Reference cell of interest
            if ((!inCell->file_exists && refCell->file_exists) ||
                (inCell->file_exists && !refCell->file_exists)) {
                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                        "FPA format discrepency between input and reference");
                psFree(view);
                return false;
            }
            if (!inCell->file_exists) {
                continue;
            }
            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
                return false;
            }

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

            pmReadout *inRO;           // Readin of interest
            while ((inRO = pmFPAviewNextReadout(view, input->fpa, 1))) {
                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
                    return false;
                }
                pmReadout *refRO = pmFPAviewThisReadout(view, reference->fpa);// Reference readout of interest
                if (!refRO || (!inRO->data_exists && refRO->data_exists) ||
                    (inRO->data_exists && !refRO->data_exists)) {
                    psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                            "FPA format discrepency between input and reference");
                    psFree(view);
                    return false;
                }
                if (!inRO->data_exists) {
                    continue;
                }

                // Perform the analysis
                if (!ppSubReadout(config, stats, view)) {
                    psError(PS_ERR_UNKNOWN, false, "Unable to subtract images.\n");
                    return false;
                }

                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
                    return false;
                }
            }

            // Perform statistics on the cell
            if (stats) {
                pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PPSUB.OUTPUT"); // Output file
                if (!output) {
                    psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find file PPSUB.OUTPUT.\n");
                    return false;
                }
                ppStatsFPA(stats, output->fpa, view, pmConfigMaskGet("MASK.VALUE", config), config);
            }

            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
                return false;
            }
        }

        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
            return false;
        }
    }

    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
        return false;
    }

    psFree(view);

    // Write out summary statistics
    if (stats) {
        psMetadataAddF32(stats, PS_LIST_TAIL, "TIME_SUB", 0, "Time for subtraction completion",
                         psTimerMark("ppSub"));

        const char *statsMDC = psMetadataConfigFormat(stats);
        if (!statsMDC || strlen(statsMDC) == 0) {
            psWarning("Unable to generate statistics MDC file.\n");
        } else {
            fprintf(statsFile, "%s", statsMDC);
        }
        psFree((void *)statsMDC);
        fclose(statsFile);

        psFree(stats);
    }

    return true;
}
