/*
 This file is part of the Astrometry.net suite.
 Copyright 2007-2009 Dustin Lang, Keir Mierle and Sam Roweis.
 Copyright 2010, 2011, 2012 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
 */


/**
 A command-line interface to the blind solver system.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libgen.h>
#include <errors.h>
#include <getopt.h>
#include <assert.h>

#include "boilerplate.h"
#include "an-bool.h"
#include "bl.h"
#include "ioutils.h"
#include "xylist.h"
#include "matchfile.h"
#include "fitsioutils.h"
#include "augment-xylist.h"
#include "an-opts.h"
#include "log.h"
#include "errors.h"
#include "anqfits.h"
#include "sip_qfits.h"
#include "sip-utils.h"
#include "wcs-rd2xy.h"
#include "new-wcs.h"
#include "scamp.h"

static an_option_t options[] = {
	{'h', "help",		   no_argument, NULL,
     "print this help message" },
	{'v', "verbose",       no_argument, NULL,
     "be more chatty -- repeat for even more verboseness" },
    {'D', "dir", required_argument, "directory",
     "place all output files in the specified directory"},
    {'o', "out", required_argument, "base-filename",
     "name the output files with this base name"},
    // DEPRECATED
    {'b', "backend-config", required_argument, "filename",
     "use this config file for the \"astrometry-engine\" program"},
    {'\x89', "config", required_argument, "filename",
     "use this config file for the \"astrometry-engine\" program"},
	{'(', "batch",  no_argument, NULL,
	 "run astrometry-engine once, rather than once per input file"},
	{'f', "files-on-stdin", no_argument, NULL,
     "read filenames to solve on stdin, one per line"},
	{'p', "no-plots",       no_argument, NULL,
     "don't create any plots of the results"},
	{'\x84', "plot-scale",  required_argument, "scale",
	 "scale the plots by this factor (eg, 0.25)"},
	{'\x85', "plot-bg",  required_argument, "filename (JPEG)",
	 //.jpg, .jpeg, .ppm, .pnm, .png)",
	 "set the background image to use for plots"},
    {'G', "use-wget",       no_argument, NULL,
     "use wget instead of curl"},
  	{'O', "overwrite",      no_argument, NULL,
     "overwrite output files if they already exist"},
    {'K', "continue",       no_argument, NULL,
     "don't overwrite output files if they already exist; continue a previous run"},
    {'J', "skip-solved",    no_argument, NULL,
     "skip input files for which the 'solved' output file already exists;"
     " NOTE: this assumes single-field input files"},
	{'\x87', "fits-image", no_argument, NULL,
	 "assume the input files are FITS images"},
    {'N', "new-fits",    required_argument, "filename",
     "output filename of the new FITS file containing the WCS header; \"none\" to not create this file"},
    {'Z', "kmz",            required_argument, "filename",
     "create KMZ file for Google Sky.  (requires wcs2kml)"},
    {'i', "scamp",          required_argument, "filename",
     "create image object catalog for SCAMP"},
    {'n', "scamp-config",   required_argument, "filename",
     "create SCAMP config file snippet"},
    {'U', "index-xyls",     required_argument, "filename",
     "output filename for xylist containing the image coordinate of stars from the index"},
	{'@', "just-augment",   no_argument, NULL,
	 "just write the augmented xylist files; don't run astrometry-engine."},
	{'\x88', "timestamp", no_argument, NULL,
	 "add timestamps to log messages"},
};

static void print_help(const char* progname, bl* opts) {
	boilerplate_help_header(stdout);
	printf("\nUsage:   %s [options]  [<image-file-1> <image-file-2> ...] [<xyls-file-1> <xyls-file-2> ...]\n"
           "\n"
           "You can specify http:// or ftp:// URLs instead of filenames.  The \"wget\" or \"curl\" program will be used to retrieve the URL.\n"
	       "\n", progname);
    printf("Options include:\n");
    opts_print_help(opts, stdout, augment_xylist_print_special_opts, NULL);
    printf("\n");
    printf("Note that most output files can be disabled by setting the filename to \"none\".\n"
           " (If you have a sick sense of humour and you really want to name your output\n"
		   "  file \"none\", you can use \"./none\" instead.)\n");
    printf("\n\n");
}

static int run_command(const char* cmd, anbool* ctrlc) {
	int rtn;
    logverb("Running: %s\n", cmd);
    fflush(NULL);
	rtn = system(cmd);
    fflush(NULL);
	if (rtn == -1) {
		SYSERROR("Failed to run command \"%s\"", cmd);
		return -1;
	}
	if (WIFSIGNALED(rtn)) {
        if (ctrlc && (WTERMSIG(rtn) == SIGTERM))
            *ctrlc = TRUE;
		return -1;
	}
	rtn = WEXITSTATUS(rtn);
	if (rtn) {
		ERROR("Command exited with exit status %i", rtn);
        ERROR("Command was: \"%s\"\n", cmd);
    }
	return rtn;
}

static void append_escape(sl* list, const char* fn) {
    sl_append_nocopy(list, shell_escape(fn));
}
static void appendf_escape(sl* list, const char* fmt, const char* fn) {
    char* esc = shell_escape(fn);
    sl_appendf(list, fmt, esc);
    free(esc);
}
static void append_executable(sl* list, const char* fn, const char* me) {
    char* exec = find_executable(fn, me);
    if (!exec) {
        ERROR("Error, couldn't find executable \"%s\"", fn);
        exit(-1);
    }
    sl_append_nocopy(list, shell_escape(exec));
    free(exec);
}

static int write_kmz(const augment_xylist_t* axy, const char* kmzfn,
                     const char* tempdir, sl* tempdirs, sl* tempfiles) {
    char* pngfn = NULL;
    char* kmlfn = NULL;
    char* warpedpngfn = NULL;
    char* basekmlfn = NULL;
    char* basewarpedpngfn = NULL;
    char* tmpdir;
    char* cmd = NULL;
    sl* cmdline = sl_new(16);
    char* wcsbase = NULL;

    tmpdir = create_temp_dir("kmz", tempdir);
    if (!tmpdir) {
        ERROR("Failed to create temp dir for KMZ output");
        sl_free2(cmdline);
        return -1;
    }
    sl_append_nocopy(tempdirs, tmpdir);

    pngfn = create_temp_file("png", tempdir);
    sl_append_nocopy(tempfiles, pngfn);

    sl_append(cmdline, "pnmtopng");
    append_escape(cmdline, axy->pnmfn);
    sl_append(cmdline, ">");
    append_escape(cmdline, pngfn);
    // run it
    cmd = sl_implode(cmdline, " ");
    sl_remove_all(cmdline);
    logverb("Running:\n  %s\n", cmd);
    if (run_command_get_outputs(cmd, NULL, NULL)) {
        ERROR("pnmtopng failed");
        free(cmd);
        sl_free2(cmdline);
        return -1;
    }
    free(cmd);

    basekmlfn       = "doc.kml";
    basewarpedpngfn = "warped.png";
    kmlfn       = sl_appendf(tempfiles, "%s/%s", tmpdir, basekmlfn);
    warpedpngfn = sl_appendf(tempfiles, "%s/%s", tmpdir, basewarpedpngfn);
    // delete the wcs we create with "cp" below.
	assert(axy->wcsfn);
    wcsbase = basename_safe(axy->wcsfn);
    sl_appendf(tempfiles, "%s/%s", tmpdir, wcsbase);
    free(wcsbase);

    logverb("Trying to run wcs2kml to generate KMZ output.\n");
    sl_appendf(cmdline, "cp %s %s; cd %s; ", axy->wcsfn, tmpdir, tmpdir);
    sl_append(cmdline, "wcs2kml");
    // FIXME - if parity?
    sl_append(cmdline, "--input_image_origin_is_upper_left");
    appendf_escape(cmdline, "--fitsfile=%s", axy->wcsfn);
    appendf_escape(cmdline, "--imagefile=%s", pngfn);
    appendf_escape(cmdline, "--kmlfile=%s", basekmlfn);
    appendf_escape(cmdline, "--outfile=%s", basewarpedpngfn);
    // run it
    cmd = sl_implode(cmdline, " ");
    sl_remove_all(cmdline);
    logverb("Running:\n  %s\n", cmd);
    if (run_command_get_outputs(cmd, NULL, NULL)) {
        ERROR("wcs2kml failed");
        free(cmd);
        sl_free2(cmdline);
        return -1;
    }
    free(cmd);

    sl_append(cmdline, "zip");
    sl_append(cmdline, "-j"); // no paths, just filenames
    //if (!verbose)
    //sl_append(cmdline, "-q");
    // pipe to stdout, because zip likes to add ".zip" to the
    // output filename, and provides no way to turn off this
    // behaviour.
    sl_append(cmdline, "-");
    appendf_escape(cmdline, "%s", warpedpngfn);
    appendf_escape(cmdline, "%s", kmlfn);
    sl_append(cmdline, ">");
    append_escape(cmdline, kmzfn);

    // run it
    cmd = sl_implode(cmdline, " ");
    sl_remove_all(cmdline);
    logverb("Running:\n  %s\n", cmd);
    if (run_command_get_outputs(cmd, NULL, NULL)) {
        ERROR("zip failed");
        free(cmd);
        sl_free2(cmdline);
        return -1;
    }
    free(cmd);
    sl_free2(cmdline);
    return 0;
}

static int plot_source_overlay(augment_xylist_t* axy, const char* me,
                               const char* objsfn, double plotscale, const char* bgfn) {
    // plotxy -i harvard.axy -I /tmp/pnm -C red -P -w 2 -N 50 | plotxy -w 2 -r 3 -I - -i harvard.axy -C red -n 50 > harvard-objs.png
    sl* cmdline = sl_new(16);
    char* cmd;
    anbool ctrlc;
	char* imgfn;

	if (bgfn) {
		append_executable(cmdline, "jpegtopnm", me);
		append_escape(cmdline, bgfn);
		sl_append(cmdline, "|");
		imgfn = "-";
	} else {
		imgfn = axy->pnmfn;
		if (axy->imagefn && plotscale != 1.0) {
			append_executable(cmdline, "pnmscale", me);
			sl_appendf(cmdline, "%f", plotscale);
			append_escape(cmdline, axy->pnmfn);
			sl_append(cmdline, "|");
			imgfn = "-";
		}
	}

    append_executable(cmdline, "plotxy", me);
    if (imgfn) {
        sl_append(cmdline, "-I");
        append_escape(cmdline, imgfn);
    } else {
		sl_appendf(cmdline, "-W %i -H %i", (int)(plotscale * axy->W), (int)(plotscale * axy->H));
	}
    sl_append(cmdline, "-i");
    append_escape(cmdline, axy->outfn);
    if (axy->xcol) {
        sl_append(cmdline, "-X");
        append_escape(cmdline, axy->xcol);
    }
    if (axy->ycol) {
        sl_append(cmdline, "-Y");
        append_escape(cmdline, axy->ycol);
    }
	if (plotscale != 1.0) {
		sl_append(cmdline, "-S");
		sl_appendf(cmdline, "%f", plotscale);
	}
    sl_append(cmdline, "-C red -w 2 -N 50 -x 1 -y 1");
    sl_append(cmdline, "-P");
    sl_append(cmdline, "|");

    append_executable(cmdline, "plotxy", me);
    sl_append(cmdline, "-i");
    append_escape(cmdline, axy->outfn);
    if (axy->xcol) {
        sl_append(cmdline, "-X");
        append_escape(cmdline, axy->xcol);
    }
    if (axy->ycol) {
        sl_append(cmdline, "-Y");
        append_escape(cmdline, axy->ycol);
    }
    sl_append(cmdline, "-I - -w 2 -r 3 -C red -n 50 -N 200 -x 1 -y 1");
	if (plotscale != 1.0) {
		sl_append(cmdline, "-S");
		sl_appendf(cmdline, "%f", plotscale);
	}

    sl_append(cmdline, ">");
    append_escape(cmdline, objsfn);

    cmd = sl_implode(cmdline, " ");
    sl_free2(cmdline);

    if (run_command(cmd, &ctrlc)) {
        ERROR("Plotting command %s", (ctrlc ? "was cancelled" : "failed"));
        if (!ctrlc) {
            errors_print_stack(stdout);
            errors_clear_stack();
        }
        free(cmd);
        return -1;
    }
    free(cmd);
    return 0;
}

static int plot_index_overlay(augment_xylist_t* axy, const char* me,
                              const char* indxylsfn, const char* redgreenfn,
							  double plotscale, const char* bgfn) {
    sl* cmdline = sl_new(16);
    char* cmd;
    matchfile* mf;
    MatchObj* mo;
    int i;
    anbool ctrlc;
	char* imgfn;

	assert(axy->matchfn);
    mf = matchfile_open(axy->matchfn);
    if (!mf) {
        ERROR("Failed to read matchfile %s", axy->matchfn);
        return -1;
    }
    // just read the first match...
    mo = matchfile_read_match(mf);
    if (!mo) {
        ERROR("Failed to read a match from matchfile %s", axy->matchfn);
        return -1;
    }

    // sources + index overlay
	imgfn = axy->pnmfn;

	if (bgfn) {
		append_executable(cmdline, "jpegtopnm", me);
		append_escape(cmdline, bgfn);
		sl_append(cmdline, "|");
		imgfn = "-";
	} else {
		if (axy->imagefn && plotscale != 1.0) {
			append_executable(cmdline, "pnmscale", me);
			sl_appendf(cmdline, "%f", plotscale);
			append_escape(cmdline, axy->pnmfn);
			sl_append(cmdline, "|");
			imgfn = "-";
		}
	}

    append_executable(cmdline, "plotxy", me);
    if (imgfn) {
        sl_append(cmdline, "-I");
        append_escape(cmdline, imgfn);
    } else {
		sl_appendf(cmdline, "-W %i -H %i", (int)(plotscale * axy->W), (int)(plotscale * axy->H));
	}
    sl_append(cmdline, "-i");
    append_escape(cmdline, axy->outfn);
    if (axy->xcol) {
        sl_append(cmdline, "-X");
        append_escape(cmdline, axy->xcol);
    }
    if (axy->ycol) {
        sl_append(cmdline, "-Y");
        append_escape(cmdline, axy->ycol);
    }
	if (plotscale != 1.0) {
		sl_append(cmdline, "-S");
		sl_appendf(cmdline, "%f", plotscale);
	}
    sl_append(cmdline, "-C red -w 2 -r 6 -N 200 -x 1 -y 1");
    sl_append(cmdline, "-P");
    sl_append(cmdline, "|");
    append_executable(cmdline, "plotxy", me);
    sl_append(cmdline, "-i");
    append_escape(cmdline, indxylsfn);
    sl_append(cmdline, "-I - -w 2 -r 4 -C green -x 1 -y 1");
	if (plotscale != 1.0) {
		sl_append(cmdline, "-S");
		sl_appendf(cmdline, "%f", plotscale);
	}

    // if we solved by verifying an existing WCS, there is no quad.
    if (mo->dimquads) {
        sl_append(cmdline, " -P |");
        append_executable(cmdline, "plotquad", me);
        sl_append(cmdline, "-I -");
        sl_append(cmdline, "-C green");
        sl_append(cmdline, "-w 2");
        sl_appendf(cmdline, "-d %i", mo->dimquads);
		if (plotscale != 1.0) {
			sl_append(cmdline, "-s");
			sl_appendf(cmdline, "%f", plotscale);
		}
        for (i=0; i<(2 * mo->dimquads); i++)
            sl_appendf(cmdline, " %g", mo->quadpix[i]);
    }

    matchfile_close(mf);
			
    sl_append(cmdline, ">");
    append_escape(cmdline, redgreenfn);
    
    cmd = sl_implode(cmdline, " ");
    sl_free2(cmdline);
    logverb("Running:\n  %s\n", cmd);
    if (run_command(cmd, &ctrlc)) {
        ERROR("Plotting commands %s; exiting.", (ctrlc ? "were cancelled" : "failed"));
        return -1;
    }
    free(cmd);
    return 0;
}

static int plot_annotations(augment_xylist_t* axy, const char* me, anbool verbose,
                            const char* annfn, double plotscale, const char* bgfn) {
    sl* cmdline = sl_new(16);
    char* cmd;
    sl* lines;
	char* imgfn;

	imgfn = axy->pnmfn;
	if (bgfn) {
		append_executable(cmdline, "jpegtopnm", me);
		append_escape(cmdline, bgfn);
		sl_append(cmdline, "|");
		imgfn = "-";
	} else if (axy->imagefn && plotscale != 1.0) {
		append_executable(cmdline, "pnmscale", me);
		sl_appendf(cmdline, "%f", plotscale);
        append_escape(cmdline, axy->pnmfn);
		sl_append(cmdline, "|");
		imgfn = "-";
	}

    append_executable(cmdline, "plot-constellations", me);
    if (verbose)
        sl_append(cmdline, "-v");
    sl_append(cmdline, "-w");
	assert(axy->wcsfn);
    append_escape(cmdline, axy->wcsfn);

	sl_append(cmdline, "-i");
	append_escape(cmdline, imgfn);
	if (plotscale != 1.0) {
		sl_append(cmdline, "-s");
		sl_appendf(cmdline, "%f", plotscale);
	}
    sl_append(cmdline, "-N");
    sl_append(cmdline, "-B");
    sl_append(cmdline, "-C");
    sl_append(cmdline, "-o");
	assert(annfn);
    append_escape(cmdline, annfn);
    cmd = sl_implode(cmdline, " ");
    sl_free2(cmdline);
    logverb("Running:\n  %s\n", cmd);
    if (run_command_get_outputs(cmd, &lines, NULL)) {
        ERROR("plot-constellations failed");
        return -1;
    }
    free(cmd);
    if (lines && sl_size(lines)) {
        int i;
        if (strlen(sl_get(lines, 0))) {
            logmsg("Your field contains:\n");
            for (i=0; i<sl_size(lines); i++)
                logmsg("  %s\n", sl_get(lines, i));
        }
    }
    if (lines)
        sl_free2(lines);
    return 0;
}

// "none" => NULL
static char* none_is_null(char* in) {
    return streq(in, "none") ? NULL : in;
}

static void run_engine(sl* engineargs) {
	char* cmd;
	cmd = sl_implode(engineargs, " ");
	logmsg("Solving...\n");
	logverb("Running:\n  %s\n", cmd);
	fflush(NULL);
	if (run_command_get_outputs(cmd, NULL, NULL)) {
		ERROR("engine failed.  Command that failed was:\n  %s", cmd);
		exit(-1);
	}
	free(cmd);
	fflush(NULL);
}

struct solve_field_args {
	char* newfitsfn;
	char* indxylsfn;
	char* redgreenfn;
	char* ngcfn;
	char* kmzfn;
	char* scampfn;
	char* scampconfigfn;
};
typedef struct solve_field_args solve_field_args_t;


// This runs after "astrometry-engine" is run on the file.
static void after_solved(augment_xylist_t* axy,
						 solve_field_args_t* sf,
						 anbool makeplots,
						 const char* me,
						 anbool verbose,
						 const char* tempdir,
						 sl* tempdirs,
						 sl* tempfiles,
						 double plotscale,
						 const char* bgfn) {
	sip_t wcs;
	double ra, dec, fieldw, fieldh;
	char rastr[32], decstr[32];
	char* fieldunits;

	// print info about the field.
	logmsg("Field: %s\n", axy->imagefn ? axy->imagefn : axy->xylsfn);
	if (file_exists(axy->wcsfn)) {
		double orient;
		if (axy->wcs_last_mod) {
			time_t t = file_get_last_modified_time(axy->wcsfn);
			if (t == axy->wcs_last_mod) {
				logmsg("Warning: there was already a WCS file, and its timestamp has not changed.\n");
			}
		}
		if (!sip_read_header_file(axy->wcsfn, &wcs)) {
			ERROR("Failed to read WCS header from file %s", axy->wcsfn);
			exit(-1);
		}
		sip_get_radec_center(&wcs, &ra, &dec);
		sip_get_radec_center_hms_string(&wcs, rastr, decstr);
		sip_get_field_size(&wcs, &fieldw, &fieldh, &fieldunits);
		orient = sip_get_orientation(&wcs);
		logmsg("Field center: (RA,Dec) = (%.4g, %.4g) deg.\n", ra, dec);
		logmsg("Field center: (RA H:M:S, Dec D:M:S) = (%s, %s).\n", rastr, decstr);
		logmsg("Field size: %g x %g %s\n", fieldw, fieldh, fieldunits);
		logmsg("Field rotation angle: up is %g degrees E of N\n", orient);
	} else {
		logmsg("Did not solve (or no WCS file was written).\n");
	}

	// create new FITS file...
	if (axy->fitsimgfn && sf->newfitsfn && file_exists(axy->wcsfn)) {
		logmsg("Creating new FITS file \"%s\"...\n", sf->newfitsfn);
		if (new_wcs(axy->fitsimgfn, axy->wcsfn, sf->newfitsfn, TRUE)) {
			ERROR("Failed to create FITS image with new WCS headers");
			exit(-1);
		}
	}

	// write list of index stars in image coordinates
	if (sf->indxylsfn && file_exists(axy->wcsfn) && file_exists(axy->rdlsfn)) {
		assert(axy->wcsfn);
		assert(axy->rdlsfn);
		// index rdls to xyls.
		if (wcs_rd2xy(axy->wcsfn, 0, axy->rdlsfn, sf->indxylsfn,
					  NULL, NULL, FALSE, FALSE, NULL)) {
			ERROR("Failed to project index stars into field coordinates using wcs-rd2xy");
			exit(-1);
		}
	}

	if (makeplots && file_exists(sf->indxylsfn) && file_readable(axy->matchfn) && file_readable(axy->wcsfn)) {
		logmsg("Creating index object overlay plot...\n");
		if (plot_index_overlay(axy, me, sf->indxylsfn, sf->redgreenfn, plotscale, bgfn)) {
			ERROR("Plot index overlay failed.");
		}
	}

	if (makeplots && axy->imagefn && file_exists(axy->wcsfn) && axy->pnmfn) {
		logmsg("Creating annotation plot...\n");
		if (plot_annotations(axy, me, verbose, sf->ngcfn, plotscale, bgfn)) {
			ERROR("Plot annotations failed.");
		}
	}

	if (axy->imagefn && sf->kmzfn && file_exists(axy->wcsfn)) {
		logmsg("Writing kmz file...\n");
		if (write_kmz(axy, sf->kmzfn, tempdir, tempdirs, tempfiles)) {
			ERROR("Failed to write KMZ.");
			exit(-1);
		}
	}

	if (sf->scampfn && file_exists(axy->wcsfn)) {
		//char* hdrfile = NULL;
		qfits_header* imageheader = NULL;
		starxy_t* xy;
		xylist_t* xyls;

		xyls = xylist_open(axy->outfn);
		if (!xyls) {
			ERROR("Failed to read xylist to write SCAMP catalog");
			exit(-1);
		}
		if (axy->xcol)
			xylist_set_xname(xyls, axy->xcol);
		if (axy->ycol)
			xylist_set_yname(xyls, axy->ycol);
		//xylist_set_include_flux(xyls, FALSE);
		xylist_set_include_background(xyls, FALSE);
		xy = xylist_read_field(xyls, NULL);
		xylist_close(xyls);

		if (axy->fitsimgfn) {
			//hdrfile = axy->fitsimgfn;
			imageheader = anqfits_get_header2(axy->fitsimgfn, 0);
		}
		if (axy->xylsfn) {
			char val[32];
			//hdrfile = axy->xylsfn;
			imageheader = anqfits_get_header2(axy->xylsfn, 0);
			// Set NAXIS=2, NAXIS1=IMAGEW, NAXIS2=IMAGEH
			fits_header_mod_int(imageheader, "NAXIS", 2, NULL);
			sprintf(val, "%i", axy->W);
			qfits_header_add_after(imageheader, "NAXIS",  "NAXIS1", val, "image width", NULL);
			sprintf(val, "%i", axy->H);
			qfits_header_add_after(imageheader, "NAXIS1", "NAXIS2", val, "image height", NULL);
			//fits_header_add_int(imageheader, "NAXIS1", axy->W, NULL);
			//fits_header_add_int(imageheader, "NAXIS2", axy->H, NULL);
			logverb("Using NAXIS 1,2 = %i,%i\n", axy->W, axy->H);
		}

		if (scamp_write_field(imageheader, &wcs, xy, sf->scampfn)) {
			ERROR("Failed to write SCAMP catalog");
			exit(-1);
		}
		starxy_free(xy);
		if (imageheader)
			qfits_header_destroy(imageheader);
	}

	if (sf->scampconfigfn) {
		if (scamp_write_config_file(axy->scampfn, sf->scampconfigfn)) {
			ERROR("Failed to write SCAMP config file snippet to %s", sf->scampconfigfn);
			exit(-1);
		}
	}
}

static void delete_temp_files(sl* tempfiles, sl* tempdirs) {
	int i;
	for (i=0; i<sl_size(tempfiles); i++) {
		char* fn = sl_get(tempfiles, i);
		logverb("Deleting temp file %s\n", fn);
		if (unlink(fn))
			SYSERROR("Failed to delete temp file \"%s\"", fn);
	}
	for (i=0; i<sl_size(tempdirs); i++) {
		char* fn = sl_get(tempdirs, i);
		logverb("Deleting temp dir %s\n", fn);
		if (rmdir(fn))
			SYSERROR("Failed to delete temp dir \"%s\"", fn);
	}
	sl_remove_all(tempfiles);
	sl_remove_all(tempdirs);
}


int main(int argc, char** args) {
	int c;
	anbool help = FALSE;
	char* outdir = NULL;
	char* cmd;
	int i, j, f;
    int inputnum;
	int rtn;
	sl* engineargs;
	int nbeargs;
	anbool fromstdin = FALSE;
	anbool overwrite = FALSE;
	anbool cont = FALSE;
    anbool skip_solved = FALSE;
    anbool makeplots = TRUE;
	double plotscale = 1.0;
	char* bgfn = NULL;
    char* me;
    anbool verbose = FALSE;
    int loglvl = LOG_MSG;
    char* outbase = NULL;
	anbool usecurl = TRUE;
    bl* opts;
    augment_xylist_t theallaxy;
    augment_xylist_t* allaxy = &theallaxy;
    int nmyopts;
    char* removeopts = "ixo\x01";
    char* newfits;
    char* kmz = NULL;
    char* scamp = NULL;
    char* scampconfig = NULL;
    char* index_xyls;
	anbool just_augment = FALSE;
	anbool engine_batch = FALSE;
	bl* batchaxy = NULL;
	bl* batchsf = NULL;
	sl* outfiles;
	sl* tempfiles;
	sl* tempdirs;
	anbool timestamp = FALSE;

    errors_print_on_exit(stderr);
    fits_use_error_system();

    me = find_executable(args[0], NULL);

	engineargs = sl_new(16);
	append_executable(engineargs, "astrometry-engine", me);

	// output filenames.
	outfiles = sl_new(16);
	tempfiles = sl_new(4);
	tempdirs = sl_new(4);

	rtn = 0;

    nmyopts = sizeof(options)/sizeof(an_option_t);
    opts = opts_from_array(options, nmyopts, NULL);
    augment_xylist_add_options(opts);

    // remove duplicate short options.
    for (i=0; i<nmyopts; i++) {
        an_option_t* opt1 = bl_access(opts, i);
        for (j=nmyopts; j<bl_size(opts); j++) {
            an_option_t* opt2 = bl_access(opts, j);
            if (opt2->shortopt == opt1->shortopt)
                bl_remove_index(opts, j);
        }
    }

    // remove unwanted augment-xylist options.
    for (i=0; i<strlen(removeopts); i++) {
        for (j=nmyopts; j<bl_size(opts); j++) {
            an_option_t* opt2 = bl_access(opts, j);
            if (opt2->shortopt == removeopts[i])
                bl_remove_index(opts, j);
        }
    }

	// which options are left?
	/*{
		char options[256];
		memset(options, 0, 256);
		printf("options:\n");
		for (i=0; i<bl_size(opts); i++) {
			an_option_t* opt = bl_access(opts, i);
			printf("  %c (%i) %s\n", opt->shortopt, (int)opt->shortopt, opt->name);
			options[(int)((opt->shortopt + 256) % 256)] = 1;
		}
		printf("Remaining short opts:\n");
		for (i=0; i<256; i++) {
			if (!options[i])
				printf("  %c (%i, 0x%x)\n", (char)i, i, i);
		}
	 }*/

    augment_xylist_init(allaxy);

    // default output filename patterns.
    allaxy->outfn    = "%s.axy";
    allaxy->matchfn  = "%s.match";
    allaxy->rdlsfn   = "%s.rdls";
    allaxy->solvedfn = "%s.solved";
    allaxy->wcsfn    = "%s.wcs";
    allaxy->corrfn   = "%s.corr";
    newfits          = "%s.new";
    index_xyls = "%s-indx.xyls";

	while (1) {
        int res;
		c = opts_getopt(opts, argc, args);
		//printf("option %c (%i)\n", c, (int)c);
        if (c == -1)
            break;
        switch (c) {
		case '\x88':
			timestamp = TRUE;
			break;
		case '\x84':
			plotscale = atof(optarg);
			break;
		case '\x85':
			bgfn = optarg;
			break;
		case '\x87':
			allaxy->assume_fits_image = TRUE;
			break;
		case '(':
			engine_batch = TRUE;
			break;
		case '@':
			just_augment = TRUE;
			break;
        case 'U':
            index_xyls = optarg;
            break;
        case 'n':
            scampconfig = optarg;
            break;
        case 'i':
            scamp = optarg;
            break;
        case 'Z':
            kmz = optarg;
            break;
        case 'N':
            newfits = optarg;
            break;
		case 'h':
			help = TRUE;
			break;
        case 'v':
            sl_append(engineargs, "--verbose");
            verbose = TRUE;
			allaxy->verbosity++;
            loglvl++;
            break;
		case 'D':
			outdir = optarg;
			break;
        case 'o':
            outbase = optarg;
            break;
		case 'b':
		case '\x89':
			sl_append(engineargs, "--config");
			append_escape(engineargs, optarg);
			break;
		case 'f':
			fromstdin = TRUE;
			break;
        case 'O':
            overwrite = TRUE;
            break;
        case 'p':
            makeplots = FALSE;
            break;
        case 'G':
            usecurl = FALSE;
            break;
        case 'K':
            cont = TRUE;
            break;
        case 'J':
            skip_solved = TRUE;
            break;
        default:
            res = augment_xylist_parse_option(c, optarg, allaxy);
            if (res) {
                rtn = -1;
                goto dohelp;
            }
        }
    }

	if ((optind == argc) && !fromstdin) {
		printf("ERROR: You didn't specify any files to process.\n");
		help = TRUE;
	}

	if (help) {
    dohelp:
		print_help(args[0], opts);
		exit(rtn);
	}
    bl_free(opts);

    log_init(loglvl);
	if (timestamp)
		log_set_timestamp(TRUE);

    if (kmz && starts_with(kmz, "-"))
        logmsg("Do you really want to save KMZ to the file named \"%s\" ??\n", kmz);

    if (starts_with(newfits, "-")) {
        logmsg("Do you really want to save the new FITS file to the file named \"%s\" ??\n", newfits);
    }

	if (engine_batch) {
		batchaxy = bl_new(16, sizeof(augment_xylist_t));
		batchsf  = bl_new(16, sizeof(solve_field_args_t));
	}

    // Allow (some of the) default filenames to be disabled by setting them to "none".
    allaxy->matchfn  = none_is_null(allaxy->matchfn);
    allaxy->rdlsfn   = none_is_null(allaxy->rdlsfn);
    allaxy->solvedfn = none_is_null(allaxy->solvedfn);
    allaxy->solvedinfn = none_is_null(allaxy->solvedinfn);
    allaxy->wcsfn    = none_is_null(allaxy->wcsfn);
    allaxy->corrfn   = none_is_null(allaxy->corrfn);
    newfits          = none_is_null(newfits);
    index_xyls = none_is_null(index_xyls);

	if (outdir) {
        if (mkdir_p(outdir)) {
            ERROR("Failed to create output directory %s", outdir);
            exit(-1);
        }
	}

	// number of engine args not specific to a particular file
	nbeargs = sl_size(engineargs);

	f = optind;
    inputnum = 0;
	while (1) {
		char* infile = NULL;
		anbool isxyls;
		char* reason;
		int len;
		char* base;
        char* basedir;
        char* basefile = NULL;
		char *objsfn=NULL;
		char *ppmfn=NULL;
        char* downloadfn = NULL;
        char* suffix = NULL;
		sl* cmdline;
        anbool ctrlc;
        anbool isurl;
        augment_xylist_t theaxy;
        augment_xylist_t* axy = &theaxy;
        int j;
		solve_field_args_t thesf;
		solve_field_args_t* sf = &thesf;
		anbool want_pnm = FALSE;

        // reset augment-xylist args.
        memcpy(axy, allaxy, sizeof(augment_xylist_t));

		memset(sf, 0, sizeof(solve_field_args_t));

		if (fromstdin) {
            char fnbuf[1024];
			if (!fgets(fnbuf, sizeof(fnbuf), stdin)) {
				if (ferror(stdin))
					SYSERROR("Failed to read a filename from stdin");
				break;
			}
			len = strlen(fnbuf);
			if (fnbuf[len-1] == '\n')
				fnbuf[len-1] = '\0';
			infile = fnbuf;
            logmsg("Reading input file \"%s\"...\n", infile);
		} else {
			if (f == argc)
				break;
			infile = args[f];
			f++;
            logmsg("Reading input file %i of %i: \"%s\"...\n",
                   f - optind, argc - optind, infile);
		}
        inputnum++;

        cmdline = sl_new(16);

		if (!engine_batch) {
			// Remove arguments that might have been added in previous trips through this loop
			sl_remove_from(engineargs,  nbeargs);
		}

		// Choose the base path/filename for output files.
        if (outbase)
            asprintf_safe(&basefile, outbase, inputnum, infile);
        else
			basefile = basename_safe(infile);
        //logverb("Base filename: %s\n", basefile);

        isurl = (!file_exists(infile) &&
                 (starts_with(infile, "http://") ||
                  starts_with(infile, "ftp://")));

		if (outdir)
            basedir = strdup(outdir);
		else {
            if (isurl)
                basedir = strdup(".");
            else
				basedir = dirname_safe(infile);
        }
        //logverb("Base directory: %s\n", basedir);

        asprintf_safe(&base, "%s/%s", basedir, basefile);
        //logverb("Base name for output files: %s\n", base);

        // trim .gz, .bz2
        // hmm, we drop the suffix in this case...
		len = strlen(base);
        if (ends_with(base, ".gz"))
            base[len-3] = '\0';
        else if (ends_with(base, ".bz2"))
            base[len-4] = '\0';
		len = strlen(base);
		// trim .xx / .xxx / .xxxx
		if (len >= 5) {
            for (j=3; j<=5; j++) {
                if (base[len - j] == '/')
                    break;
                if (base[len - j] == '.') {
                    base[len - j] = '\0';
                    suffix = base + len - j + 1;
                    break;
                }
            }
		}
        logverb("Base: \"%s\", basefile \"%s\", basedir \"%s\", suffix \"%s\"\n", base, basefile, basedir, suffix);

		axy->outfn    = sl_appendf(outfiles, axy->outfn,       base);
        if (axy->matchfn)
            axy->matchfn  = sl_appendf(outfiles, axy->matchfn,     base);
        if (axy->rdlsfn)
            axy->rdlsfn   = sl_appendf(outfiles, axy->rdlsfn,      base);
        if (axy->solvedfn)
            axy->solvedfn = sl_appendf(outfiles, axy->solvedfn,    base);
        if (axy->wcsfn)
            axy->wcsfn    = sl_appendf(outfiles, axy->wcsfn,       base);
        if (axy->corrfn)
            axy->corrfn   = sl_appendf(outfiles, axy->corrfn,      base);
        if (axy->cancelfn)
            axy->cancelfn  = sl_appendf(outfiles, axy->cancelfn, base);
        if (axy->keepxylsfn)
            axy->keepxylsfn  = sl_appendf(outfiles, axy->keepxylsfn, base);
        if (axy->pnmfn)
            axy->pnmfn  = sl_appendf(outfiles, axy->pnmfn, base);
        if (newfits)
            sf->newfitsfn  = sl_appendf(outfiles, newfits,  base);
		if (kmz)
			sf->kmzfn = sl_appendf(outfiles, kmz, base);
        if (index_xyls)
            sf->indxylsfn  = sl_appendf(outfiles, index_xyls, base);
		if (scamp)
			sf->scampfn = sl_appendf(outfiles, scamp, base);
		if (scampconfig)
			sf->scampconfigfn = sl_appendf(outfiles, scampconfig, base);
        if (makeplots) {
            objsfn     = sl_appendf(outfiles, "%s-objs.png",  base);
            sf->redgreenfn = sl_appendf(outfiles, "%s-indx.png",  base);
            sf->ngcfn      = sl_appendf(outfiles, "%s-ngc.png",   base);
        }
        if (isurl) {
            if (suffix)
                downloadfn = sl_appendf(outfiles, "%s.%s", base, suffix);
            else
                downloadfn = sl_appendf(outfiles, "%s", base);
        }

        if (axy->solvedinfn)
            asprintf_safe(&axy->solvedinfn, axy->solvedinfn, base);

        // Do %s replacement on --verify-wcs entries...
        if (sl_size(axy->verifywcs)) {
            sl* newlist = sl_new(4);
            for (j=0; j<sl_size(axy->verifywcs); j++)
                sl_appendf(newlist, sl_get(axy->verifywcs, j), base);
            axy->verifywcs = newlist;
        }

        if (axy->solvedinfn && axy->solvedfn && streq(axy->solvedfn, axy->solvedinfn)) {
            // solved input and output files are the same: don't delete the input!
            sl_remove_string(outfiles, axy->solvedfn);
            free(axy->solvedfn);
            axy->solvedfn = axy->solvedinfn;
        }

        free(basedir);
        free(basefile);

        if (skip_solved) {
            char* tocheck[] = { axy->solvedinfn, axy->solvedfn };
            for (j=0; j<sizeof(tocheck)/sizeof(char*); j++) {
                if (!tocheck[j])
                    continue;
                logverb("Checking for solved file %s\n", tocheck[j]);
                if (file_exists(tocheck[j])) {
                    logmsg("Solved file exists: %s; skipping this input file.\n", tocheck[j]);
                    goto nextfile;
                } else {
                    logverb("File \"%s\" does not exist.\n", tocheck[j]);
                }
            }
        }

        // Check for overlap between input and output filenames
		for (i = 0; i < sl_size(outfiles); i++) {
			char* fn = sl_get(outfiles, i);
            if (streq(fn, infile)) {
                logmsg("Output filename \"%s\" is the same as your input file.\n"
                       "Refusing to continue.\n"
                       "You can either choose a different output filename, or\n"
                       "rename your input file to have a different extension.\n", fn);
                goto nextfile;
            }
        }

		// Check for (and possibly delete) existing output filenames.
		for (i = 0; i < sl_size(outfiles); i++) {
			char* fn = sl_get(outfiles, i);
			if (!file_exists(fn))
				continue;
            if (cont) {
            } else if (overwrite) {
				if (unlink(fn)) {
					SYSERROR("Failed to delete an already-existing output file \"%s\"", fn);
					exit(-1);
				}
			} else {
				logmsg("Output file already exists: \"%s\".\n"
				       "Use the --overwrite flag to overwrite existing files,\n"
                       " or the --continue  flag to not overwrite existing files but still try solving.\n", fn);
				logmsg("Continuing to next input file.\n");
                goto nextfile;
			}
		}

		// if we're making "redgreen" plot, we need:
		if (sf->redgreenfn) {
			// -- index xylist
			if (!sf->indxylsfn) {
				sf->indxylsfn = create_temp_file("indxyls", axy->tempdir);
				sl_append_nocopy(tempfiles, sf->indxylsfn);
			}
			// -- match file.
			if (!axy->matchfn) {
				axy->matchfn = create_temp_file("match", axy->tempdir);
				sl_append_nocopy(tempfiles, axy->matchfn);
			}
		}

		// if index xyls file is needed, we need:
		if (sf->indxylsfn) {
			// -- wcs
			if (!axy->wcsfn) {
				axy->wcsfn = create_temp_file("wcs", axy->tempdir);
				sl_append_nocopy(tempfiles, axy->wcsfn);
			}
			// -- rdls
			if (!axy->rdlsfn) {
				axy->rdlsfn = create_temp_file("rdls", axy->tempdir);
				sl_append_nocopy(tempfiles, axy->rdlsfn);
			}
		}

        // Download URL...
        if (isurl) {

            sl_append(cmdline, usecurl ? "curl" : "wget");
            if (!verbose)
                sl_append(cmdline, usecurl ? "--silent" : "--quiet");
            sl_append(cmdline, usecurl ? "--output" : "-O");
            append_escape(cmdline, downloadfn);
            append_escape(cmdline, infile);

            cmd = sl_implode(cmdline, " ");

            logmsg("Downloading...\n");
            if (run_command(cmd, &ctrlc)) {
                ERROR("%s command %s", sl_get(cmdline, 0),
                      (ctrlc ? "was cancelled" : "failed"));
                exit(-1);
            }
            sl_remove_all(cmdline);
            free(cmd);

            infile = downloadfn;
        }

		if (makeplots)
			want_pnm = TRUE;

		if (axy->assume_fits_image) {
			axy->imagefn = infile;
			if (axy->pnmfn)
				want_pnm = TRUE;
		} else {
			logverb("Checking if file \"%s\" is xylist or image: ", infile);
			fflush(NULL);
			reason = NULL;
			isxyls = xylist_is_file_xylist(infile, axy->xcol, axy->ycol, &reason);
			logverb(isxyls ? "xyls\n" : "image\n");
			if (!isxyls)
				logverb("  (not xyls because: %s)\n", reason);
			free(reason);
			fflush(NULL);

			if (isxyls)
				axy->xylsfn = infile;
			else {
				axy->imagefn = infile;
				want_pnm = TRUE;
			}
		}

		if (want_pnm && !axy->pnmfn) {
            ppmfn = create_temp_file("ppm", axy->tempdir);
            sl_append_nocopy(tempfiles, ppmfn);
            axy->pnmfn = ppmfn;
            axy->force_ppm = TRUE;
		}

        axy->keep_fitsimg = (newfits || scamp);

        if (augment_xylist(axy, me)) {
            ERROR("augment-xylist failed");
            exit(-1);
        }

		if (just_augment)
			goto nextfile;

        if (makeplots) {
            // Check that the plotting executables were built...
            char* exec = find_executable("plotxy", me);
            free(exec);
            if (!exec) {
                logmsg("Couldn't find \"plotxy\" executable - maybe you didn't build the plotting programs?\n");
                logmsg("Disabling plots.\n");
                makeplots = FALSE;
            }
        }
        if (makeplots) {
            // source extraction overlay
            if (plot_source_overlay(axy, me, objsfn, plotscale, bgfn))
                makeplots = FALSE;
        }

		append_escape(engineargs, axy->outfn);

		if (file_readable(axy->wcsfn))
			axy->wcs_last_mod = file_get_last_modified_time(axy->wcsfn);
		else
			axy->wcs_last_mod = 0;

		if (!engine_batch) {
			run_engine(engineargs);
			after_solved(axy, sf, makeplots, me, verbose,
						 axy->tempdir, tempdirs, tempfiles, plotscale, bgfn);
		} else {
			bl_append(batchaxy, axy);
			bl_append(batchsf,  sf );
		}
        fflush(NULL);

        // clean up and move on to the next file.
    nextfile:        
		free(base);
        sl_free2(cmdline);

		if (!engine_batch) {
			free(axy->fitsimgfn);
			free(axy->solvedinfn);
			// erm.
			if (axy->verifywcs != allaxy->verifywcs)
				sl_free2(axy->verifywcs);
			sl_remove_all(outfiles);
			if (!axy->no_delete_temp)
				delete_temp_files(tempfiles, tempdirs);
		}
        errors_print_stack(stdout);
        errors_clear_stack();
        logmsg("\n");
	}

	if (engine_batch) {
		run_engine(engineargs);
		for (i=0; i<bl_size(batchaxy); i++) {
			augment_xylist_t* axy = bl_access(batchaxy, i);
			solve_field_args_t* sf = bl_access(batchsf, i);

			after_solved(axy, sf, makeplots, me, verbose,
						 axy->tempdir, tempdirs, tempfiles, plotscale, bgfn);
			errors_print_stack(stdout);
			errors_clear_stack();
			logmsg("\n");

			free(axy->fitsimgfn);
			free(axy->solvedinfn);
			// erm.
			if (axy->verifywcs != allaxy->verifywcs)
				sl_free2(axy->verifywcs);
		}
		if (!allaxy->no_delete_temp)
			delete_temp_files(tempfiles, tempdirs);
		bl_free(batchaxy);
		bl_free(batchsf);
	}

	sl_free2(outfiles);
	sl_free2(tempfiles);
	sl_free2(tempdirs);
	sl_free2(engineargs);
    free(me);
    augment_xylist_free_contents(allaxy);

	return 0;
}

