#!/usr/bin/nickle

autoimport Cairo;
autoload PRNG;

int
sign(int x)
{
	return x == 0 ? 0 : x < 0 ? -1 : 1;
}

int X_AXIS = 0;
int Y_AXIS = 1;

typedef struct {
	int	major;
	int	minor;
	int	sign_major;
	int	sign_minor;
	int	e;
	int	e1;
	int	e3;
	bool	first;
} clip_context;

typedef struct {
	int	maj1, min1, maj2, min2;
} clip_box;

typedef struct {
	int	x1, y1, x2, y2;
} box;

typedef struct {
	int	x, y;
} point;

typedef struct {
	int	x1, y1, x2, y2;
	box	b;
	point[]	clipped;
	point[]	run;
} test;

box	bounds = { .x1 = 10, .x2 = 30, .y1 = 10, .y2 = 30 };

int
div_ceil(a, b) {
	a += b;
	assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b);
	return (a + b - 1) // b - 1;
}

int
div_floor_plus_one(a, b) {
	a += b;
	assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b);
	return a // b;
}

bool
clip(*clip_context c, *clip_box b)
{
	int	adjust_major = 0, adjust_minor = 0;

	/* Clip major axis */
	if (c->major < b->maj1) {
		if (c->sign_major <= 0)
			return false;
		adjust_major = b->maj1 - c->major;
	} else if (c->major >= b->maj2) {
		if (c->sign_major >= 0)
			return false;
		adjust_major = c->major - (b->maj2-1);
	}

	/* Clip minor axis */
	if (c->minor < b->min1) {
		if (c->sign_minor <= 0)
			return false;
		adjust_minor = b->min1 - c->minor;
	} else if (c->minor >= b->min2) {
		if (c->sign_minor >= 0)
			return false;
		adjust_minor = c->minor - (b->min2-1);
	}

	/* If unclipped, we're done */
	if (adjust_major == 0 && adjust_minor == 0)
		return true;

	/* See how much minor adjustment would happen during
	 * a major clip. This is a bit tricky because line drawing
	 * isn't symmetrical when the line passes exactly between
	 * two pixels, we have to pick which one gets drawn
	 */
	int	adj_min;

	if (!c->first)
		adj_min = div_ceil(c->e + adjust_major * c->e1, -c->e3);
	else
		adj_min = div_floor_plus_one(c->e + adjust_major * c->e1, -c->e3);

	/* Compare that to the minor clip and pick
	 * the larger amount.
	 */
	printf ("\tinitial major %d minor %d error %d e1 %d e3 %d\n", c->major, c->minor, c->e, c->e1, c->e3);

	if (adj_min < adjust_minor) {
		printf("\tminor clip dominates %d < %d. adjust major %d -> ",
		       adj_min, adjust_minor, adjust_major);
		if (c->first)
			adjust_major = div_ceil(c->e - adjust_minor * c->e3, c->e1);
		else
			adjust_major = div_floor_plus_one(c->e - adjust_minor * c->e3, c->e1);
		printf("%d\n", adjust_major);
	} else {
		printf("\tminor clip dominates %d > %d. adjust minor %d -> ",
		       adj_min, adjust_minor, adjust_minor);
		adjust_minor = adj_min;
		printf("%d\n", adjust_minor);
	}

	c->e += adjust_major * c->e1 + adjust_minor * c->e3;

	c->major += c->sign_major * adjust_major;
	c->minor += c->sign_minor * adjust_minor;

	printf ("\tadjust major %d adjust minor %d e %d e1 %d e3 %e\n",
		adjust_major, adjust_minor, c->e, c->e1, c->e3);

	if (c->e >= 0)
		printf ("error positive e %d e1 %d e3 %d\n",
			c->e, c->e1, c->e3);
	if (c->e < c->e3)
		printf ("error magnitude too large e %d e1 %d e3 %d\n", c->e, c->e1, c->e3);

	return true;
}

