// #define DEBUG_SPLINED
#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "common.h"
#include "linklist.h"
#include "waypoint.h"


static BITMAP *dbuf;
static int black, red, green, blue, yellow, magenta, cyan, white;
static waypoint_t *waypoint_array;
static int total_waypoint_steps;

static ControlPoint *cp_in_use;		/* The ctrlpt being moved or placed. */
static BOOL mouse_held_lmb;
static int control_point_n;		/* Which of the sub-ctrlpts in use. */

static BOOL snap_to_grid;

static double offx, offy;


static void draw_spline(void)
{
    int i, c = makecol(0x80, 0x80, 0x80);

    for (i = 0; i < total_waypoint_steps; i++)
	putpixel(dbuf, waypoint_array[i].x-offx, waypoint_array[i].y-offy, c);
}

static void draw_control_points(List *list)
{
    ListIterator *it;
    int n = 0, steps = 0;
    double x, y, x0, y0, x1, y1, first_x = 0, first_y = 0;

    foreach (it, list) {
	ControlPoint *cp = [it getItem];

	[cp getX:&x Y:&y];
	[cp getControlPoint:0 X:&x0 Y:&y0];
	[cp getControlPoint:1 X:&x1 Y:&y1];
	steps = [cp steps];

	x -= offx; x0 -= offx; x1 -= offx;
	y -= offy; y0 -= offy; y1 -= offy;

	if (n == 0)
	    first_x = x, first_y = y;

	line(dbuf, x, y, x0, y0, white);
	line(dbuf, x, y, x1, y1, white);
	circle(dbuf, x, y, 8, (cp == cp_in_use) ? yellow : green);
	circle(dbuf, x0, y0, 4, red);
	circle(dbuf, x1, y1, 4, blue);
	textprintf(dbuf, font, x, y, white, "%d", n);
	textprintf(dbuf, font, 0, n * 10, (cp == cp_in_use) ? yellow : white,
                "%02d: (%03g, %03g) - %d", n, x - first_x, y - first_y, steps);
	n++;
    }

    y = n * 10;
    textprintf(dbuf, font, 0, y, cyan, "         total - %d", n);
}

/*--------------------------------------------------------------*/
/* Load/Save (old, uses PACKFILE).				*/
/*--------------------------------------------------------------*/
#if 0
static void export_file_pack(List *list)
{
    ListIterator *it;
    PACKFILE *fp;
    char path[1024] = "";
    double first_x, first_y;

    if (file_select_ex("Save as...", path, "spline", sizeof path, 480, 320) == 0)
	return;

    fp = pack_fopen(path, "wp");

    if (!fp)
	return;

    first_x = waypoint_array[0].x;
    first_y = waypoint_array[0].y;

    foreach (it, list) {
	ControlPoint *cp = [it getItem];
	double x, y, x0, y0, x1, y1;
	int steps;

	[cp getX:&x Y:&y];
	steps = [cp steps];
	[cp getControlPoint:0 X:&x0 Y:&y0];
	[cp getControlPoint:1 X:&x1 Y:&y1];

	pack_iputl(x - first_x, fp);
	pack_iputl(y - first_y, fp);
	pack_iputl(steps, fp);
	pack_iputl(x0 - x, fp);
	pack_iputl(y0 - y, fp);
	pack_iputl(x1 - x, fp);
	pack_iputl(y1 - y, fp);
    }

    pack_fclose(fp);
}
#endif

static unsigned int load_spline_pack(const char *fn, waypoint_t *waypoints[], List **ll)
{
    PACKFILE *fp;
    unsigned int len;
    List *l;

    assert(fn);

    if (!(fp = pack_fopen(fn, "rp")))
	return 0;

    if (ll)
	l = *ll;
    else
	l = [LIST_NEW];	   /* Create a temporary list of waypoints. */
    assert(l);

    while (!pack_feof(fp)) {
	ControlPoint *cp;
	double x, y, x0, y0, x1, y1;
	int steps;

	x = pack_igetl(fp);
	y = pack_igetl(fp);
	steps = pack_igetl(fp);
	x0 = pack_igetl(fp);
	y0 = pack_igetl(fp);
	x1 = pack_igetl(fp);
	y1 = pack_igetl(fp);

	cp = [[[[ControlPoint new] setX:x Y:y steps:steps]
                setControlPoint:0 X:x0 Y:y0]
                setControlPoint:1 X:x1 Y:y1];
	[l insertItemAtEnd:cp];
    }
    pack_fclose(fp);

    len = spline_length(l);
    (*waypoints) = articulate_spline(l);

    if (ll)
	*ll = l;
    else
	l = [l free];

    return len;
}

static List *import_file_pack(void)
{
    char fn[PATH_MAX] = "";
    List *list = [LIST_NEW];
    assert(list);

    if (file_select_ex("Load...", fn, "spline", sizeof fn, 480, 320) == 0)
	return NULL;

    total_waypoint_steps = load_spline_pack(fn, &waypoint_array, &list);
    return list;
}

