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

#include <stdio.h>
#include <pslib.h>

#include "pmConcepts.h"
#include "pmFPA.h"

#include "pmConceptsAverage.h"


// Update a metadata entry directly
#define MD_UPDATE(MD, NAME, TYPE, VALUE) \
{ \
    psMetadataItem *item = psMetadataLookup(MD, NAME); \
    item->data.TYPE = VALUE; \
}


// Set a variety of concepts in a cell by averaging over several
bool pmConceptsAverageCells(pmCell *target, psList *sources, psRegion *trimsec, psRegion *biassec)
{
    PS_ASSERT_PTR_NON_NULL(target, false);
    PS_ASSERT_PTR_NON_NULL(sources, false);
    PS_ASSERT_INT_POSITIVE(sources->n, false);

    bool success = true;                // Result of setting everything
    float gain       = 0.0;             // Gain
    float readnoise  = 0.0;             // Read noise
    float saturation = INFINITY;        // Saturation level
    float bad        = -INFINITY;       // Bad level
    float exposure   = 0.0;             // Exposure time
    float darktime   = 0.0;             // Dark time
    double time      = 0.0;             // Time of observation
    psTimeType timeSys = 0;             // Time system
    int readdir      = 0;               // Cell read direction
    int xBin = 0, yBin = 0;             // Binning

    int nCells = 0;                     // Number of cells;
    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
    pmCell *cell = NULL;                // Source cell from iteration
    while ((cell = psListGetAndIncrement(sourcesIter))) {
        if (!cell) {
            continue;
        }

        nCells++;
        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
        psTime *cellTime = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME");
        time       += psTimeToMJD(cellTime);
        if (nCells == 1) {
            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
            readdir = psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR");
            xBin    = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
            yBin    = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN");
        } else {
            if (timeSys != psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS")) {
                psLogMsg(__func__, PS_LOG_ERROR, "Differing time systems in use: %d vs %d\n", timeSys,
                         psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS"));
                success = false;
            }
            if (readdir != psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR")) {
                psLogMsg(__func__, PS_LOG_ERROR, "Differing cell read directions in use: %d vs %d\n", readdir,
                         psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR"));
                success = false;
            }
            if (xBin != psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN")) {
                psLogMsg(__func__, PS_LOG_ERROR, "Differing cell x binnings in use: %d vs %d\n", xBin,
                         psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"));
                success = false;
            }
            if (yBin != psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN")) {
                psLogMsg(__func__, PS_LOG_ERROR, "Differing cell y binnings in use: %d vs %d\n", yBin,
                         psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"));
                success = false;
            }
        }

        float cellSaturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
        if (cellSaturation < saturation) {
            saturation = cellSaturation;
        }
        float cellBad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
        if (cellBad > bad) {
            bad = cellBad;
        }
    }
    psFree(sourcesIter);

    gain      /= (float)nCells;
    readnoise /= (float)nCells;
    exposure  /= (float)nCells;
    darktime  /= (float)nCells;
    time      /= (double)nCells;

    MD_UPDATE(target->concepts, "CELL.GAIN", F32, gain);
    MD_UPDATE(target->concepts, "CELL.READNOISE", F32, readnoise);
    MD_UPDATE(target->concepts, "CELL.SATURATION", F32, saturation);
    MD_UPDATE(target->concepts, "CELL.BAD", F32, bad);
    MD_UPDATE(target->concepts, "CELL.EXPOSURE", F32, exposure);
    MD_UPDATE(target->concepts, "CELL.DARKTIME", F32, darktime);
    MD_UPDATE(target->concepts, "CELL.TIMESYS", S32, timeSys);
    MD_UPDATE(target->concepts, "CELL.READDIR", S32, readdir);
    MD_UPDATE(target->concepts, "CELL.XBIN", S32, xBin);
    MD_UPDATE(target->concepts, "CELL.YBIN", S32, yBin);

    // CELL.TIME needs special care
    {
        psMetadataItem *timeItem = psMetadataLookup(target->concepts, "CELL.TIME");
        psFree(timeItem->data.V);
        timeItem->data.V = psTimeFromMJD(time);
    }

    // CELL.TRIMSEC needs special care
    if (trimsec) {
        psMetadataItem *trimsecItem = psMetadataLookup(target->concepts, "CELL.TRIMSEC");
        psFree(trimsecItem->data.V);
        trimsecItem->data.V = psMemIncrRefCounter(trimsec);
    }

    // CELL.BIASSEC needs special care
    if (biassec) {
        psMetadataItem *biassecItem = psMetadataLookup(target->concepts, "CELL.BIASSEC");
        psFree(biassecItem->data.V);
        biassecItem->data.V = psMemIncrRefCounter(biassec);
    }

    return success;
}

