/*
# This file is part of the Astrometry.net suite.
# Licensed under a 3-clause BSD style license - see LICENSE
*/

/*
 Hackery for dstn's thesis - cairo pdf rendering of "plotxy" and "plotquad"
 over a background image.
 */

#include <math.h>
#include <string.h>
#include <stdint.h>

#include <cairo.h>
#include <cairo-pdf.h>
#include <cairo-ps.h>

#include "xylist.h"
#include "matchfile.h"
#include "permutedsort.h"
#include "boilerplate.h"
#include "cairoutils.h"
#include "log.h"
#include "errors.h"

static const char* OPTIONS = "hvj:x:m:W:H:s:E";

static void printHelp(char* progname) {
	fprintf(stderr, "\nUsage: %s [options] > output.pdf\n"
			"   -j <jpeg filename>\n"
			"   -x <xylist filename>\n"
			"   -m <match filename>\n"
			"  [-W <output width (points)>]\n"
			"  [-H <output height (points)>]\n"
			"  [-s <xylist-sale-factor>]\n"
			"  [-E]: EPS output\n"
			"\n", progname);
}


int main(int argc, char *args[]) {
	int argchar;
	char* progname = args[0];

	int outW=0, outH=0;
	int W, H;
	unsigned char* img = NULL;
	cairo_t* cairo;
	cairo_surface_t* target;
	xylist_t* xyls;
	starxy_t* xy;
	int Nxy;
	int Nstars;
	int ext = 1;
	double xoff, yoff;
	double scalexy = 1.0;
    float br, bg, bb;
    float r, g, b;
	double rad;
	double lw;
    int marker;
	int i;
	int nquads;
	int dimquads = 4;
	dl* coords;
	int loglvl = LOG_MSG;
	double sx, sy;
	anbool eps = FALSE;

	/*
	 #	| plotxy -i m88.xy -I - -x 1 -y 1 -b white -C black -N 100 -r 10 -w 2 -P \
	 #	| plotquad -I - -C black -m m88-${I2}.match -P \
	 */

	char* bgimgfn = NULL;
	char* xylsfn = NULL;
	char* matchfn = NULL;

	while ((argchar = getopt(argc, args, OPTIONS)) != -1)
		switch (argchar) {
		case 'E':
			eps = TRUE;
			break;
		case 'j':
			bgimgfn = optarg;
			break;
		case 'x':
			xylsfn = optarg;
			break;
		case 'm':
			matchfn = optarg;
			break;
		case 'W':
			outW = atoi(optarg);
			break;
		case 'H':
			outH = atoi(optarg);
			break;
		case 's':
			scalexy = atof(optarg);
			break;
		case 'h':
			printHelp(progname);
            exit(0);
		case 'v':
			loglvl++;
			break;
		}

	log_init(LOG_MSG);
	log_to(stderr);
	errors_log_to(stderr);

	if (!(bgimgfn && xylsfn && matchfn)) {
		ERROR("Must specify background jpeg, xyls, and match filenames.\n");
		printHelp(progname);
		exit(-1);
	}

	Nstars = 100;
	xoff = yoff = 1.0;
	r = g = b = 0.0;
	br = bg = bb = 1.0;
	rad = 6;
	lw = 2;
	marker = CAIROUTIL_MARKER_CIRCLE;
	W = H = -1;
	coords = dl_new(16);

	// read bg image to get size.
	img = cairoutils_read_jpeg(bgimgfn, &W, &H);
	if (!img) {
		ERROR("Failed to read background image \"%s\"", bgimgfn);
		exit(-1);
	}
	logmsg("Read \"%s\": %ix%i pixels\n", bgimgfn, W, H);

	xyls = xylist_open(xylsfn);
	if (!xyls) {
		ERROR("Failed to read xylist \"%s\"", xylsfn);
		exit(-1);
	}

	if (!outW)
		outW = W;
	if (!outH)
		outH = H;

	logmsg("Background image aspect ratio %g; output file aspect ratio %g.\n", W/(float)H, outW/(float)outH);

	// create output buffer.
	if (eps) {
		target = cairo_ps_surface_create_for_stream(cairoutils_file_write_func, stdout, outW, outH);
		cairo_ps_surface_set_eps(target, TRUE);
	} else {
		target = cairo_pdf_surface_create_for_stream(cairoutils_file_write_func, stdout, outW, outH);
	}
	if (!target) {
		ERROR("Failed to create cairo surface");
		exit(-1);
	}
	cairo = cairo_create(target);

	sx = outW/(float)W;
	sy = outH/(float)H;

	cairo_set_line_width(cairo, lw);
	cairo_set_antialias(cairo, CAIRO_ANTIALIAS_GRAY);

	cairoutils_surface_status_errors(target);
	cairoutils_cairo_status_errors(cairo);

	// render image.
	{
		cairo_surface_t* thissurf;
		cairo_pattern_t* pat;
		cairoutils_rgba_to_argb32(img, W, H);
		thissurf = cairo_image_surface_create_for_data(img, CAIRO_FORMAT_ARGB32, W, H, W*4);
		pat = cairo_pattern_create_for_surface(thissurf);

		logmsg("Scaling image by factors %g, %g\n", sx, sy);
		cairo_save(cairo);
		cairo_scale(cairo, sx, sy);
		cairo_set_source(cairo, pat);
		cairo_paint(cairo);
		cairo_restore(cairo);

		cairo_pattern_destroy(pat);
		cairo_surface_destroy(thissurf);
	}
	free(img);

	// render xylist.
    // we don't care about FLUX and BACKGROUND columns.
    xylist_set_include_flux(xyls, FALSE);
    xylist_set_include_background(xyls, FALSE);
	// Find number of entries in xylist.
    xy = xylist_read_field_num(xyls, ext, NULL);
    if (!xy) {
		ERROR("Failed to read FITS extension %i from file %s", ext, xylsfn);
		exit(-1);
	}
    Nxy = starxy_n(xy);
	logmsg("Xylist contains %i stars\n", Nxy);
	// If N is specified, apply it as a max.
    if (Nstars) {
		if (Nstars < Nxy)
			logmsg("Keeping %i stars.\n", Nstars);
        Nxy = MIN(Nxy, Nstars);
	}

	cairo_set_source_rgb(cairo, r, g, b);

	// render background markers.
	cairo_save(cairo);
	cairo_set_line_width(cairo, lw+2.0);
	//cairo_set_source_rgba(cairo, br, bg, bb, 0.75);
	cairo_set_source_rgba(cairo, br, bg, bb, 1.0);
	for (i=0; i<Nxy; i++) {
		double x = (starxy_getx(xy, i) - xoff) * sx * scalexy + 0.5;
		double y = (starxy_gety(xy, i) - yoff) * sy * scalexy + 0.5;
		cairoutils_draw_marker(cairo, marker, x, y, rad);
		cairo_stroke(cairo);
	}
	cairo_restore(cairo);

	// Draw markers.
	for (i=0; i<Nxy; i++) {
		double x = (starxy_getx(xy, i) - xoff) * sx * scalexy + 0.5;
		double y = (starxy_gety(xy, i) - yoff) * sy * scalexy + 0.5;
        cairoutils_draw_marker(cairo, marker, x, y, rad);
		cairo_stroke(cairo);
	}

    starxy_free(xy);
	xylist_close(xyls);

	// Plot quad.
	{
        matchfile* mf = matchfile_open(matchfn);
        MatchObj* mo;
        if (!mf) {
            ERROR("Failed to open matchfile \"%s\"", matchfn);
            exit(-1);
        }
        while (1) {
            mo = matchfile_read_match(mf);
            if (!mo)
                break;
            for (i=0; i<2*dimquads; i++) {
                dl_append(coords, mo->quadpix[i]);
            }
        }
	}
	nquads = dl_size(coords) / (2*dimquads);

	lw = 4;
	cairo_set_line_width(cairo, lw);
	cairo_set_line_join(cairo, CAIRO_LINE_JOIN_BEVEL);
	for (i=0; i<nquads; i++) {
		int j;
		double theta[dimquads];
		int perm[dimquads];
		double cx, cy;

		// Make the quad convex so Sam's eyes don't bleed.
		cx = cy = 0.0;
		for (j=0; j<dimquads; j++) {
			cx += dl_get(coords, i*(2*dimquads) + j*2);
			cy += dl_get(coords, i*(2*dimquads) + j*2 + 1);
		}
		cx /= dimquads;
		cy /= dimquads;
		for (j=0; j<dimquads; j++) {
			theta[j] = atan2(dl_get(coords, i*(2*dimquads) + j*2 + 1)-cy,
							 dl_get(coords, i*(2*dimquads) + j*2 + 0)-cx);
		}
		permutation_init(perm, dimquads);
		permuted_sort(theta, sizeof(double), compare_doubles_asc, perm, dimquads);
		for (j=0; j<dimquads; j++) {
			((j==0) ? cairo_move_to : cairo_line_to)
                (cairo,
                 (dl_get(coords, i*(2*dimquads) + perm[j]*2)
				  - xoff) * sx * scalexy + 0.5,
                 (dl_get(coords, i*(2*dimquads) + perm[j]*2 + 1)
				  - yoff) * sy * scalexy + 0.5);
		}
		cairo_close_path(cairo);
		cairo_stroke(cairo);
	}


	// do output & clean up.
	cairo_surface_flush(target);
	cairo_surface_finish(target);
	cairoutils_surface_status_errors(target);
	cairoutils_cairo_status_errors(cairo);

	cairo_surface_destroy(target);
	cairo_destroy(cairo);

	return 0;
}