/*--------------------------------------------------------------*/
/* Load/Save (new).						*/
/*--------------------------------------------------------------*/

/* Stolen from waypoint.m */
typedef struct {
    int x, y;		/* X/Y relative to first point. */
    unsigned int steps;
    int x0, y0, x1, y1;	/* Control point's x/y. */
} save_struct_t;

/* Stolen from allegro/src/file.c */
long iputl(long l, FILE *f)
{
   int b1, b2, b3, b4;
   ASSERT(f);

   b1 = (int)((l & 0xFF000000L) >> 24);
   b2 = (int)((l & 0x00FF0000L) >> 16);
   b3 = (int)((l & 0x0000FF00L) >> 8);
   b4 = (int)l & 0x00FF;

   if (fputc(b4,f)==b4)
      if (fputc(b3,f)==b3)
	 if (fputc(b2,f)==b2)
	    if (fputc(b1,f)==b1)
	       return l;

   return EOF;
}

static List *import_file(void)
{
    char fn[PATH_MAX] = "";
    List *list = [LIST_NEW];
    assert(list);

    if (file_select_ex("Load...", fn, "spline", sizeof fn, 480, 320) == 0)
	return NULL;

    total_waypoint_steps = load_spline(fn, &waypoint_array, &list);
    return list;
}

static void export_file(List *list)
{
    FILE *fp;
    ListIterator *it;
    char path[1024] = "";
    double first_x, first_y;

    if (file_select_ex("Save as...", path, "spline", sizeof path, 480, 320) == 0)
	return;

    fp = fopen(path, "wb");
    if (!fp)
	return;

    first_x = waypoint_array[0].x;
    first_y = waypoint_array[0].y;

    foreach (it, list) {
	ControlPoint *cp = [it getItem];
	double x, y, x0, y0, x1, y1;
	unsigned int steps;

	steps = [cp steps];
	[cp getX:&x Y:&y];
	[cp getControlPoint:0 X:&x0 Y:&y0];
	[cp getControlPoint:1 X:&x1 Y:&y1];

	iputl(x - first_x, fp);
	iputl(y - first_y, fp);
	iputl(steps, fp);
	iputl(x0 - x, fp);
	iputl(y0 - y, fp);
	iputl(x1 - x, fp);
	iputl(y1 - y, fp);
    }

    fclose(fp);
}

/*--------------------------------------------------------------*/

static ControlPoint *find_control_point_near(List *list, int x, int y)
{
    ControlPoint *cp;
    ListIterator *it;

    foreach (it, list) {
	double xx, yy;
        cp = [it getItem];

	[cp getControlPoint:0 X:&xx Y:&yy];
	if (SQ(x - xx) + SQ(y - yy) < SQ(6)) {
	    control_point_n = 0;
	    break;
	}

	[cp getControlPoint:1 X:&xx Y:&yy];
	if (SQ(x - xx) + SQ(y - yy) < SQ(6)) {
	    control_point_n = 1;
	    break;
	}

	[cp getX:&xx Y:&yy];
	if (SQ(x - xx) + SQ(y - yy) < SQ(8)) {
	    control_point_n = -1;
	    break;
	}
    }

    if (it) {
	[it free];
	return cp;
    }

    return NULL;
}

