/** @file psastroMosaicAstrom.c
 *
 *  @brief
 *
 *  @ingroup libpsastro
 *
 *  @author IfA
 *  @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2009-02-07 02:03:34 $
 *  Copyright 2009 Institute for Astronomy, University of Hawaii
 */

# include "psastroInternal.h"
# define NONLIN_TOL 0.001 /* tolerance in pixels */

bool psastroMosaicFit (pmFPA *fpa, psMetadata *recipe, const char *rootname, int pass);

// XXX require this fpa to have multiple chip extensions and a PHU?
bool psastroMosaicAstrom (pmConfig *config) {

    bool status;

    // select the current recipe
    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
    if (!recipe) {
        psError(PSASTRO_ERR_CONFIG, false, "Can't find PSASTRO recipe!\n");
        return false;
    }

    // select the input data sources
    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
    if (!input) {
        psError(PSASTRO_ERR_CONFIG, false, "Can't find input data!\n");
        return false;
    }

    pmFPA *fpa = input->fpa;

    char *outroot = psMetadataLookupStr (&status, config->arguments, "OUTPUT");
    if (!status || !outroot) psAbort ("Can't find outroot on config->arguments");

    int nIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NITER");
    if (!status) psAbort ("missing config value");

    // this should be in a loop with nIter =
    for (int iter = 0; iter < nIter; iter++) {
        if (!psastroMosaicFit (fpa, recipe, outroot, iter)) return false;
    }

    // now fit the chips under the common distortion with higher-order terms
    // first, re-perform the match with a slightly tighter circle
    if (!psastroMosaicSetMatch (fpa, recipe, nIter)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (4th pass)");
        return false;
    }
    if (!psastroMosaicChipAstrom (fpa, recipe, nIter)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (4th pass)");
        return false;
    }

    if (psTraceGetLevel("psastro.dump") > 0) {
        // the last filename (see filenames in psastroMosaicFit)
        char filename[256];
        snprintf (filename, 256, "%s.%d.dat", outroot, 2*nIter + 2);
        psastroDumpMatches (fpa, filename);
    }

    // save WCS and analysis metadata in update header.
    // (pull or create local view to entry on readout->analysis)
    psMetadata *updates = psMetadataLookupMetadata (&status, fpa->analysis, "PSASTRO.HEADER");
    if (!updates) {
        updates = psMetadataAlloc ();
        psMetadataAddMetadata (fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
        psFree (updates);
    }
    if (!pmAstromWriteBilevelMosaic (updates, fpa, NONLIN_TOL)) {
        psError(psErrorCodeLast(), false, "Failed to save header terms");
        return false;
    }

    // write the elapsed time here; this will be updated in psastroMosaicAstrometry, if called
    psMetadataAddF32 (updates, PS_LIST_TAIL, "DT_ASTR", PS_META_REPLACE, "elapsed psastro time", psTimerMark ("psastroAnalysis"));

    // update the headers based on the results
    // XXX need to add global summary statistics
    // psastroMosaicHeaders (config);

    return true;
}

/* coordinate frame hierachy
 * pixels (on a given readout)
 * cell
 * chip
 * FP (focal plane)
 * TP (tangent plane)
 * sky (ra, dec)
 */

// 1: match 4,5
// 2: match 6,7
// 3: match 8,9
bool psastroMosaicFit (pmFPA *fpa, psMetadata *recipe, const char *rootname, int pass) {

    char filename[256];

    // given the existing per-chip astrometry, determine matches between raw and ref stars
    // is this needed? yes, if we didn't do SingleChip astrometry first
    if (!psastroMosaicSetMatch (fpa, recipe, pass)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (pass %d)", pass);
        return false;
    }

    if ((pass == 0) && (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1)) {
        snprintf (filename, 256, "%s.0.dat", rootname);
        psastroDumpMatches (fpa, filename);
    }

    // fitted chips will follow the local plate-scale, hiding the distortion
    // modify the chip->toFPA scaling to match knowledge about pixel scale,
    // then recalculate raw and ref positions
    if (!psastroMosaicCommonScale (fpa, recipe)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to set a common scale for the chips (pass %d)", pass);
        return false;
    }

    if ((pass == 0) && (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1)) {
        snprintf (filename, 256, "%s.1.dat", rootname);
        psastroDumpMatches (fpa, filename);
    }

    // fit the distortion by fitting its gradient
    // apply the new distortion terms up and down
    // refit the per-chip terms with linear fits only
    if (!psastroMosaicDistortion (fpa, recipe, pass)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure mosaic gradients (pass %d)", pass);
        return false;
    }

    snprintf (filename, 256, "%s.%d.dat", rootname, 2*pass + 2);
    if (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1) { psastroDumpMatches (fpa, filename); }

    // measure the astrometry for the chips under the distortion term
    if (!psastroMosaicChipAstrom (fpa, recipe, pass)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (pass %d)", pass);
        return false;
    }

    if (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1) {
        snprintf (filename, 256, "%s.%d.dat", rootname, 2*pass + 3);
        psastroDumpMatches (fpa, filename);
    }

    return true;
}
