/** @file  pmSourcePhotometry.c
 *
 *  @author EAM, IfA; GLG, MHPCC
 *
 *  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-02-01 03:15:13 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 *
 */

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

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <pslib.h>
#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPAMaskWeight.h"
#include "pmPeaks.h"
#include "pmMoments.h"
#include "pmGrowthCurve.h"
#include "pmModel.h"
#include "pmPSF.h"
#include "pmSource.h"
#include "pmModelGroup.h"
#include "pmSourcePhotometry.h"

# define DO_SKY 0

static float AP_MIN_SN = 0.0;

bool pmSourceMagnitudesInit (psMetadata *config)
{

    bool status;

    float limit = psMetadataLookupF32 (&status, config, "AP_MIN_SN");
    if (status) {
        AP_MIN_SN = limit;
    }
    return true;
}

/**
    this function is used to calculate the three defined source magnitudes:
    - apMag  : only if S/N > AP_MIN_SN
             : is optionally corrected for curve-of-growth if:
        - the source is a STAR (PSF)
        - the option is selected (mode & PM_SOURCE_PHOT_GROWTH)
    - psfMag : all sources with non-NULL modelPSF
             : is optionally corrected for aperture residual if:
        - the source is a STAR (PSF)
        - the option is selected (mode & PM_SOURCE_PHOT_APCORR)
 
    - extMag : all sources with non-NULL modelEXT
**/

