#include "ppSim.h"

bool ppSimLoop(pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(config, PS_EXIT_PROG_ERROR);

    pmFPAfile *file = psMetadataLookupPtr(NULL, config->files, OUTPUT_FILE); // Output file
    assert(file);
    pmFPA *fpa = file->fpa;             // FPA for file
    assert(fpa);

    ppSimType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of image to simulate

    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0); // Random number generator

    int binning = psMetadataLookupS32(NULL, config->arguments, "BINNING"); // Binning in x and y

    // Load catalogue stars
    psArray *stars = psArrayAllocEmpty (1);
    if (type == PPSIM_TYPE_OBJECT) {
        ppSimLoadStars (stars, fpa, config);
    }

    // Add random stars
    if (type == PPSIM_TYPE_OBJECT) {
        ppSimMakeStars (stars, fpa, config, rng);
    }

    // Add random galaxies
    psArray *galaxies = psArrayAllocEmpty (1);
    if (type == PPSIM_TYPE_OBJECT) {
        ppSimMakeGalaxies (galaxies, fpa, config, rng);
    }

    pmFPAview *view = pmFPAviewAlloc(0);// View for iterating over FPA

    // XXX if we include the psphot analysis, we will need to activate the correct pmFPAfiles
    // at the correct times.  ppSim operates on PPSIM.OUTPUT and PPSIM.SOURCES

    // load any needed files (eg, input image, PSF)
    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
        psError(PS_ERR_UNKNOWN, false, "failed IO for fpa in ppSim\n");
        psFree(view);
        return false;
    }

    ppSimUpdateConceptsFPA (fpa, config);

    pmChip *chip;                       // Chip from FPA
    while ((chip = pmFPAviewNextChip(view, fpa, 1))) {

        // load any needed files (eg, input image, PSF)
        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
            psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d in ppSim\n", view->chip);
            psFree (view);
            return false;
        }

        if (type == PPSIM_TYPE_OBJECT) {
            if (!ppSimSetPSF (chip, config)) {
                psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d in ppSim\n", view->chip);
                psFree (view);
                return false;
            }
        }

        pmCell *cell;                   // Cell from chip
        while ((cell = pmFPAviewNextCell(view, fpa, 1))) {

            // check that we are able to work with this cell (readdir must be 1)
            int readdir = psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR"); // Read direction
            if (readdir != 1) {
                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.READDIR = 1 is the only supported mode.");
                psFree(rng);
                return false;
            }

            // if no readout exists, generate a single readout (upgrade to allow multiple
            // readouts?)
            if (cell->readouts->n == 0) {
                // Size, position and orientation of cell
                int numCols = psMetadataLookupS32(NULL, cell->concepts, "CELL.XSIZE") / binning;
                int numRows = psMetadataLookupS32(NULL, cell->concepts, "CELL.YSIZE") / binning;

                // generate a new readout for this cell
                pmReadout *readout = pmReadoutAlloc(cell); // Readout within cell

                // TO DO: Decide if cell is to be windowed, reduce numCols, numRows appropriately
                readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Signal in pixels
                readout->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Noise in pixels

                psImageInit (readout->image, 0.0);
                psImageInit (readout->weight, 0.0);

                psFree(readout);        // Drop reference
            }

            psVector *biasCols = ppSimMakeBiassec (cell, config);

            for (int i = 0; i < cell->readouts->n; i++) {

                pmReadout *readout = cell->readouts->data[i];
                assert (readout);

                // if we have not read in a weight or generated a fake image above, we need to
                // build one here
                if (!readout->weight) {
                    if (!pmReadoutGenerateWeight(readout, true)) {
                        psError (PS_ERR_UNKNOWN, false, "trouble creating weight");
                        return false;
                    }
                }

                psImage *expCorr = NULL; // Exposure correction per pixel, for adding objects
                if (type == PPSIM_TYPE_OBJECT) {
                    expCorr = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
                }

                psVector *biasRows = ppSimMakeBias (readout, config, rng);
                if (type == PPSIM_TYPE_BIAS) goto done;

                ppSimMakeDark (readout, config);
                if (type == PPSIM_TYPE_DARK) goto done;

                ppSimMakeSky (readout, expCorr, type, config);
                if (type == PPSIM_TYPE_FLAT) goto done;

                if (type == PPSIM_TYPE_OBJECT) {
                    ppSimInsertStars (readout, expCorr, stars, config);
                }

                if (type == PPSIM_TYPE_OBJECT) {
                    ppSimInsertGalaxies (readout, expCorr, galaxies, config);
                }

                psFree(expCorr);

            done:
                ppSimAddNoise(readout->image, readout->weight, cell, config, rng);
                ppSimSaturate(readout, config);

                ppSimAddOverscan (readout, config, biasCols, biasRows, rng);
                psFree(biasRows);

                readout->data_exists = true;
                readout->parent->data_exists = true;
                readout->parent->parent->data_exists = true;
            }
            psFree(biasCols);

            ppSimUpdateConceptsCell (cell, config);

            if (cell->hdu) {
                ppSimInitHeader(config, NULL, NULL, cell);
            }

            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
                psError(PS_ERR_IO, false, "Unable to write file.");
                psFree(rng);
                psFree(view);
                // return PS_EXIT_SYS_ERROR;
                return false;
            }
        }

        if (chip->hdu) {
            ppSimInitHeader(config, NULL, chip, NULL);
        }

        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
            psError(PS_ERR_IO, false, "Unable to write file.");
            psFree(rng);
            psFree(view);
            // return PS_EXIT_SYS_ERROR;
            return false;
        }

    }

    psFree(stars);
    psFree(galaxies);

    if (fpa->hdu) {
        ppSimInitHeader(config, fpa, NULL, NULL);
    }

    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
        psError(PS_ERR_IO, false, "Unable to write file.");
        psFree(rng);
        psFree(view);
        // return PS_EXIT_SYS_ERROR;
        return false;
    }

    psFree(rng);
    psFree(view);

    return true;
}
