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

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

    // select the current recipe
    psMetadata *recipe  = psMetadataLookupPtr (NULL, 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;

    // before we do object matches, we need to (optionally) fix failed chips.  We compare chips with
    // the supplied mosaic model.  Adjust significant outliers to match model.  
    # if (0)
    if (!psastroFixChips (config, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
	return false;
    }
    if (!psastroFixChipsTest (config, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
	return false;
    }
    # endif

    // 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, 0)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.0.dat"); }

    // 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");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.1.dat"); }

    // 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 (!psastroMosaicGradients (fpa, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure mosaic gradients");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.2.dat"); }

    // measure the astrometry for the chips under the distortion term
    if (!psastroMosaicChipAstrom (fpa, recipe, 0)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.3.dat"); }

    // do a second pass on the distortion with improved chip positions and rotations
    // first, re-perform the match with a slightly tighter circle
    if (!psastroMosaicSetMatch (fpa, recipe, 1)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (2nd pass)");
	return false;
    }
    if (!psastroMosaicCommonScale (fpa, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to set a common scale for the chips (2nd pass)");
	return false;
    }
    if (!psastroMosaicGradients (fpa, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure mosaic gradients (2nd pass)");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.4.dat"); }

    if (!psastroMosaicChipAstrom (fpa, recipe, 1)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (2nd pass)");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.5.dat"); }

    // do a third pass on the distortion with improved chip positions and rotations
    // first, re-perform the match with a slightly tighter circle
    if (!psastroMosaicSetMatch (fpa, recipe, 2)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (3rd pass)");
	return false;
    }
    if (!psastroMosaicCommonScale (fpa, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to set a common scale for the chips (3rd pass)");
	return false;
    }
    if (!psastroMosaicGradients (fpa, recipe)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure mosaic gradients (3rd pass)");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.6.dat"); }

    if (!psastroMosaicChipAstrom (fpa, recipe, 2)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (3rd pass)");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.7.dat"); }

    // 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, 3)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (3rd pass)");
	return false;
    }
    if (!psastroMosaicChipAstrom (fpa, recipe, 3)) {
	psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (3rd pass)");
	return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.8.dat"); }

    // save WCS and analysis metadata in update header.
    psMetadata *updates = psMetadataAlloc();
    if (!pmAstromWriteBilevelMosaic (updates, fpa, NONLIN_TOL)) {
	psAbort ("failed to save header terms");
    }

    // 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"));

    psMetadataAddMetadata (fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
    psFree (updates);

    // 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)
 */
