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

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

#include <cairo.h>

#include "an-bool.h"
#include "cairoutils.h"
#include "boilerplate.h"
#include "bl.h"
#include "permutedsort.h"
#include "matchfile.h"
#include "log.h"
#include "os-features.h" // for HAVE_NETPBM.

#define OPTIONS "hW:H:w:I:C:PRo:d:cm:s:b:v"

static void printHelp(char* progname) {
	BOILERPLATE_HELP_HEADER(stdout);
	printf("\nUsage: %s [options] <quads>  > output.png\n"
           "  [-I <input-image>]  Input image (PPM format) to plot over.\n"
	       "  [-p]: Input image is PNG format, not PPM.\n"
           "  [-P]              Write PPM output instead of PNG.\n"
           "  [-C <color>]      Color to plot in: (default: white)\n",
           progname);
    cairoutils_print_color_names("\n                 ");
    printf("  [-b <color>]      Draw in <color> behind each line.\n"
           "  [-c]:            Also plot a circle at each vertex.\n"
           "  [-W <width> ]       Width of output image.\n"
		   "  [-H <height>]       Height of output image.\n"
		   "  [-w <width>]      Width of lines to draw (default: 5).\n"
		   "  [-R]:  Read quads from stdin.\n"
		   "  [-o <opacity>]\n"
           "  [-d <dimension of \"quad\">]\n"
           "  [-s <scale>]: scale quad coordinates before plotting.\n"
           "\n"
           " ( [-m <match.fits>]: Get quad from match file.\n"
		   " OR  <x1> <y1> <x2> <y2> <x3> <y3> <x4> <y4> [...])\n"
		   "\n");
}


