/*
 *  Copyright (c) 1994, Riley Rainey,  riley@netcon.com
 *
 *  Permission to use, copy, modify and distribute (without charge) this
 *  software, documentation, images, etc. is granted, provided that this 
 *  comment and the author's name is retained.
 *
 *  This software is provided by the author as is, and without any expressed
 *  or implied warranties, including, but not limited to, the implied
 *  warranties of merchantability and fitness for a particular purpose.  In no
 *  event shall the author be liable for any direct, indirect, incidental, or
 *  consequential damages arising in any way out of the use of this software.
 */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <X11/Intrinsic.h>
#include <Xm/DrawingA.h>
#include <Xm/TextF.h>

#include "../util/memory.h"
#include "shared.h"
#include "edit.h"
#include "gutil1.h"

#define gutil_IMPORT
#include "gutil.h"


/*
 *  Once we have three points entered, we can compute the equation associated
 *  with the plane defined by them.  The general equation looks like this:
 *
 *                      a x + b y + c z + d = 0
 *
 *  Where the normal vector is N=[a, b, c] and d = - a x0 - b y0 - c z0;
 *  the origin is O=[x0, y0, z0].
 *
 *  Once the plane equation is computed, it's a trivial task to determine
 *  the xyz coordinates of any subsequent points entered for this polygon
 *  -- since all points on the polygon are, by our definition, coplanar.
 */

#define magv(v) sqrt( v.x * v.x + v.y * v.y + v.z * v.z )

void
ComputePlaneEquation (polygon_t *p)
{

	VPoint          a, b;
	register double length;

	VSetPoint (&a,
		p->point[0].point.x - p->point[1].point.x,
		p->point[0].point.y - p->point[1].point.y,
		p->point[0].point.z - p->point[1].point.z);

	VSetPoint (&b,
		p->point[2].point.x - p->point[1].point.x,
		p->point[2].point.y - p->point[1].point.y,
		p->point[2].point.z - p->point[1].point.z);

	VCrossProd (&a, &b, &(p->normal));

	length = magv(p->normal);
	p->normal.x /= length;
	p->normal.y /= length;
	p->normal.z /= length;

	p->d = - VDotProd (&p->normal, &p->point[1].point);
	p->origin = p->point[1].point;
}

void
DisplayPoint(point_t *p)
{
	char    value[64];

	sprintf (value, "%d", p->x);
	XmTextFieldSetString (x_field, value);
	sprintf (value, "%d", p->y);
	XmTextFieldSetString (y_field, value);
	sprintf (value, "%d", p->z);
	XmTextFieldSetString (z_field, value);
}

polygon_t *
AllocPolygon ()
{
	register int i, n;
	register polygon_t *tmp;

	for (i=0; i < polygon_max; ++i) {
		if (polygon_list[i].num_points == 0) {
			polygon_list[i].next = -1;
			return &polygon_list[i];
		}
	}

	n = polygon_max * 2;

	tmp = (polygon_t *) memory_allocate (n * sizeof (polygon_t), NULL);

	bcopy ((char *) polygon_list, (char *) tmp,
		polygon_max * sizeof (polygon_t));

	for (i = polygon_max; i < n; ++i) {
		tmp[i].next = tmp[i].num_points = 0;
		tmp[i].id = i;
	}

	polygon_max = n;
	memory_dispose ((char *) polygon_list);
	polygon_list = tmp;
	return tmp;
}

void
FreePolygon (polygon_t *p)
{
	p->num_points = 0;
	if (p->point != tmp_point)
		memory_dispose ((char *) p->point);
}

polygon_t *
BeginPolygon ()
{

	register polygon_t *p;

	if ((p = AllocPolygon()) == (polygon_t *) NULL) {
		fprintf (stderr, "out of memory\n");
		exit (1);
	}

	p->num_points = 0;
	p->point = tmp_point;
	cur_polygon = p;

	return p;
}