// XXX masked region should be (optionally) elliptical
bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode)
{

    int status = false;
    bool isPSF;
    float x, y;
    float rflux;
    float SN;
    pmModel *model;

    source->psfMag = NAN;
    source->extMag = NAN;
    source->errMag = NAN;
    source->apMag  = NAN;

    // we must have a valid model
    model = pmSourceGetModel (&isPSF, source);
    if (model == NULL)
        return false;

    if (model->dparams->data.F32[PM_PAR_I0] > 0) {
        SN = model->params->data.F32[PM_PAR_I0] / model->dparams->data.F32[PM_PAR_I0];
        source->errMag = 1.0 / SN;
    } else {
        SN = NAN;
        source->errMag = NAN;
    }
    x = model->params->data.F32[PM_PAR_XPOS];
    y = model->params->data.F32[PM_PAR_YPOS];

    // measure object model photometry
    status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
    status = pmSourcePhotometryModel (&source->extMag, source->modelEXT);
    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
    if ((mode & PM_SOURCE_PHOT_APCORR) && isPSF && psf) {
        // convert to the equivalent 2D model?
        source->psfMag += psPolynomial4DEval (psf->ApTrend, x, y, 0.0, 0.0);
    }

    if (!isfinite(SN) || (SN < AP_MIN_SN))
        return false;

    // replace source flux
    // XXX need to be certain which model and size of mask for prior subtraction
    if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
        pmModelAdd (source->pixels, source->mask, model, false, false);
    }

    // if we are measuring aperture photometry and applying the growth correction,
    // we need to shift the flux in the selected pixels (but not the mask)
    // psImageShift ();
    psImage *flux;
    if (mode & PM_SOURCE_PHOT_INTERP) {
        float dx = 0.5 - x + (int)x;
        float dy = 0.5 - y + (int)y;
        x += dx;
        y += dy;
        flux = psImageShift (NULL, source->pixels, dx, dy, 0.0, PS_INTERPOLATE_BICUBE);
        // flux = psImageShift (NULL, source->pixels, dx, dy, 0.0, PS_INTERPOLATE_BILINEAR);

        // XXX this is test code to verify the shift is doing the right thing (seems to be)
        # if (0)
            // measure centroid of unshifted gaussian (should be 16.0,16.0)
        {
            psImage *image = source->pixels;
            float xo = 0.0;
            float yo = 0.0;
            float xo2 = 0.0;
            float yo2 = 0.0;
            float no = 0.0;
            for (int j = 0; j < image->numRows; j++)
            {
                for (int i = 0; i < image->numCols; i++) {
                    xo += i*image->data.F32[j][i];
                    yo += j*image->data.F32[j][i];
                    xo2 += i*i*image->data.F32[j][i];
                    yo2 += j*j*image->data.F32[j][i];
                    no += image->data.F32[j][i];
                }
            }
            xo /= no;
            yo /= no;
            xo2 = sqrt (xo2/no - xo*xo);
            yo2 = sqrt (yo2/no - yo*yo);
            fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
        }

        // measure centroid of unshifted gaussian (should be 16.0,16.0)
        {
            psImage *image = flux;
            float xo = 0.0;
            float yo = 0.0;
            float xo2 = 0.0;
            float yo2 = 0.0;
            float no = 0.0;
            for (int j = 0; j < image->numRows; j++)
            {
                for (int i = 0; i < image->numCols; i++) {
                    xo += i*image->data.F32[j][i];
                    yo += j*image->data.F32[j][i];
                    xo2 += i*i*image->data.F32[j][i];
                    yo2 += j*j*image->data.F32[j][i];
                    no += image->data.F32[j][i];
                }
            }
            xo /= no;
            yo /= no;
            xo2 = sqrt (xo2/no - xo*xo);
            yo2 = sqrt (yo2/no - yo*yo);
            fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
        }
        # endif

    } else {
        flux = source->pixels;
    }
    // set aperture mask circle to model radius
    // XXX use a different radius for apertures and fits...
    psImageKeepCircle (source->mask, x, y, model->radiusFit, "OR", PM_MASK_MARK);

    // measure the weight of included pixels
    // XXX is this supposed to use the weight or the flux?
    if (mode & PM_SOURCE_PHOT_WEIGHT) {
        pmSourcePixelWeight (&source->pixWeight, model, source->pixels, source->mask);
    }

    // measure object aperture photometry
    status = pmSourcePhotometryAper  (&source->apMag, model, flux, source->mask);

    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
    if (status && isPSF && psf) {
        if (psf->growth && (mode & PM_SOURCE_PHOT_GROWTH)) {
            source->apMag += pmGrowthCurveCorrect (psf->growth, model->radiusFit);
        }
        if (mode & PM_SOURCE_PHOT_APCORR) {
            rflux   = pow (10.0, 0.4*source->psfMag);
            source->apMag  -= PS_SQR(model->radiusFit)*rflux * psf->skyBias + psf->skySat / rflux;
        }
    }
    if (mode & PM_SOURCE_PHOT_INTERP) {
        psFree (flux);
    }

    // unmask aperture
    psImageKeepCircle (source->mask, x, y, model->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));

    // if source was originally subtracted, re-subtract object, leave local sky
    if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
        pmModelSub (source->pixels, source->mask, model, false, false);
    }

    return status;
}

/*
aprMag' - fitMag = flux*skySat + r^2*rflux*skyBias + ApTrend(x,y)
(aprMag - flux*skySat - r^2*rflux*skyBias) - fitMAg = ApTrend(x,y)
(aprMag - flux*skySat - r^2*rflux*skyBias) = fitMAg + ApTrend(x,y)
 
*/

// return source model magnitude
bool pmSourcePhotometryModel (float *fitMag, pmModel *model)
{

    float fitSum = 0;
    *fitMag = NAN;

    if (model == NULL) {
        return false;
    }

    // measure fitMag
    pmModelFlux modelFluxFunc = pmModelFlux_GetFunction (model->type);
    fitSum = modelFluxFunc (model->params);
    if (fitSum <= 0)
        return false;
    if (!isfinite(fitSum))
        return false;
    *fitMag = -2.5*log10(fitSum);

    return (true);
}

