# include "psphotInternal.h"

// this should be called by every program that links against libpsphot
bool psphotInit (void) {
    psphotErrorRegister();              // register our error codes/messages
    psphotModelClassInit ();            // load implementation-specific models
    psphotSetThreads ();
    return true;
}

bool psphotReadout(pmConfig *config, const pmFPAview *view) {

    // measure the total elapsed time in psphotReadout.  dtime is the elapsed time used jointly
    // by the multiple threads, not the total time used by all threads.
    psTimerStart ("psphotReadout");

    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);

    // select the current recipe
    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
    if (!recipe) {
        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
        return false;
    }
    // optional break-point for processing
    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
    psAssert (breakPt, "configuration error: set BREAK_POINT");

    // set the photcode for this image
    if (!psphotAddPhotcode (config, view)) {
        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
        return false;
    }

    // Generate the mask and weight images, including the user-defined analysis region of interest
    if (!psphotSetMaskAndVariance (config, view)) {
        return psphotReadoutCleanup(config, view);
    }
    if (!strcasecmp (breakPt, "NOTHING")) {
        return psphotReadoutCleanup(config, view);
    }

    // generate a background model (median, smoothed image)
    if (!psphotModelBackground (config, view)) {
        return psphotReadoutCleanup (config, view);
    }
    if (!psphotSubtractBackground (config, view)) {
        return psphotReadoutCleanup (config, view);
    }
    if (!strcasecmp (breakPt, "BACKMDL")) {
        return psphotReadoutCleanup (config, view);
    }

    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are determined and saved on
    // readout->analysis XXX this function currently only works with a single PSPHOT.INPUT
    if (!psphotLoadPSF (config, view)) {
        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
        return psphotReadoutCleanup (config, view);
    }

    // find the detections (by peak and/or footprint) in the image.
    if (!psphotFindDetections (config, view, true)) { // pass 1
        // this only happens if we had an error in psphotFindDetections
        psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
        return psphotReadoutCleanup (config, view);
    }

    // construct sources and measure basic stats (saved on detections->newSources)
    if (!psphotSourceStats (config, view, true)) { // pass 1
        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
        return psphotReadoutCleanup (config, view);
    }
    if (!strcasecmp (breakPt, "PEAKS")) {
        return psphotReadoutCleanup(config, view);
    }

    // find blended neighbors of very saturated stars (detections->newSources)
    if (!psphotDeblendSatstars (config, view)) {
        psError (PSPHOT_ERR_UNKNOWN, false, "failed on satstar deblend analysis");
        return psphotReadoutCleanup (config, view);
    }

    // mark blended peaks PS_SOURCE_BLEND (detections->newSources)
    if (!psphotBasicDeblend (config, view)) {
        psError (PSPHOT_ERR_UNKNOWN, false, "failed on deblend analysis");
        return psphotReadoutCleanup (config, view);
    }

    // classify sources based on moments, brightness.  if a PSF model has been loaded, the PSF
    // clump defined for it is used not measured (detections->newSources)
    if (!psphotRoughClass (config, view)) { // pass 1
        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough classifications");
        return psphotReadoutCleanup (config, view);
    }
    // if we were not supplied a PSF model, determine the IQ stats here (detections->newSources)
    if (!psphotImageQuality (config, view)) { // pass 1
        psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
        return psphotReadoutCleanup(config, view);
    }
    if (!strcasecmp (breakPt, "MOMENTS")) {
        return psphotReadoutCleanup(config, view);
    }

    // use bright stellar objects to measure PSF if we were supplied a PSF for any input file,
    // this step is skipped
    if (!psphotChoosePSF (config, view)) { // pass 1
        psLogMsg ("psphot", 3, "failure to construct a psf model");
        return psphotReadoutCleanup (config, view);
    }
    if (!strcasecmp (breakPt, "PSFMODEL")) {
        return psphotReadoutCleanup (config, view);
    }

    // include externally-supplied sources
    // XXX fix this in the new multi-input context
    // psphotLoadExtSources (config, view); // pass 1

    // construct an initial model for each object, set the radius to fitRadius, set circular
    // fit mask (detections->newSources)
    psphotGuessModels (config, view); // pass 1

    // merge the newly selected sources into the existing list
    // NOTE: merge OLD and NEW
    psphotMergeSources (config, view);

    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
    psphotFitSourcesLinear (config, view, false); // pass 1 (detections->allSources)

    // identify CRs and extended sources (only unmeasured sources are measured)
    psphotSourceSize (config, view, true); // pass 1 (detections->allSources)
    if (!strcasecmp (breakPt, "ENSEMBLE")) {
        goto finish;
    }

    // non-linear PSF and EXT fit to brighter sources
    // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
    psphotBlendFit (config, view); // pass 1 (detections->allSources)

    // replace all sources
    psphotReplaceAllSources (config, view); // pass 1 (detections->allSources)

    // linear fit to include all sources (subtract again)
    // NOTE : apply to ALL sources (extended + psf)
    psphotFitSourcesLinear (config, view, true); // pass 2 (detections->allSources)

    // if we only do one pass, skip to extended source analysis
    if (!strcasecmp (breakPt, "PASS1")) goto pass1finish;

    // NOTE: possibly re-measure background model here with objects subtracted / or masked

    // add noise for subtracted objects
    psphotAddNoise (config, view); // pass 1 (detections->allSources)

    // find fainter sources
    // NOTE: finds new peaks and new footprints, OLD and FULL set are saved on detections
    psphotFindDetections (config, view, false); // pass 2 (detections->peaks, detections->footprints)

    // remove noise for subtracted objects (ie, return to normal noise level)
    // NOTE: this needs to operate only on the OLD sources
    psphotSubNoise (config, view); // pass 1 (detections->allSources)

    // define new sources based on only the new peaks
    // NOTE: new sources are saved on detections->newSources
    psphotSourceStats (config, view, false); // pass 2 (detections->newSources)

    // set source type
    // NOTE: apply only to detections->newSources
    if (!psphotRoughClass (config, view)) { // pass 2 (detections->newSources)
        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
        return psphotReadoutCleanup (config, view);
    }

    // create full input models, set the radius to fitRadius, set circular fit mask
    // NOTE: apply only to detections->newSources
    psphotGuessModels (config, view); // pass 2 (detections->newSources)

    // replace all sources so fit below applies to all at once
    // NOTE: apply only to OLD sources (which have been subtracted)
    psphotReplaceAllSources (config, view); // pass 2

    // merge the newly selected sources into the existing list
    // NOTE: merge OLD and NEW
    // XXX check on free of sources...
    psphotMergeSources (config, view); // (detections->newSources + detections->allSources -> detections->allSources)

    // NOTE: apply to ALL sources
    psphotFitSourcesLinear (config, view, true); // pass 3 (detections->allSources)

pass1finish:

    // measure source size for the remaining sources
    // NOTE: applies only to NEW (unmeasured) sources
    psphotSourceSize (config, view, false); // pass 2 (detections->allSources)

    psphotExtendedSourceAnalysis (config, view); // pass 1 (detections->allSources)
    psphotExtendedSourceFits (config, view); // pass 1 (detections->allSources)

finish:

    // plot positive sources
    // psphotSourcePlots (readout, sources, recipe);

    // measure aperture photometry corrections
    if (!psphotApResid (config, view)) { // pass 1 (detections->allSources)
        psLogMsg ("psphot", 3, "failed on psphotApResid");
        return psphotReadoutCleanup (config, view);
    }

    // calculate source magnitudes
    psphotMagnitudes(config, view); // pass 1 (detections->allSources)

    if (!psphotEfficiency(config, view)) { // pass 1
        psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
        psErrorClear();
    }

    // replace failed sources?
    // psphotReplaceUnfitSources (sources);

    // replace background in residual image
    psphotSkyReplace (config, view); // pass 1

    // drop the references to the image pixels held by each source
    psphotSourceFreePixels (config, view); // pass 1

    // create the exported-metadata and free local data
    return psphotReadoutCleanup(config, view);
}