void PointXY (Widget w, view_info_t     *p, int x, int y, point_t *q)
{
	switch (p->layout) {

	case VL_NXZ:
		q->x = x;
		q->y = p->other_view->origin_y;
		q->z = y;
		q->point.x = - (x - p->origin_x) * pixel_scale;
		q->point.y = 0.0;
		q->point.z = (y - p->origin_y) * pixel_scale;
		break;

	case VL_NXNY:
		q->x = x;
		q->y = y;
		q->z = p->other_view->origin_y;
		q->point.x = - (x - p->origin_x) * pixel_scale;
		q->point.y = - (y - p->origin_y) * pixel_scale;
		q->point.z = 0.0;
		break;

	case VL_NYZ:    
		q->x = p->other_view->origin_y;
		q->y = x;
		q->z = y;
		q->point.x = 0.0;
		q->point.y = - (x - p->origin_x) * pixel_scale;
		q->point.z = (y - p->origin_y) * pixel_scale;
		break;

	case VL_NYX:
		q->x = y;
		q->y = x;
		q->z = p->other_view->origin_y;
		q->point.x = (y - p->origin_y) * pixel_scale;
		q->point.y = -(x - p->origin_x) * pixel_scale;
		q->point.z = 0.0;
		break;

	default:
		printf ("oops\n");
	}

}