// return source aperture magnitude
bool pmSourcePhotometryAper (float *apMag, pmModel *model, psImage *image, psImage *mask)
{
    float apSum = 0;
    float sky = 0;
    *apMag = NAN;

    if (model == NULL) {
        return false;
    }

    if (DO_SKY) {
        sky = model->params->data.F32[PM_PAR_SKY];
    } else {
        sky = 0;
    }

    psF32 **imData = image->data.F32;
    psU8 **mkData = mask->data.U8;

    // measure apMag
    for (int ix = 0; ix < image->numCols; ix++) {
        for (int iy = 0; iy < image->numRows; iy++) {
            if (mkData[iy][ix])
                continue;
            apSum += imData[iy][ix] - sky;
        }
    }
    if (apSum <= 0)
        return false;

    *apMag = -2.5*log10(apSum);
    return (true);
}

// return source aperture magnitude
bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask)
{
    float modelSum = 0;
    float validSum = 0;
    float sky = 0;
    float value;

    int Xo, Yo, dP;
    int dX, DX, NX;
    int dY, DY, NY;

    *pixWeight = 0.0;

    if (model == NULL)
        return false;

    // we only care about the value of the object model, not the local sky
    if (DO_SKY) {
        sky = model->params->data.F32[PM_PAR_SKY];
    } else {
        sky = 0;
    }

    // the model function returns the source flux at a position
    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);

    psVector *params = model->params;

    Xo = params->data.F32[PM_PAR_XPOS];
    Yo = params->data.F32[PM_PAR_YPOS];

    dX = Xo - image->col0;
    dP = image->numCols - dX;
    DX = PS_MAX(dX, dP);
    NX = image->numCols;

    dY = Yo - image->row0;
    dP = image->numRows - dY;
    DY = PS_MAX(dY, dP);
    NY = image->numRows;

    // measure modelSum and validSum
    // XXX this does not work for sources near the edge: we need to measure for
    // a full square region
    for (int ix = -DX; ix < DX + 1; ix++) {
        int mx = ix + dX;
        for (int iy = -DY; iy < DY + 1; iy++) {
            int my = iy + dY;

            coord->data.F32[0] = (psF32) (ix + Xo);
            coord->data.F32[1] = (psF32) (iy + Yo);

            // for the full model, add all points
            value = modelFunc (NULL, params, coord) - sky;
            modelSum += value;

            // include count only the unmasked pixels within the image area
            if (mx < 0)
                continue;
            if (my < 0)
                continue;
            if (mx >= NX)
                continue;
            if (my >= NY)
                continue;
            if (mask->data.U8[my][mx])
                continue;

            validSum += value;
        }
    }
    psFree (coord);

    if (validSum <= 0)
        return false;

    *pixWeight = validSum / modelSum;
    return (true);
}

double pmSourceCrossProduct (const pmSource *Mi,
                             const pmSource *Mj,
                             const bool unweighted_sum) // should the cross product be weighted?
{

    int Xs, Xe, Ys, Ye;
    int xi, xj, yi, yj;
    int xIs, xJs, yIs, yJs;
    int xIe, yIe;
    double flux, wt;

    const psImage *Pi = Mi->pixels;
    const psImage *Pj = Mj->pixels;

    const psImage *Wi = Mi->weight;

    const psImage *Ti = Mi->mask;
    const psImage *Tj = Mj->mask;

    Xs = PS_MAX (Pi->col0, Pj->col0);
    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);

    Ys = PS_MAX (Pi->row0, Pj->row0);
    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);

    xIs = Xs - Pi->col0;
    xJs = Xs - Pj->col0;
    yIs = Ys - Pi->row0;
    yJs = Ys - Pj->row0;

    xIe = Xe - Pi->col0;
    yIe = Ye - Pi->row0;

    // note that this is addressing the same image pixels,
    // though only if both are source not model images
    flux = 0;
    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
            if (Ti->data.U8[yi][xi])
                continue;
            if (Tj->data.U8[yj][xj])
                continue;

            if (unweighted_sum) {
                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
            } else {
                wt = Wi->data.F32[yi][xi];
                if (wt > 0) {
                    flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
                }
            }
        }
    }
    return flux;
}

