# 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;
    char filename[256];

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

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

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

    if (!psastroMosaicFit (fpa, recipe, outroot, 0)) return false;
    if (!psastroMosaicFit (fpa, recipe, outroot, 1)) return false;
    if (!psastroMosaicFit (fpa, recipe, outroot, 2)) return false;
    if (!psastroMosaicFit (fpa, recipe, outroot, 3)) 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, 4)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (4th pass)");
        return false;
    }
    if (!psastroMosaicChipAstrom (fpa, recipe, 4)) {
        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (4th pass)");
        return false;
    }
    if (psTraceGetLevel("psastro.dump") > 0) {
        snprintf (filename, 256, "%s.10.dat", outroot);
        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)) {
        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"));

    // 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;
}