int
PinPoint (Widget w, view_info_t *p, polygon_t *poly, int x, int y, XPoint *pt, XPoint *opt)
{
	point_t         *q, *oq;
	int             z;
	Boolean         plane_established;

	if (poly->num_points == 0)
		oq = (point_t *) NULL;
	else
		oq = &poly->point[poly->num_points-1];

	q = &poly->point[poly->num_points++];

	plane_established = (poly->num_points > 3) ? True : False;

	switch (p->layout) {

	case VL_NXZ:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->x;
			pt->y = oq->z;
			opt->y = oq->y;
			q->y = oq->y;
		}
		else {
			q->y = p->other_view->origin_y;
		}
			
		q->x = x;
		q->z = y;
		z = q->y;
		q->point.x = - (x - p->origin_x) * pixel_scale;
		q->point.z = (y - p->origin_y) * pixel_scale;

		switch (p->other_view->layout) {

		case VL_NXNY:
			if (plane_established) {
				q->point.y = - (poly->normal.x * q->point.x +
					poly->normal.z * q->point.z + poly->d) /
					poly->normal.y;
				z = q->y = - q->point.y / pixel_scale +
					p->other_view->origin_y;
			}
			else 
				q->point.y = - (q->y - p->other_view->origin_y)*
					pixel_scale;
			break;

		case VL_NXY:
			if (plane_established) {
				q->point.y = - (poly->normal.x * q->point.x +
					poly->normal.z * q->point.z + poly->d) /
					poly->normal.y;
				z = q->y = q->point.y / pixel_scale +
					p->other_view->origin_y;
			}
			else
				q->point.y = (q->y - p->other_view->origin_y) *
					pixel_scale;
			break;
		}

		break;

	case VL_NXNY:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->x;
			pt->y = oq->y;
			opt->y = oq->z;
			q->z = oq->z;
		}
		else {
			q->z = p->other_view->origin_y;
		}

		q->x = x;
		q->y = y;
		z = q->z;
		q->point.x = - (x - p->origin_x) * pixel_scale;
		q->point.y = - (y - p->origin_y) * pixel_scale;

		switch (p->other_view->layout) {

		case VL_NXNZ:
			if (plane_established) {
				q->point.z = - (poly->normal.x * q->point.x +
					poly->normal.y * q->point.y + poly->d) /
					poly->normal.z;
				z = q->z = q->point.z / pixel_scale +
					p->other_view->origin_y;
			}
			else
				q->point.z = - (q->z - p->other_view->origin_y)*
					pixel_scale;
			break;

		case VL_NXZ:
			if (plane_established) {
				q->point.z = - (poly->normal.x * q->point.x +
					poly->normal.y * q->point.y + poly->d) /
					poly->normal.z;
				z = q->z = q->point.z / pixel_scale +
					p->other_view->origin_y;
			}
			else 
				q->point.z = (q->z - p->other_view->origin_y) *
					pixel_scale;
			break;
		}

		break;

	case VL_NYZ:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->y;
			pt->y = oq->z;
			opt->y = oq->x;
			q->x = oq->x;
		}
		else {
			q->x = p->other_view->origin_y;
		}

		q->y = x;
		q->z = y;
		z = q->x;
		q->point.y = - (x - p->origin_x) * pixel_scale;
		q->point.z = (y - p->origin_y) * pixel_scale;

		switch (p->other_view->layout) {

		case VL_NYX:
			if (plane_established) {
				q->point.x = - (poly->normal.y * q->point.y +
					poly->normal.z * q->point.z + poly->d) /
					poly->normal.x;
				z = q->x = q->point.x / pixel_scale +
					p->other_view->origin_y;
			}
			else 
				q->point.x = (q->x - p->other_view->origin_y) *
					pixel_scale;
			break;

		case VL_NYNX:
			if (plane_established) {
				q->point.x = - (poly->normal.y * q->point.y +
					poly->normal.z * q->point.z + poly->d) /
					poly->normal.x;
				z = q->x = - q->point.x / pixel_scale +
					p->other_view->origin_y;
			}
			else
				q->point.x = -(q->x - p->other_view->origin_y)*
					pixel_scale;
			break;
		}

		break;

	case VL_NYX:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->y;
			pt->y = oq->x;
			opt->y = oq->z;
			q->z = oq->z;
		}
		else {
			q->z = p->other_view->origin_y;
		}
			
		q->x = y;
		q->y = x;
		z = q->z;
		q->point.x = - (y - p->origin_x) * pixel_scale;
		q->point.y = (x - p->origin_y) * pixel_scale;

		switch (p->other_view->layout) {

		case VL_NYZ:
			if (plane_established) {
				q->point.z = - (poly->normal.x * q->point.x +
					poly->normal.y * q->point.y + poly->d) /
					poly->normal.z;
				z = q->z = q->point.z / pixel_scale +
					p->other_view->origin_y;
			}
			else 
				q->point.z = (q->z - p->other_view->origin_y)*
					pixel_scale;
			break;

		case VL_NYNZ:
			if (plane_established) {
				q->point.z = - (poly->normal.x * q->point.x +
					poly->normal.y * q->point.y + poly->d) /
					poly->normal.z;
				z = q->z = - q->point.z / pixel_scale +
					p->other_view->origin_y;
			}
			else
				q->point.z = - (q->z - p->other_view->origin_y)*
					pixel_scale;
			break;
		}

		break;
	}

	DisplayPoint (q);

	return z;
	
}