static void splined_loop(void)
{
    BOOL redraw = YES;
    List *list = [LIST_NEW];

    do {
	int mx = mouse_x + offx, my = mouse_y + offy;

        /* Snap to grid.  Real cheaped. */
	if (key[KEY_SPACE]) {
	    while (key[KEY_SPACE]);
	    snap_to_grid = !snap_to_grid;
	    redraw = YES;
	}

	/* Load/save. */
	if (key[KEY_L] && (key_shifts & KB_CTRL_FLAG)) {
	    List *new_list;

	    if (key_shifts & KB_SHIFT_FLAG)
		new_list = import_file_pack();
	    else
		new_list = import_file();

	    if (new_list) {
                cp_in_use = NULL;
		[list free];
		list = new_list;
		total_waypoint_steps = spline_length(list);
		redraw = YES;
	    }
	}
	if (key[KEY_S] && (key_shifts & KB_CTRL_FLAG))
	    export_file(list);

	/* Increase/decrease steps to reach control point. */
	if (cp_in_use) {
	    if (key[KEY_UP]) {
		[cp_in_use setSteps:[cp_in_use steps] + 1];
		redraw = YES;
	    }
	    if (key[KEY_DOWN]) {
		[cp_in_use setSteps:MAX(1, [cp_in_use steps] - 1)];
		redraw = YES;
	    }
	}

	/* Pan around the area. */
	if (key[KEY_H]) offx -= 3.0, redraw = YES;
	if (key[KEY_J]) offy += 3.0, redraw = YES;
	if (key[KEY_K]) offy -= 3.0, redraw = YES;
	if (key[KEY_L]) offx += 3.0, redraw = YES;

	/* Add or move a control point. */
	if ((mouse_b & 1) && !(mouse_held_lmb)) {
	    cp_in_use = find_control_point_near(list, mx, my);

	    /* Add one. */
	    if (!cp_in_use) {
		cp_in_use = [[ControlPoint new] setX:mx Y:my];
		[list insertItemAtEnd:cp_in_use];
		
		/* After we place one, we want to set the control points. */
		control_point_n = 0;
	    }

	    mouse_held_lmb = YES;
	}
	else if (mouse_held_lmb) {
	    if (cp_in_use) {
		double x, y;

		if (control_point_n < 0) {
		    if (snap_to_grid)
			x = mx/10*10, y = my/10*10;
		    else
			x = mx, y = my;

		    [cp_in_use setX:x Y:y];
		}
		else {
		    [cp_in_use getX:&x Y:&y];

		    x = mx - x;
		    y = my - y;

		    /* 90 degrees. */
		    if (key[KEY_LCONTROL]) {
			if (ABS(x) < ABS(y))
			    x = 0;
			else
			    y = 0;
		    }

		    [cp_in_use setControlPoint:control_point_n X:x Y:y];

		    /* If shift is not pressed, move the other one too. */
		    if (!key[KEY_LSHIFT])
			[cp_in_use setControlPoint:!control_point_n X:-x Y:-y];
		}
	    }

	    mouse_held_lmb = (mouse_b & 1);
	    redraw = YES;
	}

	/* Remove a control point. */
	if (mouse_b & 2) {
	    cp_in_use = find_control_point_near(list, mx, my);
	    if (cp_in_use) {
		[list removeItem:cp_in_use];
		cp_in_use = [cp_in_use free];
                redraw = YES;
	    }
	}

	/*----------*/

	if (redraw) {
	    int old_spline_length = total_waypoint_steps;

	    clear_to_color(dbuf, black);
	    rectfill_wh(dbuf, (SCREEN_W - screen_w)/2, 0, screen_w, screen_h, makecol(0x40, 0x40, 0x40));

	    total_waypoint_steps = spline_length(list);
	    if (total_waypoint_steps < old_spline_length) {
		waypoint_array = reticulate_spline(waypoint_array, list);
	    }
	    else {
		/* Need more room + lazy to muck with realloc = */
		FREE_SPLINE(waypoint_array);
		waypoint_array = articulate_spline(list);
	    }

	    draw_control_points(list);
	    draw_spline();

	    if (snap_to_grid)
		textout(dbuf, font, "Snapped", 0, SCREEN_H - 10, white);

	    scare_mouse();
	    blit(dbuf, screen, 0, 0, 0, 0, dbuf->w, dbuf->h);
	    unscare_mouse();

	    redraw = NO;
	}
    } while (!key[KEY_Q]);

    if (list)
        list = [list free];
}

/*--------------------------------------------------------------*/

#ifdef DEBUG_SPLINED
/*--------------------------------------------------------------*/
/* MTrace.  Note: some memory leaks out of my control.		*/
/*--------------------------------------------------------------*/

#include <mcheck.h>

@interface mtraceInit
@end

@implementation mtraceInit
+ (void)load
{
    setenv("MALLOC_TRACE", "./,splined-memtrace.log", 0);
    mtrace();
}
@end
#endif

static int splined_init(void)
{
    allegro_init();
    install_timer();

    set_color_depth(16);
    if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) < 0) {
	allegro_message("Error setting video mode.\n%s.\n", allegro_error);
	return -1;
    }
    set_window_title("Raid'em Spline Editor");

    dbuf = create_bitmap(SCREEN_W, SCREEN_H);
    clear_to_color(dbuf, makecol(0x00, 0x00, 0x00));

    /* Input devices. */
    install_keyboard();
    install_mouse();
    show_mouse(screen);

    /* Basic colours. */
    black   = makecol(0x00, 0x00, 0x00);
    red     = makecol(0xff, 0x00, 0x00);
    green   = makecol(0x00, 0xff, 0x00);
    blue    = makecol(0x00, 0x00, 0xff);
    yellow  = makecol(0xff, 0xff, 0x00);
    magenta = makecol(0xff, 0x00, 0xff);
    cyan    = makecol(0x00, 0xff, 0xff);
    white   = makecol(0xff, 0xff, 0xff);

    return 0;
}

static void splined_shutdown(void)
{
    FREE_BITMAP(dbuf);
    FREE_SPLINE(waypoint_array);
}

int main(void)
{
    if (splined_init() < 0)
	return -1;
    splined_loop();
    splined_shutdown();
    return 0;
}
END_OF_MAIN()