test
line(int x1, int y1, int x2, int y2, *box b) {

	int	dx = x2 - x1;
	int	dy = y2 - y1;
	int	signdx = sign(dx);
	int	signdy = sign(dy);
	int	adx = abs(dx);
	int	ady = abs(dy);
	int	axis;
	int	e, e1, e2, e3;
	int	len;
	clip_context	clip_1, clip_2;
	clip_box	c;
	bool		clipped = false;
	test		t = {
		.x1 = x1,
		.y1 = y1,
		.x2 = x2,
		.y2 = y2,
		.b = *b,
		.clipped = (point[...]) {},
		.run = (point[...]) {}
	};

	if (adx >= ady) {
		axis = X_AXIS;
		e1 = ady << 1;
		e2 = e1 - (adx << 1);
		e = e1 - adx;
		len = adx;

		clip_1.major = x1;
		clip_1.minor = y1;
		clip_2.major = x2;
		clip_2.minor = y2;
		clip_1.sign_major = signdx;
		clip_1.sign_minor = signdy;

		c.maj1 = b->x1;
		c.maj2 = b->x2;
		c.min1 = b->y1;
		c.min2 = b->y2;
	} else {
		axis = Y_AXIS;
		e1 = adx << 1;
		e2 = e1 - (ady << 1);
		e = e1 - ady;
		len = ady;

		clip_1.major = y1;
		clip_1.minor = x1;
		clip_2.major = y2;
		clip_2.minor = x2;
		clip_1.sign_major = signdy;
		clip_1.sign_minor = signdx;
		c.maj1 = b->y1;
		c.maj2 = b->y2;
		c.min1 = b->x1;
		c.min2 = b->x2;
	}

	e3 = e2 - e1;
	e = e - e1;

	clip_1.first = true;
	clip_2.first = false;
	clip_2.e = clip_1.e = e;
	clip_2.e1 = clip_1.e1 = e1;
	clip_2.e3 = clip_1.e3 = e3;
	clip_2.sign_major = -clip_1.sign_major;
	clip_2.sign_minor = -clip_1.sign_minor;

	printf ("clip start:\n");
	if (!clip(&clip_1, &c))
		clipped = true;

	printf("clip end:\n");
	if (!clip(&clip_2, &c))
		clipped = true;

	int	clip_len;
	int	clip_x, clip_y;
	int	clip_e;
	int	x_major, x_minor;
	int	y_major, y_minor;

	clip_len = clip_1.sign_major * (clip_2.major - clip_1.major);
	if (clip_len < 0)
		clipped = true;

	int x, y;

	if (axis == X_AXIS) {
		x = clip_1.major;
		y = clip_1.minor;
		x_major = clip_1.sign_major;
		x_minor = 0;
		y_major = 0;
		y_minor = clip_1.sign_minor;
	} else {
		x = clip_1.minor;
		y = clip_1.major;
		x_major = 0;
		x_minor = clip_1.sign_minor;
		y_major = clip_1.sign_major;
		y_minor = 0;
	}

	clip_e = clip_1.e;

	if (clipped)
		clip_len = -1;

	while (clip_len-- >= 0) {
		t.clipped[dim(t.clipped)] = (point) { .x = x, .y = y };
		x += x_major;
		y += y_major;
		clip_e += e1;
		if (clip_e >= 0) {
			x += x_minor;
			y += y_minor;
			clip_e += e3;
		}
	}

	x = x1;
	y = y1;

	while (len-- >= 0) {
		if (bounds.x1 <= x && x < bounds.x2 &&
		    bounds.y1 <= y && y < bounds.y2) {
			t.run[dim(t.run)] = (point) { .x = x, .y = y };
		}
		x += x_major;
		y += y_major;
		e += e1;
		if (e >= 0) {
			x += x_minor;
			y += y_minor;
			e += e3;
		}
	}
	return t;
}

void read_events (Cairo::cairo_t cr)
{
	file	event = Cairo::open_event(cr);

	while (!File::end(event)) {
		string	event_line = File::fgets(event);
		if (String::index(event_line, "delete") >= 0)
			exit(0);
	}
}

#for (int y = 0; y < 20; y++)

void
show(cairo_t cr, test t)
{
	rectangle(cr, 0, 0, 40, 40);
	set_source_rgba(cr, 1, 1, 1, 1);
	fill(cr);

	set_source_rgba(cr, 0, 1, 0, .2);
	set_line_width(cr, 0.1);
	for (int x = 0; x < 40; x++) {
		move_to(cr, 0, x);
		line_to(cr, 40, x);
		move_to(cr, x, 0);
		line_to(cr, x, 40);
	}
	stroke(cr);

	rectangle(cr, t.b.x1, t.b.y1, t.b.x2 - t.b.x1, t.b.y2 - t.b.y1);
	set_line_width(cr, 0.1);
	set_source_rgba(cr, 0, 0, 0, 1);
	stroke(cr);

	move_to(cr, t.x1+.5, t.y1+.5);
	line_to(cr, t.x2+.5, t.y2+.5);
	move_to(cr, t.x2, t.y2);
	line_to(cr, t.x2+1, t.y2+1);
	move_to(cr, t.x2+1, t.y2);
	line_to(cr, t.x2, t.y2+1);
	stroke(cr);

	void pixels(point[] pt) {
		for (int i = 0; i < dim(pt); i++) {
			rectangle(cr, pt[i].x, pt[i].y, 1, 1);
		}
		fill(cr);
	}

	set_source_rgba(cr, 1, 0, 0, .5);
	pixels(t.clipped);

	set_source_rgba(cr, 0, 0, 1, .5);
	pixels(t.run);
}

bool
compare(test t)
{
	if (dim(t.clipped) != dim(t.run))
		return false;

	for (int i = 0; i < dim(t.clipped); i++)
		if (t.clipped[i] != t.run[i])
			return false;
	return true;
}

void
doit(int i)
{
	int	n;
	*box	b = &bounds;

	cairo_t cr = new(800, 800);

	scale(cr, 20, 20);

	for (;;) {
		PRNG::srandom(i);
		int	x1 = PRNG::randint(40);
		int	x2 = PRNG::randint(40);
		int	y1 = PRNG::randint(40);
		int	y2 = PRNG::randint(40);

		test t = line (x1, y1, x2, y2, &bounds);
		show(cr, t);
		if (!compare(t)) {
			printf("line %d -- %d x %d - %d x %d\n", i, x1, y1, x2, y2);
			gets();
		}
		i++;
	}

	read_events(cr);
}

int i = 0;
if (dim(argv) > 1)
	i = atoi(argv[1]);

doit(i);