int
DragPoint (Widget w, view_info_t        *p, polygon_t *poly, int delta, XPoint *pt, XPoint *opt)
{
	point_t         *q, *oq;
	int             z;

	if (poly->num_points == 1)
		oq = (point_t *) NULL;
	else
		oq = &poly->point[poly->num_points-2];

	q = &poly->point[poly->num_points-1];

	switch (p->layout) {

	case VL_NXZ:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->x;
			pt->y = oq->z;
			opt->y = oq->y;
		}

		switch (p->other_view->layout) {

		case VL_NXNY:
			q->y = drag_origin.y + delta;
			q->point.y = - (q->y - p->other_view->origin_y) *
				pixel_scale;
			break;

		case VL_NXY:
			q->y = drag_origin.y + delta;
			q->point.y = (q->y - p->other_view->origin_y) *
				pixel_scale;
			break;
		}

		z = q->y;

		break;

	case VL_NXNY:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->x;
			pt->y = oq->y;
			opt->y = oq->z;
		}

		switch (p->other_view->layout) {

		case VL_NXNZ:
			q->z = drag_origin.y + delta;
			q->point.z = - (q->z - p->other_view->origin_y) *
				pixel_scale;
			break;

		case VL_NXZ:
			q->z = drag_origin.y + delta;
			q->point.z = (q->z - p->other_view->origin_y) *
				pixel_scale;
			break;
		}

		z = q->z;

		break;

	case VL_NYX:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->y;
			pt->y = oq->x;
			opt->y = oq->z;
		}

		switch (p->other_view->layout) {

		case VL_NYNZ:
			q->z = drag_origin.y + delta;
			q->point.z = - (q->z - p->other_view->origin_y) *
				pixel_scale;
			break;

		case VL_NYZ:
			q->z = drag_origin.y + delta;
			q->point.x = (q->z - p->other_view->origin_y) *
				pixel_scale;
			break;
		}

		z = q->z;

		break;

	case VL_NYZ:
		if (oq != (point_t *) NULL) {
			pt->x = opt->x = oq->y;
			pt->y = oq->z;
			opt->y = oq->x;
		}

		switch (p->other_view->layout) {

		case VL_NYNX:
			q->x = drag_origin.y + delta;
			q->point.x = - (q->x - p->other_view->origin_y) *
				pixel_scale;
			break;

		case VL_NYX:
			q->x = drag_origin.y + delta;
			q->point.x = (q->x - p->other_view->origin_y) *
				pixel_scale;
			break;
		}

		z = q->x;

		break;
	}

	DisplayPoint (q);

	return z;
	
}

void
BeginPolygonPoint (Widget w, view_info_t *p, int x, int y)
{

	int             z;
	Display         *dpy;
	Drawable        w1, w2;
	XPoint          last, last_other;
	polygon_t       *poly;

	dpy = XtDisplay (w);
	w1 = XtWindow (w);
	w2 = XtWindow (p->other_widget);

	if (cur_polygon == NULL)
		BeginPolygon ();

	poly = cur_polygon;

	drag_origin.x = x;
	drag_origin.y = y;
	XSetFunction (dpy, p->gc, GXxor);
	XSetForeground (dpy, p->gc, app_data.select_pixel);
	XSetLineAttributes (dpy, p->gc, app_data.selection_thickness, LineSolid,
		CapButt, JoinMiter);

	XFillRectangle (dpy, w1, p->gc,
		x + app_data.box_offset, y + app_data.box_offset,
		app_data.box_size, app_data.box_size);
	z = PinPoint (w, p, poly, x, y, &last, &last_other);
	XFillRectangle (dpy, w2, p->gc,
		x + app_data.box_offset, z + app_data.box_offset,
		app_data.box_size, app_data.box_size);

	if (poly->num_points > 1) {
		XDrawLine (dpy, w1, p->gc, last.x, last.y, x, y);
		XDrawLine (dpy, w2, p->gc, last_other.x, last_other.y, x, z);
		rubber_lines[0].x1 = last.x;
		rubber_lines[0].y1 = last.y;
		rubber_lines[0].x2 = x;
		rubber_lines[0].y2 = y;
		rubber_lines[1].x1 = last_other.x;
		rubber_lines[1].y1 = last_other.y;
	}
	rubber_lines[1].x2 = x;
	rubber_lines[1].y2 = z;
	XSetFunction (dpy, p->gc, GXcopy);

}

