/*
  This file is part of the Astrometry.net suite.
  Copyright 2006-2008 Dustin Lang, Keir Mierle and Sam Roweis.
  Copyright 2009, 2010 Dustin Lang.

  The Astrometry.net suite is free software; you can redistribute
  it and/or modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation, version 2.

  The Astrometry.net suite is distributed in the hope that it will be
  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with the Astrometry.net suite ; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <sys/param.h>

#include "kdtree.h"
#include "starutil.h"
#include "mathutil.h"
#include "bl.h"
#include "matchobj.h"
#include "catalog.h"
#include "tic.h"
#include "quadfile.h"
#include "quad-utils.h"
#include "xylist.h"
#include "rdlist.h"
#include "qidxfile.h"
#include "verify.h"
#include "ioutils.h"
#include "starkd.h"
#include "codekd.h"
#include "index.h"
#include "boilerplate.h"
#include "sip.h"
#include "sip-utils.h"
#include "sip_qfits.h"
#include "log.h"
#include "fitsioutils.h"
#include "fit-wcs.h"
#include "codefile.h"
#include "solver.h"
#include "permutedsort.h"
#include "intmap.h"

#include "plotstuff.h"
#include "plotxy.h"
#include "plotindex.h"

static const char* OPTIONS = "hx:w:i:vj:X:Y:Q:s:O:";

void print_help(char* progname) {
	boilerplate_help_header(stdout);
	printf("\nUsage: %s\n"
		   "   -w <WCS input file>\n"
		   "   -x <xyls input file>\n"
		   "   -i <index-name>\n"
		   "   [-X <x-column>]: X column name\n"
		   "   [-Y <y-column>]: Y column name\n"
           "   [-v]: verbose\n"
		   "   [-j <pixel-jitter>]: set pixel jitter (default 1.0)\n"
		   "   [-Q <quad-plot-basename>]: plot all the quads in the image, one per file.  Put a %%i in this filename!\n"
		   "   [-O <obj-plot-basename>]: make a plot for each image object; Put a %%i in this filename\n"
		   "   [-s <plot-scale>]: scale everything by this factor (eg, 0.25)\n"
		   "\n", progname);
}

extern char *optarg;
extern int optind, opterr, optopt;

struct foundquad {
	//unsigned int stars[DQMAX];
	double codedist;
	double logodds;
	double pscale;
	int quadnum;
	MatchObj mo;
};
typedef struct foundquad foundquad_t;

static int sort_fq_by_stars(const void* v1, const void* v2) {
	const foundquad_t* fq1 = v1;
	const foundquad_t* fq2 = v2;
	int mx1=0, mx2=0;
	int i;
	for (i=0; i<DQMAX; i++) {
		mx1 = MAX(mx1, fq1->mo.field[i]);
		mx2 = MAX(mx2, fq2->mo.field[i]);
	}
	if (mx1 < mx2)
		return -1;
	if (mx1 == mx2)
		return 0;
	return 1;
}

struct correspondence {
	int star;
	int field;
	double starx, stary;
	double fieldx, fieldy;
	double dist;
};
typedef struct correspondence corr_t;

struct indexstar {
	int star;
	double starx, stary;
	pl* corrs;
	il* allquads;
};
typedef struct indexstar indexstar_t;


int main(int argc, char** args) {
	int c;
	char* xylsfn = NULL;
	char* wcsfn = NULL;

	sl* indexnames;
	pl* indexes;
	pl* qidxes;

	xylist_t* xyls = NULL;
	sip_t sip;
	int i;
	int W, H;
	double xyzcenter[3];
	double fieldrad2;
	double pixeljitter = 1.0;
    int loglvl = LOG_MSG;
	double wcsscale;

	char* xcol = NULL;
	char* ycol = NULL;
	
	double nsigma = 3.0;

	char* quadplotfn = NULL;
	char* objplotfn = NULL;
	double quadplotscale = 1.0;

	fits_use_error_system();

	indexnames = sl_new(8);

    while ((c = getopt(argc, args, OPTIONS)) != -1) {
        switch (c) {
		case 's':
			quadplotscale = atof(optarg);
			break;
		case 'Q':
			quadplotfn = optarg;
			break;
		case 'O':
			objplotfn = optarg;
			break;
		case 'X':
			xcol = optarg;
			break;
		case 'Y':
			ycol = optarg;
			break;
		case 'j':
			pixeljitter = atof(optarg);
			break;
        case 'h':
			print_help(args[0]);
			exit(0);
		case 'i':
			sl_append(indexnames, optarg);
			break;
		case 'x':
			xylsfn = optarg;
			break;
		case 'w':
			wcsfn = optarg;
			break;
        case 'v':
            loglvl++;
            break;
		}
	}
	if (optind != argc) {
		print_help(args[0]);
		exit(-1);
	}
	if (!xylsfn || !wcsfn) {
		print_help(args[0]);
		exit(-1);
	}
    log_init(loglvl);

	// read WCS.
	logmsg("Trying to parse SIP header from %s...\n", wcsfn);
	if (!sip_read_header_file(wcsfn, &sip)) {
		logmsg("Failed to parse SIP header from %s.\n", wcsfn);
	}
	// image W, H
	W = sip.wcstan.imagew;
	H = sip.wcstan.imageh;
	if ((W == 0.0) || (H == 0.0)) {
		logmsg("WCS file %s didn't contain IMAGEW and IMAGEH headers.\n", wcsfn);
		// FIXME - use bounds of xylist?
		exit(-1);
	}
	wcsscale = sip_pixel_scale(&sip);
	logmsg("WCS scale: %g arcsec/pixel\n", wcsscale);

	// read XYLS.
	xyls = xylist_open(xylsfn);
	if (!xyls) {
		logmsg("Failed to read an xylist from file %s.\n", xylsfn);
		exit(-1);
	}
	if (xcol)
		xylist_set_xname(xyls, xcol);
	if (ycol)
		xylist_set_yname(xyls, ycol);
	xylist_set_include_flux(xyls, FALSE);
	xylist_set_include_background(xyls, FALSE);



	// read indices.
	indexes = pl_new(8);
	qidxes = pl_new(8);
	for (i=0; i<sl_size(indexnames); i++) {
		char* name = sl_get(indexnames, i);
		index_t* indx;
		char* qidxfn;
		qidxfile* qidx;
		logmsg("Loading index from %s...\n", name);
		indx = index_load(name, 0, NULL);
		if (!indx) {
			logmsg("Failed to read index \"%s\".\n", name);
			exit(-1);
		}
		pl_append(indexes, indx);

		logmsg("Index name: %s\n", indx->indexname);

        qidxfn = index_get_qidx_filename(indx->indexname);
		qidx = qidxfile_open(qidxfn);
		if (!qidx) {
			logmsg("Failed to open qidxfile \"%s\".\n", qidxfn);
            exit(-1);            
		}
		free(qidxfn);
		pl_append(qidxes, qidx);
	}
	sl_free2(indexnames);

	// Find field center and radius.
	sip_pixelxy2xyzarr(&sip, W/2, H/2, xyzcenter);
	fieldrad2 = arcsec2distsq(sip_pixel_scale(&sip) * hypot(W/2, H/2));

	// Find all stars in the field.
	for (i=0; i<pl_size(indexes); i++) {
		index_t* indx;
		int nquads;
		uint32_t* quads;
		int j, k;
		qidxfile* qidx;
		il* uniqquadlist;
		bl* foundquads = NULL;

        // index stars that are inside the image.
		il* starlist;

		// index stars that have correspondences.
		il* corrstarlist;

        // quads that are at least partly-contained in the image.
		il* quadlist;

        // quads that are fully-contained in the image.
		il* fullquadlist;

        // index stars that are in partly-contained quads.
		il* starsinquadslist;

        // index stars that are in fully-contained quads.
		il* starsinquadsfull;

        // index stars that are in quads and have correspondences.
		il* corrstars;
        // the corresponding field stars
        il* corrfield;

        // quads that are fully in the image and built from stars with correspondences.
		il* corrfullquads;


		// Map from index stars to list of field stars corresponding.
		//intmap_t* corr_i2f;
		//intmap_t* corr_f2i;

		bl* corrs;

		indexstar_t* istars;
	
		dl* starxylist;
		il* corrquads;
		il* corruniqquads;
        starxy_t* xy;

        // (x,y) positions of field stars.
		double* fieldxy;

		int Nfield;
		kdtree_t* ftree;
		kdtree_t* itree;
		int Nleaf = 5;
        int dimquads, dimcodes;
		int ncorr, nindexcorr;
		double pixr2;
		il* indstarswithcorrs;

		double* xyzs = NULL;

		// Indices (in the star-kdtree) of stars that are within the image rectangle;
		// Ngood of them.
		int* starinds = NULL;

		int Nindex;
		int Ngood;

		indx = pl_get(indexes, i);
		qidx = pl_get(qidxes, i);

		corrs = bl_new(256, sizeof(corr_t));

		logmsg("Index jitter: %g arcsec (%g pixels)\n", indx->index_jitter, indx->index_jitter / wcsscale);
		pixr2 = square(indx->index_jitter / wcsscale) + square(pixeljitter);
		logmsg("Total jitter: %g pixels\n", sqrt(pixr2));

		// Read field
        xy = xylist_read_field(xyls, NULL);
        if (!xy) {
			logmsg("Failed to read xyls entries.\n");
			exit(-1);
        }
        Nfield = starxy_n(xy);
        fieldxy = starxy_to_xy_array(xy, NULL);
		logmsg("Found %i field objects\n", Nfield);

		// Find index stars.
		startree_search_for(indx->starkd, xyzcenter, fieldrad2*1.05,
							&xyzs, NULL, &starinds, &Nindex);
		if (!Nindex) {
			logmsg("No index stars found.\n");
			exit(-1);
		}
		logmsg("Found %i index stars in range.\n", Nindex);

		starlist = il_new(16);
		corrstarlist = il_new(16);
		starxylist = dl_new(16);

		// Find which ones in range are inside the image rectangle.
		Ngood = 0;
		for (j=0; j<Nindex; j++) {
			int starnum = starinds[j];
			double x, y;
			if (!sip_xyzarr2pixelxy(&sip, xyzs + j*3, &x, &y))
				continue;
			if ((x < 0) || (y < 0) || (x >= W) || (y >= H))
				continue;
			il_append(starlist, starnum);
			dl_append(starxylist, x);
			dl_append(starxylist, y);
			// compact this array...
			starinds[Ngood] = starnum;
			Ngood++;
		}
		logmsg("Found %i index stars inside the field.\n", il_size(starlist));

		assert(Ngood == il_size(starlist));
		assert(Ngood * 2 == dl_size(starxylist));

		istars = malloc(Ngood * sizeof(indexstar_t));

		// Now find correspondences between index objects and field objects.
		// Build a tree out of the field objects (in pixel space)
        // We make a copy of fieldxy because the copy gets permuted in this process.
        {
            double* fxycopy = malloc(Nfield * 2 * sizeof(double));
            memcpy(fxycopy, fieldxy, Nfield * 2 * sizeof(double));
            ftree = kdtree_build(NULL, fxycopy, Nfield, 2, Nleaf, KDTT_DOUBLE, KD_BUILD_SPLIT);
        }
		if (!ftree) {
			logmsg("Failed to build kdtree.\n");
			exit(-1);
		}

		// Search for correspondences
		/*
		 corr_i2f = intmap_new(sizeof(int), 4, 256, 0);
		 corr_f2i = intmap_new(sizeof(int), 4, 256, 0);
		 */
		ncorr = 0;
		nindexcorr = 0;
		indstarswithcorrs = il_new(16);
		for (j=0; j<dl_size(starxylist)/2; j++) {
			double xy[2];
			kdtree_qres_t* res;
			int nn;
			double nnd2;
			corr_t c;
			xy[0] = dl_get(starxylist, j*2+0);
			xy[1] = dl_get(starxylist, j*2+1);

			// kdtree check.
			/*
			 int k;
			 for (k=0; k<Nfield; k++) {
			 double d2 = distsq(fieldxy + 2*k, xy, 2);
			 if (d2 < pixr2) {
			 logverb("  index star at (%.1f, %.1f) and field star at (%.1f, %.1f)\n", xy[0], xy[1],
			 fieldxy[2*k+0], fieldxy[2*k+1]);
			 }
			 }
			 */

			nn = kdtree_nearest_neighbour(ftree, xy, &nnd2);
			logverb("  Index star at (%.1f, %.1f): nearest field star is %g pixels away.\n",
					xy[0], xy[1], sqrt(nnd2));

			res = kdtree_rangesearch_options(ftree, xy, pixr2 * nsigma*nsigma, KD_OPTIONS_SMALL_RADIUS | KD_OPTIONS_COMPUTE_DISTS | KD_OPTIONS_SORT_DISTS);

			istars[j].star = il_get(starlist, j);
			istars[j].starx = dl_get(starxylist, 2*j+0);
			istars[j].stary = dl_get(starxylist, 2*j+1);
			istars[j].corrs = pl_new(16);
			istars[j].allquads = il_new(16);

			if (!res || !res->nres)
				continue;

			for (k=0; k<res->nres; k++) {
				corr_t* cc;
				memset(&c, 0, sizeof(c));
				c.star = il_get(starlist, j);
				c.field = res->inds[k];
				c.starx = dl_get(starxylist, 2*j+0);
				c.stary = dl_get(starxylist, 2*j+1);
				c.fieldx = fieldxy[2*res->inds[k]+0];
				c.fieldy = fieldxy[2*res->inds[k]+1];
				c.dist = sqrt(res->sdists[k]);
				// add to master list, recording ptr.
				cc = bl_append(corrs, &c);
				pl_append(istars[j].corrs, cc);
			}

			/*
			 for (k=0; k<res->nres; k++) {
			 intmap_append(corr_i2f, il_get(starlist, j), res->inds + k);
			 }
			 */

			ncorr += res->nres;
			nindexcorr++;
			kdtree_free_query(res);
			il_append(indstarswithcorrs, j);

			il_append(corrstarlist, il_get(starlist, j));
		}
		logmsg("Found %i index stars with corresponding field stars.\n", nindexcorr);
		logmsg("Found %i total index star correspondences\n", ncorr);

		{
			double* ixycopy = malloc(Ngood * 2 * sizeof(double));
			dl_copy(starxylist, 0, Ngood * 2, ixycopy);
			itree = kdtree_build(NULL, ixycopy, Ngood, 2, Nleaf, KDTT_DOUBLE, KD_BUILD_SPLIT);
		}
		if (!itree) {
			logmsg("Failed to build index kdtree.\n");
			exit(-1);
		}
		for (j=0; j<Nfield; j++) {
			double* fxy;
			int nn;
			kdtree_qres_t* res;
			double nnd2;

			fxy = fieldxy + 2*j;

			logverb("Field object %i: (%g, %g)\n", j, fxy[0], fxy[1]);

			nn = kdtree_nearest_neighbour(itree, fxy, &nnd2);
			logverb("  Nearest index star is %g pixels away.\n",
					sqrt(nnd2));

			res = kdtree_rangesearch_options(itree, fxy, pixr2 * nsigma*nsigma, KD_OPTIONS_SMALL_RADIUS);
			if (!res || !res->nres) {
				logverb("  No index stars within %g pixels\n", sqrt(pixr2)*nsigma);
				continue;
			}
			logverb("  %i index stars within %g pixels\n", res->nres, sqrt(pixr2)*nsigma);

			/*
			 for (k=0; k<res->nres; k++) {
			 intmap_append(corr_f2i, j, il_access(starlist, res->inds[k]));
			 }
			 */
			//intmap_append(corr_f2i, res->inds[k], il_access(starlist, j));
			kdtree_free_query(res);
		}


		if (log_get_level() >= LOG_VERB) {
			// See what quads could be built from the index stars with correspondences.
			double* corrxy;
			int k, N = il_size(indstarswithcorrs);
			corrxy = malloc(N * 2 * sizeof(double));
			for (j=0; j<N; j++) {
				int ind = il_get(indstarswithcorrs, j);
				corrxy[2*j+0] = dl_get(starxylist, ind*2+0);
				corrxy[2*j+1] = dl_get(starxylist, ind*2+1);
			}
			for (j=0; j<N; j++) {
				for (k=0; k<j; k++) {
					int m;
					double q2 = distsq(corrxy + j*2, corrxy + k*2, 2);
					double qc[2];
					int nvalid;
					qc[0] = (corrxy[j*2+0] + corrxy[k*2+0]) / 2.0;
					qc[1] = (corrxy[j*2+1] + corrxy[k*2+1]) / 2.0;
					nvalid = 0;
					for (m=0; m<N; m++) {
						if (m == j || m == k)
							continue;
						if (distsq(qc, corrxy + m*2, 2) < q2/4.0)
							nvalid++;
					}
					logverb("  Quad diameter: %g pix (%g arcmin): %i stars in the circle.\n",
							sqrt(q2), sqrt(q2) * wcsscale / 60.0, nvalid);
				}
			}
			free(corrxy);
		}

		uniqquadlist = il_new(16);
		quadlist = il_new(16);

		// For each index star, find the quads of which it is part.
		for (j=0; j<il_size(starlist); j++) {
			int k;
			int starnum = il_get(starlist, j);
			if (qidxfile_get_quads(qidx, starnum, &quads, &nquads)) {
				logmsg("Failed to get quads for star %i.\n", starnum);
				exit(-1);
			}
			//logmsg("star %i is involved in %i quads.\n", starnum, nquads);
			for (k=0; k<nquads; k++) {
				il_insert_ascending(quadlist, quads[k]);
				il_insert_unique_ascending(uniqquadlist, quads[k]);

				il_append(istars[j].allquads, quads[k]);
			}
		}
		logmsg("Found %i quads partially contained in the field.\n", il_size(uniqquadlist));

        dimquads = quadfile_dimquads(indx->quads);
        dimcodes = dimquad2dimcode(dimquads);

		// Find quads that are fully contained in the image.
		fullquadlist = il_new(16);
		for (j=0; j<il_size(uniqquadlist); j++) {
			int quad = il_get(uniqquadlist, j);
			int ind = il_index_of(quadlist, quad);
			if (log_get_level() >= LOG_VERB) {
				int k, nin=0, ncorr=0;
				unsigned int stars[dimquads];
				for (k=0; k<dimquads; k++) {
					if (ind+k >= il_size(quadlist))
						break;
					if (il_get(quadlist, ind+k) != quad)
						break;
					nin++;
				}
				quadfile_get_stars(indx->quads, quad, stars);
				for (k=0; k<dimquads; k++)
					if (il_contains(corrstarlist, stars[k]))
						ncorr++;
				debug("Quad %i has %i stars in the field (%i with correspondences).\n", quad, nin, ncorr);
			}
			if (ind + (dimquads-1) >= il_size(quadlist))
				continue;
			if (il_get(quadlist, ind+(dimquads-1)) != quad)
				continue;
			il_append(fullquadlist, quad);
		}
		logmsg("Found %i quads fully contained in the field.\n", il_size(fullquadlist));


		{
			double* sortdata = startree_get_data_column(indx->starkd, "r", starinds, Ngood);
			int k;
			int* sweeps = malloc(Ngood * sizeof(int));
			int* perm = NULL;
			int* maxsweep = malloc(il_size(uniqquadlist) * sizeof(int));
			unsigned int stars[DQMAX];

			// DEBUG -- how does "sweep" correspond to column "r" ?
			// (answer: small sweep --> small r, pretty much.)
			for (k=0; k<Ngood; k++) {
				sweeps[k] = startree_get_sweep(indx->starkd, starinds[k]);
			}
			perm = permuted_sort(sweeps, sizeof(int), compare_ints_asc, NULL, Ngood);
			if (sortdata) {
				for (k=0; k<Ngood; k++) {
					logverb("  sweep %i, r mag %g\n", sweeps[perm[k]], sortdata[perm[k]]);
				}
			}

			/*
			 for (k=0; k<Ngood; k++) {
			 int star = starinds[perm[k]];
			 double sd = sortdata[perm[k]];
			 il* lst;
			 lst = (il*)intmap_find(corr_i2f, star, FALSE);
			 logmsg("Index star %i (%i), sweep %i, mag %g.  %i correspondences\n", k, star, sweeps[perm[k]], sd, (lst ? il_size(lst) : 0));
			 if (lst) {
			 for (j=0; j<il_size(lst); j++) {
			 logmsg("  field star %i\n", il_get(lst, j));
			 }
			 }
			 }
			 */


			free(perm);


			for (j=0; j<il_size(uniqquadlist); j++) {
				int quad = il_get(uniqquadlist, j);
				int ms = 0;
				quadfile_get_stars(indx->quads, quad, stars);
				for (k=0; k<dimquads; k++) {
					int sweep = startree_get_sweep(indx->starkd, stars[k]);
					ms = MAX(ms, sweep);
				}
				maxsweep[j] = ms;
			}
			perm = permuted_sort(maxsweep, sizeof(int), compare_ints_asc, NULL, il_size(uniqquadlist));

			logverb("\nQuads completely within the image:\n");
			for (j=0; j<il_size(uniqquadlist); j++) {
				int quad = il_get(uniqquadlist, perm[j]);
				if (!il_contains(fullquadlist, quad))
					continue;
				quadfile_get_stars(indx->quads, quad, stars);
				logverb("(full) quad %i: made from stars with sweeps:", quad);
				for (k=0; k<dimquads; k++) {
					int sweep = startree_get_sweep(indx->starkd, stars[k]);
					logverb(" %i", sweep);
				}
				logverb("\n");
			}

			logverb("\nNearby quads, not completely within the image:\n");
			for (j=0; j<il_size(uniqquadlist); j++) {
				int quad = il_get(uniqquadlist, perm[j]);
				if (il_contains(fullquadlist, quad))
					continue;
				quadfile_get_stars(indx->quads, quad, stars);
				logverb("(near) quad %i: made from stars with sweeps:", quad);
				for (k=0; k<dimquads; k++) {
					int sweep = startree_get_sweep(indx->starkd, stars[k]);
					logverb(" %i", sweep);
				}
				logverb("\n");
			}

			free(perm);
			free(maxsweep);

			free(sweeps);
			free(sortdata);
		}



		if (quadplotfn) {
			for (j=0; j<il_size(fullquadlist); j++) {
				char* fn;
				int SW, SH;
				int k;
				plot_args_t* pargs;
				plotxy_t* pxy;
				plotindex_t* pind;
				int quad;
				unsigned int stars[dimquads];
				double quadxy[DQMAX * 2];

				asprintf(&fn, quadplotfn, j);
				SW = (int)(quadplotscale * W);
				SH = (int)(quadplotscale * H);

				pargs = plotstuff_new();
				pargs->outformat = PLOTSTUFF_FORMAT_PNG;
				pargs->outfn = fn;
				plotstuff_set_size(pargs, SW, SH);

				plotstuff_set_color(pargs, "black");
				plotstuff_plot_layer(pargs, "fill");
				pargs->lw = 2.0;

				pxy = plot_xy_get(pargs);
				pxy->scale = quadplotscale;

				plotstuff_set_color(pargs, "red");
				plotstuff_set_markersize(pargs, 6);
				for (k=0; k<Nfield; k++)
					plot_xy_vals(pxy, fieldxy[2*k+0], fieldxy[2*k+1]);
				plotstuff_plot_layer(pargs, "xy");

				plotstuff_set_color(pargs, "green");
				//plotstuff_set_marker(pargs, "crosshair");
				plotstuff_set_markersize(pargs, 4);
				for (k=0; k<dl_size(starxylist)/2; k++)
					plot_xy_vals(pxy, dl_get(starxylist, 2*k+0), dl_get(starxylist, 2*k+1));
				plotstuff_plot_layer(pargs, "xy");

				pind = plot_index_get(pargs);

				quad = il_get(fullquadlist, j);
				quadfile_get_stars(indx->quads, quad, stars);
				for (k=0; k<dimquads; k++) {
					int m = il_index_of(starlist, stars[k]);
					quadxy[k*2+0] = dl_get(starxylist, 2*m+0) * quadplotscale;
					quadxy[k*2+1] = dl_get(starxylist, 2*m+1) * quadplotscale;
				}

				plot_quad_xy(pargs->cairo, quadxy, dimquads);
				cairo_stroke(pargs->cairo);

				plotstuff_output(pargs);

				plotstuff_free(pargs);
				logmsg("Wrote %s\n", fn);
				free(fn);
			}
		}


		// Find the stars that are in quads.
		starsinquadslist = il_new(16);
		for (j=0; j<il_size(uniqquadlist); j++) {
            int k;
			unsigned int stars[dimquads];
			int quad = il_get(uniqquadlist, j);
			quadfile_get_stars(indx->quads, quad, stars);
            for (k=0; k<dimquads; k++)
                il_insert_unique_ascending(starsinquadslist, stars[k]);
		}
		logmsg("Found %i index stars involved in quads (with at least one star contained in the image).\n", il_size(starsinquadslist));

		// Find the stars that are in quads that are completely contained.
		starsinquadsfull = il_new(16);
		for (j=0; j<il_size(fullquadlist); j++) {
            int k;
			unsigned int stars[dimquads];
			int quad = il_get(fullquadlist, j);
			quadfile_get_stars(indx->quads, quad, stars);
            for (k=0; k<dimquads; k++)
                il_insert_unique_ascending(starsinquadsfull, stars[k]);
		}
		logmsg("Found %i index stars involved in quads (with all stars contained in the image).\n", il_size(starsinquadsfull));

		// For each index object involved in quads, search for a correspondence.
		corrstars = il_new(16);
        corrfield = il_new(16);
		for (j=0; j<il_size(starsinquadslist); j++) {
			int star;
			double sxyz[3];
			double sxy[2];
			kdtree_qres_t* fres;
			star = il_get(starsinquadslist, j);
			if (startree_get(indx->starkd, star, sxyz)) {
				logmsg("Failed to get position for star %i.\n", star);
				exit(-1);
			}
			if (!sip_xyzarr2pixelxy(&sip, sxyz, sxy, sxy+1)) {
				logmsg("SIP backward for star %i.\n", star);
				exit(-1);
			}
			fres = kdtree_rangesearch_options(ftree, sxy, pixr2 * nsigma*nsigma,
											  KD_OPTIONS_SMALL_RADIUS | KD_OPTIONS_SORT_DISTS);
			if (!fres || !fres->nres)
				continue;
			if (fres->nres > 1) {
				logmsg("%i matches for star %i.\n", fres->nres, star);
			}

			il_append(corrstars, star);
            il_append(corrfield, fres->inds[0]); //kdtree_permute(ftree, fres->inds[0]));

			logverb("  star %i: dist %g to field star %i\n", star, sqrt(fres->sdists[0]), fres->inds[0]);

            /*{
              double fx, fy;
              int fi;
              fi = il_get(corrfield, il_size(corrfield)-1);
              fx = fieldxy[2*fi + 0];
              fy = fieldxy[2*fi + 1];
              logmsg("star   %g,%g\n", sxy[0], sxy[1]);
              logmsg("field  %g,%g\n", fx, fy);
              }*/
		}
		logmsg("Found %i correspondences for stars involved in quads (with at least one star in the field).\n",
				il_size(corrstars));

		// Find quads built only from stars with correspondences.
		corrquads = il_new(16);
		corruniqquads = il_new(16);
		for (j=0; j<il_size(corrstars); j++) {
			int k;
			int starnum = il_get(corrstars, j);
			if (qidxfile_get_quads(qidx, starnum, &quads, &nquads)) {
				logmsg("Failed to get quads for star %i.\n", starnum);
				exit(-1);
			}
			for (k=0; k<nquads; k++) {
				il_insert_ascending(corrquads, quads[k]);
				il_insert_unique_ascending(corruniqquads, quads[k]);
			}
		}

		// Find quads that are fully contained in the image.
		logverb("Looking at quads built from stars with correspondences...\n");
		corrfullquads = il_new(16);

		for (j=0; j<il_size(corruniqquads); j++) {
			int quad = il_get(corruniqquads, j);
			int ind = il_index_of(corrquads, quad);

			if (log_get_level() >= LOG_VERB) {
				int k, nin=0;
				for (k=0; k<dimquads; k++) {
					if (ind+k >= il_size(corrquads))
						break;
					if (il_get(corrquads, ind+k) != quad)
						break;
					nin++;
				}
				debug("  Quad %i has %i stars with correspondences.\n", quad, nin);
			}

			if (ind + (dimquads-1) >= il_size(corrquads))
				continue;
			if (il_get(corrquads, ind+(dimquads-1)) != quad)
				continue;
			il_append(corrfullquads, quad);
		}
		logmsg("Found %i quads built from stars with correspondencs, fully contained in the field.\n", il_size(corrfullquads));

		foundquads = bl_new(16, sizeof(foundquad_t));

		for (j=0; j<il_size(corrfullquads); j++) {
			unsigned int stars[dimquads];
			int k;
			int ind;
            double realcode[dimcodes];
            double fieldcode[dimcodes];
            tan_t wcs;
            MatchObj mo;
			foundquad_t fq;
			double codedist;

			int quad = il_get(corrfullquads, j);

            memset(&mo, 0, sizeof(MatchObj));

			quadfile_get_stars(indx->quads, quad, stars);

            codetree_get(indx->codekd, quad, realcode);

            for (k=0; k<dimquads; k++) {
                int find;
                // position of corresponding field star.
                ind = il_index_of(corrstars, stars[k]);
                assert(ind >= 0);
                find = il_get(corrfield, ind);
                mo.quadpix[k*2 + 0] = fieldxy[find*2 + 0];
                mo.quadpix[k*2 + 1] = fieldxy[find*2 + 1];
                // index star xyz.
                startree_get(indx->starkd, stars[k], mo.quadxyz + 3*k);

                mo.star[k] = stars[k];
                mo.field[k] = find;
            }

			logmsg("\nquad #%i (quad id %i): stars", j, quad);
            for (k=0; k<dimquads; k++)
                logmsg(" %i", mo.field[k]);
            logmsg("\n");

            codefile_compute_field_code(mo.quadpix, fieldcode, dimquads);

			//logmsg(" code (index): %.3f,%.3f,%.3f,%.3f\n", realcode[0], realcode[1], realcode[2], realcode[3]);
			//logmsg(" code (field): %.3f,%.3f,%.3f,%.3f\n", fieldcode[0], fieldcode[1], fieldcode[2], fieldcode[3]);

			quad_enforce_invariants(mo.star, realcode, dimquads, dimcodes);
			quad_enforce_invariants(mo.field, fieldcode, dimquads, dimcodes);
			codedist = sqrt(distsq(realcode, fieldcode, dimcodes));
            logmsg("  code distance (normal parity): %g\n", codedist);

			quad_flip_parity(fieldcode, fieldcode, dimcodes);
			quad_enforce_invariants(mo.star, realcode, dimquads, dimcodes);
			quad_enforce_invariants(mo.field, fieldcode, dimquads, dimcodes);
			codedist = sqrt(distsq(realcode, fieldcode, dimcodes));
            logmsg("  code distance (flip parity): %g\n", codedist);

            fit_tan_wcs(mo.quadxyz, mo.quadpix, dimquads, &wcs, NULL);
			wcs.imagew = W;
			wcs.imageh = H;

			{
				double pscale = tan_pixel_scale(&wcs);
				logmsg("  quad scale: %g arcsec/pix -> field size %g x %g arcmin\n",
					   pscale, arcsec2arcmin(pscale * W), arcsec2arcmin(pscale * H));
			}

			logverb("Distances between corresponding stars:\n");
            for (k=0; k<il_size(corrstars); k++) {
				int star = il_get(corrstars, k);
				int field = il_get(corrfield, k);
				double* fxy;
				double xyz[2];
				double sxy[2];
				double d;
				anbool ok;

				startree_get(indx->starkd, star, xyz);
				ok = tan_xyzarr2pixelxy(&wcs, xyz, sxy, sxy+1);
				fxy = fieldxy + 2*field;
				d = sqrt(distsq(fxy, sxy, 2));

				logverb("  correspondence: field star %i: distance %g pix\n", field, d);
			}

			logmsg("  running verify() with the found WCS:\n");

			//log_set_level(LOG_ALL);
			log_set_level(log_get_level() + 1);

            {
                double llxyz[3];
                verify_field_t* vf;
                double verpix2 = pixr2;

                tan_pixelxy2xyzarr(&wcs, W/2.0, H/2.0, mo.center);
                tan_pixelxy2xyzarr(&wcs, 0, 0, llxyz);
                mo.radius = sqrt(distsq(mo.center, llxyz, 3));
				mo.radius_deg = dist2deg(mo.radius);
				mo.scale = tan_pixel_scale(&wcs);
                mo.dimquads = dimquads;
                memcpy(&mo.wcstan, &wcs, sizeof(tan_t));
                mo.wcs_valid = TRUE;

                vf = verify_field_preprocess(xy);

                verify_hit(indx->starkd, indx->cutnside, &mo, NULL, vf, verpix2,
                           DEFAULT_DISTRACTOR_RATIO, W, H,
                           log(1e-100), log(1e9), HUGE_VAL, TRUE, FALSE);

                verify_field_free(vf);
            }

			log_set_level(loglvl);

            logmsg("Verify log-odds %g (odds %g)\n", mo.logodds, exp(mo.logodds));


			memset(&fq, 0, sizeof(foundquad_t));
			//memcpy(fq.stars, stars, dimquads);
			fq.codedist = codedist;
			fq.logodds = mo.logodds;
			fq.pscale = tan_pixel_scale(&wcs);
			fq.quadnum = quad;
			memcpy(&(fq.mo), &mo, sizeof(MatchObj));
			bl_append(foundquads, &fq);
		}

		// Sort the found quads by star index...
		bl_sort(foundquads, sort_fq_by_stars);

		logmsg("\n\n\n");
		logmsg("Sorted by star number (ie, the order they'll be found):\n\n");
		int ngood = 0;
		double maxcodedist = 0.01;
		double minlogodds = log(1e9);
		logmsg("Assuming max code distance %g and min logodds %g\n", maxcodedist, minlogodds);

		for (j=0; j<bl_size(foundquads); j++) {
			int k;
			foundquad_t* fq = bl_access(foundquads, j);
			if (fq->codedist <= maxcodedist && fq->logodds >= minlogodds) {
				ngood++;
				logmsg("*** ");
			}
			logmsg("quad #%i: stars", fq->quadnum);
			for (k=0; k<fq->mo.dimquads; k++) {
				logmsg(" %i", fq->mo.field[k]);
			}
			logmsg("\n");
			logmsg("  codedist %g\n", fq->codedist);
			logmsg("  logodds %g (odds %g)\n", fq->logodds, exp(fq->logodds));
		}
		printf("\nTotal of %i quads that would solve the image.\n", ngood);



		if (objplotfn) {
			il* closeistars = il_new(256);
			int SW, SH;
			plot_args_t* pargs;
			sip_t scalesip;

			SW = (int)(quadplotscale * W);
			SH = (int)(quadplotscale * H);

			pargs = plotstuff_new();
			pargs->outformat = PLOTSTUFF_FORMAT_PNG;
			plotstuff_set_size(pargs, SW, SH);

			sip_copy(&scalesip, &sip);
			tan_transform(&scalesip.wcstan, &scalesip.wcstan,
						  1, scalesip.wcstan.imagew,
						  1, scalesip.wcstan.imageh,
						  quadplotscale);
			plotstuff_set_wcs_sip(pargs, &scalesip);

			for (j=0; j<Nfield; j++) {
				char* fn;
				int k,m;
				plotxy_t* pxy;
				plotindex_t* pind;
				int quad;
				unsigned int stars[dimquads];
				//double quadxy[DQMAX * 2];
				double* fxy;
				int nn;
				double nnd2;
				kdtree_qres_t* res;
				double x,y;
				int closestart;

				fxy = fieldxy + 2*j;

				logverb("Field object %i: (%g, %g)\n", j, fxy[0], fxy[1]);

				nn = kdtree_nearest_neighbour(itree, fxy, &nnd2);
				logverb("  Nearest index star is %g pixels away.\n",
						sqrt(nnd2));

				res = kdtree_rangesearch_options(itree, fxy, pixr2 * nsigma*nsigma, KD_OPTIONS_SMALL_RADIUS);
				if (!res || !res->nres) {
					logverb("  No index stars within %g pixels\n", sqrt(pixr2)*nsigma);
					continue;
				}

				logverb("  %i index stars within %g pixels\n", res->nres, sqrt(pixr2)*nsigma);

				// NOTE, "closeistars" contains indices in "starlist" / "starxy" directly.

				closestart = il_size(closeistars);

				for (k=0; k<res->nres; k++) {
					il_append(closeistars, res->inds[k]);
				}

				asprintf(&fn, objplotfn, j);
				pargs->outfn = fn;

				plotstuff_set_color(pargs, "black");
				plotstuff_plot_layer(pargs, "fill");
				pargs->lw = 2.0;

				pxy = plot_xy_get(pargs);
				pxy->scale = quadplotscale;

				plotstuff_set_color(pargs, "green");
				//plotstuff_set_marker(pargs, "crosshair");

				plotstuff_set_markersize(pargs, 5);
				for (k=0; k<il_size(closeistars); k++) {
					int ind = il_get(closeistars, k);
					double x,y;
					assert(ind >= 0);
					x = dl_get(starxylist, 2*ind+0);
					y = dl_get(starxylist, 2*ind+1);
					plot_xy_vals(pxy, x, y);
				}
				plotstuff_plot_layer(pargs, "xy");
				plot_xy_clear_list(pxy);

				plotstuff_set_color(pargs, "darkgreen");
				plotstuff_set_markersize(pargs, 2);
				for (k=0; k<il_size(starlist); k++) {
					//int star = il_get(starlist, k);
					//if (il_contains(closeistars, star))
					if (il_contains(closeistars, k))
						continue;
					x = dl_get(starxylist, 2*k+0);
					y = dl_get(starxylist, 2*k+1);
					plot_xy_vals(pxy, x, y);
				}
				plotstuff_plot_layer(pargs, "xy");
				plot_xy_clear_list(pxy);

				pind = plot_index_get(pargs);

				// All quads incident on index stars near field stars we've looked at (cumulative)
				for (k=0; k<il_size(uniqquadlist); k++) {
					int nclose = 0;
					anbool thistime = FALSE;
					quad = il_get(uniqquadlist, k);
					quadfile_get_stars(indx->quads, quad, stars);
					for (m=0; m<dimquads; m++) {
						int cind;
						int ind = il_index_of(starlist, stars[m]);
						if (ind == -1)
							continue;
						cind = il_index_of(closeistars, ind);
						if (cind < 0)
							continue;
						nclose++;
						if (cind >= closestart)
							thistime = TRUE;
						//logverb("quad %i: star %i, starlist ind %i, close ind %i (start %i)\n", quad, stars[m], ind, cind, closestart);
					}
					if (!nclose)
						continue;

					/*
					 for (m=0; m<dimquads; m++) {
					 int ind = il_index_of(starlist, stars[m]);
					 assert(ind >= 0);
					 quadxy[m*2+0] = dl_get(starxylist, 2*ind+0) * quadplotscale;
					 quadxy[m*2+1] = dl_get(starxylist, 2*ind+1) * quadplotscale;
					 }
					 plot_quad_xy(pargs->cairo, quadxy, dimquads);
					 */

					if (thistime)
						logverb("Quad %i: made from %i stars we've seen so far.\n", quad, nclose);

					if (thistime) {
						//plotstuff_set_color(pargs, "green");
						cairo_set_color(pargs->cairo, "green");
					} else {
						//plotstuff_set_color(pargs, "darkgreen");
						cairo_set_color(pargs->cairo, "darkgreen");
					}

					plot_index_plotquad(pargs->cairo, pargs, pind, indx, quad, dimquads);
				}


				plotstuff_set_color(pargs, "red");
				plotstuff_set_markersize(pargs, 6);
				for (k=0; k<=j; k++)
					plot_xy_vals(pxy, fieldxy[2*k+0], fieldxy[2*k+1]);
				plotstuff_plot_layer(pargs, "xy");
				plot_xy_clear_list(pxy);

				plotstuff_set_markersize(pargs, 10);
				plot_xy_vals(pxy, fxy[0], fxy[1]);
				plotstuff_plot_layer(pargs, "xy");
				plot_xy_clear_list(pxy);

				plotstuff_set_color(pargs, "darkred");
				plotstuff_set_markersize(pargs, 3);
				for (k=j+1; k<Nfield; k++)
					plot_xy_vals(pxy, fieldxy[2*k+0], fieldxy[2*k+1]);
				plotstuff_plot_layer(pargs, "xy");
				plot_xy_clear_list(pxy);

				plotstuff_output(pargs);

				logmsg("Wrote %s\n", fn);
				free(fn);

				kdtree_free_query(res);
			}

			plotstuff_free(pargs);
			free(itree->data.any);
			kdtree_free(itree);
		}

		il_free(fullquadlist);
		il_free(uniqquadlist);
		il_free(quadlist);
		il_free(starlist);
		il_free(corrstarlist);
	}

	if (xylist_close(xyls)) {
		logmsg("Failed to close XYLS file.\n");
	}
	return 0;
}