double pmSourceCrossWeight(const pmSource *Mi,
                           const pmSource *Mj,
                           const bool unweighted_sum) // should the cross product be weighted?
{

    int Xs, Xe, Ys, Ye;
    int xi, xj, yi, yj;
    int xIs, xJs, yIs, yJs;
    int xIe, yIe;
    double flux, wt;

    const psImage *Pi = Mi->pixels;
    const psImage *Pj = Mj->pixels;

    const psImage *Wi = Mi->weight;

    const psImage *Ti = Mi->mask;
    const psImage *Tj = Mj->mask;

    Xs = PS_MAX (Pi->col0, Pj->col0);
    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);

    Ys = PS_MAX (Pi->row0, Pj->row0);
    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);

    xIs = Xs - Pi->col0;
    xJs = Xs - Pj->col0;
    yIs = Ys - Pi->row0;
    yJs = Ys - Pj->row0;

    xIe = Xe - Pi->col0;
    yIe = Ye - Pi->row0;

    // note that this is addressing the same image pixels,
    // though only if both are source not model images
    flux = 0;
    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
            if (Ti->data.U8[yi][xi])
                continue;
            if (Tj->data.U8[yj][xj])
                continue;

            if (unweighted_sum) {
                flux++;
            } else {
                wt = Wi->data.F32[yi][xi];
                if (wt > 0) {
                    flux += 1.0 / wt;
                }
            }
        }
    }
    return flux;
}

double pmSourceWeight(const pmSource *Mi,
                      int term,
                      const bool unweighted_sum) // should the cross product be weighted?
{
    double flux = 0, wt = 0, factor = 0;

    const psImage *Pi = Mi->pixels;
    const psImage *Wi = Mi->weight;
    const psImage *Ti = Mi->mask;

    // note that this is addressing the same image pixels,
    // though only if both are source not model images
    for (int yi = 0; yi < Pi->numRows; yi++) {
        for (int xi = 0; xi < Pi->numCols; xi++) {
            if (Ti->data.U8[yi][xi])
                continue;
            if (!unweighted_sum) {
                wt = Wi->data.F32[yi][xi];
                if (wt == 0)
                    continue;
            }

            switch (term) {
            case 0:
                factor = 1;
                break;
            case 1:
                factor = xi + Pi->col0;
                break;
            case 2:
                factor = yi + Pi->row0;
                break;
            default:
                psAbort ("models/objects", "invalid term for pmSourceWeight");
            }

            if (unweighted_sum) {
                flux += (factor * Pi->data.F32[yi][xi]);
            } else {
                flux += (factor * Pi->data.F32[yi][xi]) / wt;
            }
            // fprintf (stderr, "Pi: %f, flux: %f\n", Pi->data.F32[yi][xi], flux);
        }
    }
    return flux;
}

bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight)
{

    double dC = 0.0;
    int Npix = 0;
    for (int j = 0; j < image->numRows; j++) {
        for (int i = 0; i < image->numCols; i++) {
            if (mask->data.U8[j][i])
                continue;
            if (weight->data.F32[j][i] <= 0)
                continue;
            dC += PS_SQR (image->data.F32[j][i]) / weight->data.F32[j][i];
            Npix ++;
        }
    }
    model->nDOF = Npix - 1;
    model->chisq = dC;

    return (true);
}

// given a source, which model is currently appropriate?
// choose PSF or EXT based on source->type, but fall back on PSF
// if the EXT model is NULL
pmModel *pmSourceGetModel (bool *isPSF, const pmSource *source)
{

    pmModel *model;

    switch (source->type) {
    case PM_SOURCE_TYPE_STAR:
        model = source->modelPSF;
        if (model == NULL)
            return NULL;
        if (isPSF)
            *isPSF = true;
        return model;

    case PM_SOURCE_TYPE_EXTENDED:
        model = source->modelEXT;
        if (isPSF)
            *isPSF = false;
        if (model == NULL) {
            model = source->modelPSF;
            if (isPSF)
                *isPSF = true;
        }
        if (model == NULL) {
            if (isPSF)
                *isPSF = FALSE;
            return NULL;
        }
        return (model);
        break;

    default:
        if (isPSF)
            *isPSF = false;
        return NULL;
    }
    return NULL;
}