void
DragPolygonPoint(Widget w, view_info_t *p, int x, int y)
{
	int             z, nx, delta;
	Display         *dpy;
	//Drawable      w1;
	Drawable        w2;
	XPoint          last, last_other;
	polygon_t       *poly;

	dpy = XtDisplay (w);
	//w1 = XtWindow (w);
	w2 = XtWindow (p->other_widget);

	poly = cur_polygon;

/*
 *  We don't drag points once the polygon's plane is determined.
 */

	if (poly->num_points > 3)
		return;

/*
 *  Erase old stuff
 */
	XSetFunction (dpy, p->other_view->gc, GXxor);
	XFillRectangle (dpy, w2, p->other_view->gc,
		rubber_lines[1].x2 + app_data.box_offset,
		rubber_lines[1].y2 + app_data.box_offset,
		app_data.box_size, app_data.box_size);
	if (poly->num_points > 1) {
		XDrawSegments (dpy, w2, p->other_view->gc,
			&rubber_lines[1], 1);
	}

/*
 *  Draw new line/point
 */

	delta = y - drag_origin.y;
	nx = rubber_lines[1].x2;
	z = DragPoint (w, p, poly, delta, &last, &last_other);
	XFillRectangle (dpy, w2, p->other_view->gc,
		nx + app_data.box_offset, z + app_data.box_offset,
		app_data.box_size, app_data.box_size);

	if (poly->num_points > 1) {
		XDrawLine (dpy, w2, p->other_view->gc,
			last_other.x, last_other.y, nx, z);
	}
	rubber_lines[1].y2 = z;
	
	XSetFunction (dpy, p->other_view->gc, GXcopy);
}

void
CompletePolygonPoint(Widget w, view_info_t *p, int x, int y)
{
	if (cur_polygon)
		if (cur_polygon->num_points == 3)
			ComputePlaneEquation (cur_polygon);
}

void
CompletePolygon (Widget w, polygon_t *p)
{

	register point_t *points;
	view_info_t     *q;

	XtVaGetValues (w,
		XmNuserData,    &q,
		NULL);

	if (p == (polygon_t *) NULL)
		return;

	if (p->num_points < 3) {
		p->num_points = 0;
		return;
	}

	points = (point_t *) memory_allocate (p->num_points * sizeof(point_t), NULL);

	bcopy ((char *) tmp_point, (char *) points,
		p->num_points * sizeof (point_t));

	p->point = points;

/*
 *  Add this polygon to the selected list.
 */

	p->next = sel_polygon;
	sel_polygon = p->id;
	cur_polygon = (polygon_t *) NULL;

#ifdef notdef
	DrawPolygon (w, p, True);
	DrawPolygon (q->other_widget, p, True);
#endif
	DrawWidget (w, False);
	DrawWidget (q->other_widget, False);

}

int
PolygonProximity (view_info_t *p, polygon_t *poly, int x, int y)
{

	register long   i, min_distance, d;
	int             xp, yp;

	min_distance = 0x3fffffff;

	for (i=0; i < poly->num_points; ++ i) {
		WorldToWidget (p, &poly->point[i], &xp, &yp);
		xp -= x;
		yp -= y;
		d = (int) sqrt ((double) (xp * xp + yp * yp));
		if (d < min_distance) {
			min_distance = d;
		}
	}

	return min_distance;
}

int
PickObject (view_info_t *p, int x, int y)
{

	register int    i, dist, d, id = -1;

	dist = 0x3fffffff;

	for (i=0; i < polygon_max; ++i) {
		if (polygon_list[i].num_points == 0)
			continue;
		if ((d = PolygonProximity (p, &polygon_list[i], x, y)) < dist) {
			dist = d;
			id = i;
		}
	}

	if (dist < app_data.pick_sensitivity)
		return id;
	else
		return -1;
}

void
CompleteMarker(Widget w, view_info_t *p, int x, int y)
{
	if (cur_polygon) {
		marker_list[current_marker].defined = True;
		marker_list[current_marker].location = cur_polygon->point[0];
		cur_polygon = NULL;
		DrawWidget (w, False);
		DrawWidget (p->other_widget, False);
	}
}

void
BeginPick (Widget w, view_info_t *p, int x, int y, Boolean extend)
{

	register int    id, last, i;

	id = PickObject (p, x, y);

	if (extend) {
		if (id >= 0) {
			SelectObject (id);
		}
	}
	else {
		if (id == -1) { 
		    if (sel_polygon >= 0) {
			for (i=sel_polygon; i >= 0; i = polygon_list[i].next)
				last = i;
			polygon_list[last].next = unsel_polygon;
			unsel_polygon = sel_polygon;
			sel_polygon = -1;
		    }
		}
		else if (sel_polygon >= 0) {
			drag_origin.x = x;
			drag_origin.y = y;
			drag_mode = True;
		}
		else {
			SelectObject (id);
		}
	}

	DrawWidget (w, False);
	DrawWidget (p->other_widget, False);
}