int main(int argc, char *args[]) {
	int argchar;
	char* progname = args[0];
	int W = 0, H = 0;
	int lw = 5;
	int nquads;
	int i;
	dl* coords;
    char* infn = NULL;
    anbool pngoutput = TRUE;
    anbool pnginput = FALSE;
	anbool fromstdin = FALSE;
	anbool randomcolor = FALSE;
	float a = 1.0;
    anbool plotmarker = FALSE;

    unsigned char* img = NULL;
	cairo_t* cairo;
	cairo_surface_t* target;
    float r=1.0, g=1.0, b=1.0;
	int dimquads = 4;
    double scale = 1.0;
    char* matchfn = NULL;

    anbool background = FALSE;
    float br=0.0, bg=0.0, bb=0.0;

	int loglvl = LOG_MSG;

	coords = dl_new(16);

	while ((argchar = getopt(argc, args, OPTIONS)) != -1)
		switch (argchar) {
		case 'v':
			loglvl++;
			break;
        case 's':
            scale = atof(optarg);
            break;
        case 'm':
            matchfn = optarg;
            break;
        case 'c':
            plotmarker = TRUE;
            break;
		case 'd':
			dimquads = atoi(optarg);
			break;
        case 'C':
			if (!strcasecmp(optarg, "random"))
				randomcolor = TRUE;
            else if (cairoutils_parse_color(optarg, &r, &g, &b)) {
                fprintf(stderr, "I didn't understand color \"%s\".\n", optarg);
                exit(-1);
            }
            break;
        case 'b':
            background = TRUE;
            if (cairoutils_parse_color(optarg, &br, &bg, &bb)) {
                fprintf(stderr, "I didn't understand color \"%s\".\n", optarg);
                exit(-1);
            }
            break;
		case 'o':
			a = atof(optarg);
			break;
		case 'R':
			fromstdin = TRUE;
			break;
        case 'I':
            infn = optarg;
            break;
        case 'P':
            pngoutput = FALSE;
            break;
		case 'p':
            pnginput = TRUE;
            break;
		case 'W':
			W = atoi(optarg);
			break;
		case 'H':
			H = atoi(optarg);
			break;
		case 'w':
			lw = atoi(optarg);
			break;
		case 'h':
		case '?':
		default:
			printHelp(progname);
			exit(-1);
		}

	log_init(loglvl);
	log_to(stderr);

    if (dimquads == 0) {
        printf("Error: dimquads (-d) must be positive.\n");
        printHelp(progname);
        exit(-1);
    }

	if (!fromstdin && ((argc - optind) % (2*dimquads))) {
		printHelp(progname);
		exit(-1);
	}
	if (!((W && H) || infn)) {
		printHelp(progname);
		exit(-1);
	}
    if (infn && (W || H)) {
        printf("Error: if you specify an input file, you can't give -W or -H (width or height) arguments.\n\n");
        printHelp(progname);
        exit(-1);
    }

    if (matchfn) {
        matchfile* mf = matchfile_open(matchfn);
        MatchObj* mo;
        if (!mf) {
            fprintf(stderr, "Failed to open matchfile \"%s\".\n", 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]);
            }
        }
    }
    for (i=optind; i<argc; i++) {
        double pos = atof(args[i]);
        dl_append(coords, pos);
    }
	if (fromstdin) {
		for (;;) {
			int j;
			double p;
			if (feof(stdin))
				break;
			for (j=0; j<(2*dimquads); j++) {
				if (fscanf(stdin, " %lg", &p) != 1) {
					fprintf(stderr, "Failed to read a quad from stdin.\n");
					exit(-1);
				}
				dl_append(coords, p);
			}
		}
	}

    if (scale != 1.0) {
        for (i=0; i<dl_size(coords); i++) {
            dl_set(coords, i, scale * dl_get(coords, i));
        }
    }

	nquads = dl_size(coords) / (2*dimquads);

    if (infn) {
#if HAVE_NETPBM
#else
		logverb("No netpbm available: forcing PNG input.\n");
		pnginput = TRUE;
#endif
		if (pnginput) {
			logverb("Reading PNG file %s\n", infn);
			img = cairoutils_read_png(infn, &W, &H);
		}
#if HAVE_NETPBM
		else {
			logverb("Reading PPM from %s\n", infn);
			cairoutils_fake_ppm_init();
			img = cairoutils_read_ppm(infn, &W, &H);
		}
#else
#endif
        if (!img) {
            fprintf(stderr, "Failed to read input image %s.\n", infn);
            exit(-1);
        }
        cairoutils_rgba_to_argb32(img, W, H);
    } else {
        // Allocate a black image.
        img = calloc(4 * W * H, 1);
    }

    target = cairo_image_surface_create_for_data(img, CAIRO_FORMAT_ARGB32, W, H, W*4);
	cairo = cairo_create(target);
	cairo_set_line_width(cairo, lw);
	cairo_set_line_join(cairo, CAIRO_LINE_JOIN_BEVEL);
	//cairo_set_line_join(cairo, CAIRO_LINE_JOIN_ROUND);
	cairo_set_antialias(cairo, CAIRO_ANTIALIAS_GRAY);

	if (!randomcolor)
		cairo_set_source_rgba(cairo, r, g, b, a);

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

        // hack.
        if (background) {
            cairo_save(cairo);
            cairo_set_line_width(cairo, lw + 2.0);
            cairo_set_source_rgba(cairo, br, bg, bb, 0.75);
            for (j=0; j<dimquads; j++) {
                ((j==0) ? cairo_move_to : cairo_line_to)
                    (cairo,
                     dl_get(coords, i*(2*dimquads) + perm[j]*2),
                     dl_get(coords, i*(2*dimquads) + perm[j]*2 + 1));
            }
            cairo_close_path(cairo);
            cairo_stroke(cairo);
            cairo_restore(cairo);
        }

		if (randomcolor) {
			r = ((rand() % 128) + 127) / 255.0;
			g = ((rand() % 128) + 127) / 255.0;
			b = ((rand() % 128) + 127) / 255.0;
			cairo_set_source_rgba(cairo, r, g, b, a);
		}
		for (j=0; j<dimquads; j++) {
			((j==0) ? cairo_move_to : cairo_line_to)
                (cairo,
                 dl_get(coords, i*(2*dimquads) + perm[j]*2),
                 dl_get(coords, i*(2*dimquads) + perm[j]*2 + 1));
		}
		cairo_close_path(cairo);
		cairo_stroke(cairo);
	}

    if (plotmarker) {
        for (i=0; i<dl_size(coords)/2; i++) {
            double x = dl_get(coords, i*2 + 0);
            double y = dl_get(coords, i*2 + 1);
            double rad = 5;
            cairo_arc(cairo, x, y, rad, 0.0, 2.0*M_PI);
            cairo_stroke(cairo);
        }
    }

    // Convert image for output...
    cairoutils_argb32_to_rgba(img, W, H);

    if (pngoutput) {
        if (cairoutils_stream_png(stdout, img, W, H)) {
            fprintf(stderr, "Failed to write PNG.\n");
            exit(-1);
        }
    } else {
        if (cairoutils_stream_ppm(stdout, img, W, H)) {
            fprintf(stderr, "Failed to write PPM.\n");
            exit(-1);
        }
    }

	cairo_surface_destroy(target);
	cairo_destroy(cairo);
    free(img);

	return 0;
}