void
DragSelection (Widget w, view_info_t *p, int x, int y)
{
	register int    dx, dy, i, j;
	point_t         q;
	polygon_t       *poly;

	dx = x - drag_origin.x + p->origin_x;
	dy = y - drag_origin.y + p->origin_y;
	drag_origin.x = x;
	drag_origin.y = y;

	PointXY (w, p, dx, dy, &q);

	for (i=sel_polygon; i >= 0; i = polygon_list[i].next)
		DrawSelectedPolygon (w, &polygon_list[i], True, True);

	for (i=sel_polygon; i >= 0; i = polygon_list[i].next) {
		poly = &polygon_list[i];
		for (j=0; j<poly->num_points; ++j) {
			poly->point[j].point.x += q.point.x;
			poly->point[j].point.y += q.point.y;
			poly->point[j].point.z += q.point.z;
			PointToXYZ (p, &poly->point[j]);
		}
		DrawSelectedPolygon (w, poly, True, False);
	}

}

void
CompleteDrag (Widget w, view_info_t *p, int x, int y)
{
	drag_mode = False;
	DrawWidget (w, False);
	DrawWidget (p->other_widget, False);
}

void
SelectObject (int id)
{
	register int    i, last;

	if (unsel_polygon == -1) {
		polygon_list[id].next = sel_polygon;
		sel_polygon = id;
		return;
	}

	if (unsel_polygon == id) {
		unsel_polygon = polygon_list[id].next;
		polygon_list[id].next = sel_polygon;
		sel_polygon = id;
		return;
	}

	last = unsel_polygon;   
	for (i=polygon_list[unsel_polygon].next; i >= 0;
		i = polygon_list[i].next) {
		if (i == id) {
			polygon_list[last].next = polygon_list[id].next;
			polygon_list[id].next = sel_polygon;
			sel_polygon = id;
			return;
		}
		last = i;
	}
		
}

void
DetermineObjectExtent (VPoint *extent)
{

	VPoint min, max;
	register int i, j;
	polygon_t *poly;

	min.x = min.y = min.z =  10000000.0;
	max.x = max.y = max.z = -10000000.0;

	for (i=0; i < polygon_max; ++i) {
		if (polygon_list[i].num_points == 0)
			continue;
		poly = &polygon_list[i];
		for (j=0; j<poly->num_points; ++j) {
			if (poly->point[j].point.x > max.x)
				max.x = poly->point[j].point.x;
			if (poly->point[j].point.y > max.y)
				max.y = poly->point[j].point.y;
			if (poly->point[j].point.z > max.z)
				max.z = poly->point[j].point.z;
			if (poly->point[j].point.x < min.x)
				min.x = poly->point[j].point.x;
			if (poly->point[j].point.y < min.y)
				min.y = poly->point[j].point.y;
			if (poly->point[j].point.z < min.z)
				min.z = poly->point[j].point.z;
		}
	}

	extent->x = max.x - min.x;
	extent->y = max.y - min.y;
	extent->z = max.z - min.z;
}

void
RescaleObject (double factor)
{

	register int    i, j;
	polygon_t       *poly;

	for (i=0; i < polygon_max; ++i) {
		if (polygon_list[i].num_points == 0)
			continue;
		poly = &polygon_list[i];
		for (j=0; j<poly->num_points; ++j) {
			poly->point[j].point.x *= factor;
			poly->point[j].point.y *= factor;
			poly->point[j].point.z *= factor;
		}
	}

	for (i=0; i < marker_count; ++i) {
		if (marker_list[i].defined) {
			marker_list[i].location.point.x *= factor;
			marker_list[i].location.point.y *= factor;
			marker_list[i].location.point.z *= factor;
		}
	}

	RescaleView (twindow, 1.0);
}
