/*
 * FIG : Facility for Interactive Generation of figures
 * Copyright (c) 1985 by Supoj Sutanthavibul
 *
 * "Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both the copyright
 * notice and this permission notice appear in supporting documentation. 
 * No representations are made about the suitability of this software for 
 * any purpose.  It is provided "as is" without express or implied warranty."
 */

#include "fig.h"
#include "resources.h"
#include "object.h"
#include "mode.h"
#include "u_bound.h"

#define		Ninety_deg		M_PI_2
#define		One_eighty_deg		M_PI
#define		Two_seventy_deg		(M_PI + M_PI_2)
#define		Three_sixty_deg		(M_PI + M_PI)
#define		half(z1 ,z2)		((z1+z2)/2.0)

/* macro which rounds DOWN the coordinates depending on point positioning mode */
#define		floor_coords(x) \
    if (cur_pointposn != P_ANY) { \
	    tmp_t = ((x) + 1) % posn_rnd[cur_pointposn]; \
	    (x) = (x) - tmp_t; \
	}

/* macro which rounds UP the coordinates depending on point positioning mode */
#define		ceil_coords(x) \
    if (cur_pointposn != P_ANY) { \
	    (x) = (x) + posn_rnd[cur_pointposn]; \
	    tmp_t = (x)%posn_rnd[cur_pointposn]; \
	    (x) = (x) - tmp_t - 1; \
	}

static void	points_bound();
static void	int_spline_bound();
static void	normal_spline_bound();
static int	tmp_t;

arc_bound(arc, xmin, ymin, xmax, ymax)
    F_arc	   *arc;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    float	    alpha, beta;
    double	    dx, dy, radius;
    int		    bx, by, sx, sy;
    int		    half_wd;

    dx = arc->point[0].x - arc->center.x;
    dy = arc->center.y - arc->point[0].y;
    alpha = atan2(dy, dx);
    if (alpha < 0.0)
	alpha += Three_sixty_deg;
    /* compute_angle returns value between 0 to 2PI */

    radius = sqrt((double) (dx * dx + dy * dy));

    dx = arc->point[2].x - arc->center.x;
    dy = arc->center.y - arc->point[2].y;
    beta = atan2(dy, dx);
    if (beta < 0.0)
	beta += Three_sixty_deg;

    bx = max2(arc->point[0].x, arc->point[1].x);
    bx = max2(arc->point[2].x, bx);
    by = max2(arc->point[0].y, arc->point[1].y);
    by = max2(arc->point[2].y, by);
    sx = min2(arc->point[0].x, arc->point[1].x);
    sx = min2(arc->point[2].x, sx);
    sy = min2(arc->point[0].y, arc->point[1].y);
    sy = min2(arc->point[2].y, sy);

    if (arc->direction == 1) {	/* counter clockwise */
	if (alpha > beta) {
	    if (alpha <= 0 || 0 <= beta)
		bx = (int) (arc->center.x + radius + 1.0);
	    if (alpha <= Ninety_deg || Ninety_deg <= beta)
		sy = (int) (arc->center.y - radius - 1.0);
	    if (alpha <= One_eighty_deg || One_eighty_deg <= beta)
		sx = (int) (arc->center.x - radius - 1.0);
	    if (alpha <= Two_seventy_deg || Two_seventy_deg <= beta)
		by = (int) (arc->center.y + radius + 1.0);
	} else {
	    if (0 <= beta && alpha <= 0)
		bx = (int) (arc->center.x + radius + 1.0);
	    if (Ninety_deg <= beta && alpha <= Ninety_deg)
		sy = (int) (arc->center.y - radius - 1.0);
	    if (One_eighty_deg <= beta && alpha <= One_eighty_deg)
		sx = (int) (arc->center.x - radius - 1.0);
	    if (Two_seventy_deg <= beta && alpha <= Two_seventy_deg)
		by = (int) (arc->center.y + radius + 1.0);
	}
    } else {			/* clockwise	 */
	if (alpha > beta) {
	    if (beta <= 0 && 0 <= alpha)
		bx = (int) (arc->center.x + radius + 1.0);
	    if (beta <= Ninety_deg && Ninety_deg <= alpha)
		sy = (int) (arc->center.y - radius - 1.0);
	    if (beta <= One_eighty_deg && One_eighty_deg <= alpha)
		sx = (int) (arc->center.x - radius - 1.0);
	    if (beta <= Two_seventy_deg && Two_seventy_deg <= alpha)
		by = (int) (arc->center.y + radius + 1.0);
	} else {
	    if (0 <= alpha || beta <= 0)
		bx = (int) (arc->center.x + radius + 1.0);
	    if (Ninety_deg <= alpha || beta <= Ninety_deg)
		sy = (int) (arc->center.y - radius - 1.0);
	    if (One_eighty_deg <= alpha || beta <= One_eighty_deg)
		sx = (int) (arc->center.x - radius - 1.0);
	    if (Two_seventy_deg <= alpha || beta <= Two_seventy_deg)
		by = (int) (arc->center.y + radius + 1.0);
	}
    }
    half_wd = arc->thickness / 2;
    *xmax = bx + half_wd;
    *ymax = by + half_wd;
    *xmin = sx - half_wd;
    *ymin = sy - half_wd;
}

compound_bound(compound, xmin, ymin, xmax, ymax)
    F_compound	   *compound;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    F_arc	   *a;
    F_ellipse	   *e;
    F_compound	   *c;
    F_spline	   *s;
    F_line	   *l;
    F_text	   *t;
    int		    bx, by, sx, sy, first = 1;
    int		    llx, lly, urx, ury;

    for (a = compound->arcs; a != NULL; a = a->next) {
	arc_bound(a, &sx, &sy, &bx, &by);
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    for (c = compound->compounds; c != NULL; c = c->next) {
	sx = c->nwcorner.x;
	sy = c->nwcorner.y;
	bx = c->secorner.x;
	by = c->secorner.y;
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    for (e = compound->ellipses; e != NULL; e = e->next) {
	ellipse_bound(e, &sx, &sy, &bx, &by);
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    for (l = compound->lines; l != NULL; l = l->next) {
	line_bound(l, &sx, &sy, &bx, &by);
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    for (s = compound->splines; s != NULL; s = s->next) {
	spline_bound(s, &sx, &sy, &bx, &by);
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    for (t = compound->texts; t != NULL; t = t->next) {
	int    dum;
	text_bound_actual(t, t->angle, &sx, &sy, &bx, &by, 
			  &dum,&dum,&dum,&dum,&dum,&dum,&dum,&dum);
	if (first) {
	    first = 0;
	    llx = sx;
	    lly = sy;
	    urx = bx;
	    ury = by;
	} else {
	    llx = min2(llx, sx);
	    lly = min2(lly, sy);
	    urx = max2(urx, bx);
	    ury = max2(ury, by);
	}
    }

    /* round the corners to the current positioning grid */
    floor_coords(llx);
    floor_coords(lly);
    ceil_coords(urx);
    ceil_coords(ury);
    *xmin = llx;
    *ymin = lly;
    *xmax = urx;
    *ymax = ury;
}

/* basically, use the code for drawing the ellipse to find its bounds */
/* From James Tough (see u_draw.c: angle_ellipse() */
/* include the bounds for the markers (even though we don't know if they 
   are on or off now */

ellipse_bound(e, xmin, ymin, xmax, ymax)
    F_ellipse	   *e;
    int		   *xmin, *ymin, *xmax, *ymax;
{
	int	    half_wd;
	double	    c1, c2, c3, c4, c5, c6, v1, cphi, sphi, cphisqr, sphisqr;
	double	    xleft, xright, d, asqr, bsqr;
	int	    yymax, yy=0;
	float	    xcen, ycen, a, b; 

	xcen = e->center.x;
	ycen = e->center.y;
	a = e->radiuses.x;
	b = e->radiuses.y;
	if (a==0 || b==0)
		{
		*xmin = *xmax = xcen;
		*ymin = *ymax = ycen;
		return;
		}

	cphi = cos((double)e->angle);
	sphi = sin((double)e->angle);
	cphisqr = cphi*cphi;
	sphisqr = sphi*sphi;
	asqr = a*a;
	bsqr = b*b;
	
	c1 = (cphisqr/asqr)+(sphisqr/bsqr);
	c2 = ((cphi*sphi/asqr)-(cphi*sphi/bsqr))/c1;
	c3 = (bsqr*cphisqr) + (asqr*sphisqr);
	yymax = sqrt(c3);
	c4 = a*b/c3;
	c5 = 0;
	v1 = c4*c4;
	c6 = 2*v1;
	c3 = c3*v1-v1;
	/* odd first points */
	*xmin = *ymin =  100000;
	*xmax = *ymax = -100000;
	if (yymax % 2) {
		d = sqrt(c3);
		*xmin = min2(*xmin,xcen-d);
		*xmax = max2(*xmax,xcen+d);
		*ymin = min2(*ymin,ycen);
		*ymax = max2(*ymax,ycen);
		c5 = c2;
		yy=1;
	}
	while (c3>=0) {
		d = sqrt(c3);
		xleft = c5-d;
		xright = c5+d;                        
		*xmin = min2(*xmin,xcen+xleft);
		*xmax = max2(*xmax,xcen+xleft);
		*ymax = max2(*ymax,ycen+yy);
		*xmin = min2(*xmin,xcen+xright);
		*xmax = max2(*xmax,xcen+xright);
		*ymax = max2(*ymax,ycen+yy);
		*xmin = min2(*xmin,xcen-xright);
		*xmax = max2(*xmax,xcen-xright);
		*ymin = min2(*ymin,ycen-yy);
		*xmin = min2(*xmin,xcen-xleft);
		*xmax = max2(*xmax,xcen-xleft);
		*ymin = min2(*ymin,ycen-yy);
		c5+=c2;
		v1+=c6;
		c3-=v1;
		yy=yy+1;
	}
	/* for simplicity, just add half the line thickness to xmax and ymax
	   and subtract half from xmin and ymin */
	half_wd = e->thickness/2;
	*xmax += half_wd;
	*ymax += half_wd;
	*xmin -= half_wd;
	*ymin -= half_wd;
	/* now include the markers because they could be outside the bounds of 
	   the ellipse (+/-3 is (roughly) half the size of the markers (5)) */
	*xmax = max2(*xmax, max2(e->start.x, e->end.x)+3);
	*ymax = max2(*ymax, max2(e->start.y, e->end.y)+3);
	*xmin = min2(*xmin, min2(e->start.x, e->end.x)-3);
	*ymin = min2(*ymin, min2(e->start.y, e->end.y)-3);
}

line_bound(l, xmin, ymin, xmax, ymax)
    F_line	   *l;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    points_bound(l->points, (l->thickness / 2), xmin, ymin, xmax, ymax);
}

spline_bound(s, xmin, ymin, xmax, ymax)
    F_spline	   *s;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    if (int_spline(s)) {
	int_spline_bound(s, xmin, ymin, xmax, ymax);
    } else {
	normal_spline_bound(s, xmin, ymin, xmax, ymax);
    }
}

static void
int_spline_bound(s, xmin, ymin, xmax, ymax)
    F_spline	   *s;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    F_point	   *p1, *p2;
    F_control	   *cp1, *cp2;
    float	    x0, y0, x1, y1, x2, y2, x3, y3, sx1, sy1, sx2, sy2;
    float	    tx, ty, tx1, ty1, tx2, ty2;
    float	    sx, sy, bx, by;
    int		    half_wd;

    half_wd = s->thickness / 2;
    p1 = s->points;
    sx = bx = p1->x;
    sy = by = p1->y;
    cp1 = s->controls;
    for (p2 = p1->next, cp2 = cp1->next; p2 != NULL;
	 p1 = p2, cp1 = cp2, p2 = p2->next, cp2 = cp2->next) {
	x0 = p1->x;
	y0 = p1->y;
	x1 = cp1->rx;
	y1 = cp1->ry;
	x2 = cp2->lx;
	y2 = cp2->ly;
	x3 = p2->x;
	y3 = p2->y;
	tx = half(x1, x2);
	ty = half(y1, y2);
	sx1 = half(x0, x1);
	sy1 = half(y0, y1);
	sx2 = half(sx1, tx);
	sy2 = half(sy1, ty);
	tx2 = half(x2, x3);
	ty2 = half(y2, y3);
	tx1 = half(tx2, tx);
	ty1 = half(ty2, ty);

	sx = min2(x0, sx);
	sy = min2(y0, sy);
	sx = min2(sx1, sx);
	sy = min2(sy1, sy);
	sx = min2(sx2, sx);
	sy = min2(sy2, sy);
	sx = min2(tx1, sx);
	sy = min2(ty1, sy);
	sx = min2(tx2, sx);
	sy = min2(ty2, sy);
	sx = min2(x3, sx);
	sy = min2(y3, sy);

	bx = max2(x0, bx);
	by = max2(y0, by);
	bx = max2(sx1, bx);
	by = max2(sy1, by);
	bx = max2(sx2, bx);
	by = max2(sy2, by);
	bx = max2(tx1, bx);
	by = max2(ty1, by);
	bx = max2(tx2, bx);
	by = max2(ty2, by);
	bx = max2(x3, bx);
	by = max2(y3, by);
    }
    *xmin = round(sx) - half_wd;
    *ymin = round(sy) - half_wd;
    *xmax = round(bx) + half_wd;
    *ymax = round(by) + half_wd;
}

static void
normal_spline_bound(s, xmin, ymin, xmax, ymax)
    F_spline	   *s;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    F_point	   *p;
    float	    cx1, cy1, cx2, cy2, cx3, cy3, cx4, cy4;
    float	    x1, y1, x2, y2, sx, sy, bx, by;
    float	    px, py, qx, qy;
    int		    half_wd;

    half_wd = s->thickness / 2;
    p = s->points;
    x1 = p->x;
    y1 = p->y;
    p = p->next;
    x2 = p->x;
    y2 = p->y;
    cx1 = (x1 + x2) / 2.0;
    cy1 = (y1 + y2) / 2.0;
    cx2 = (cx1 + x2) / 2.0;
    cy2 = (cy1 + y2) / 2.0;
    if (closed_spline(s)) {
	x1 = (cx1 + x1) / 2.0;
	y1 = (cy1 + y1) / 2.0;
    }
    sx = min2(x1, cx2);
    sy = min2(y1, cy2);
    bx = max2(x1, cx2);
    by = max2(y1, cy2);

    for (p = p->next; p != NULL; p = p->next) {
	x1 = x2;
	y1 = y2;
	x2 = p->x;
	y2 = p->y;
	cx4 = (x1 + x2) / 2.0;
	cy4 = (y1 + y2) / 2.0;
	cx3 = (x1 + cx4) / 2.0;
	cy3 = (y1 + cy4) / 2.0;
	cx2 = (cx4 + x2) / 2.0;
	cy2 = (cy4 + y2) / 2.0;

	px = min2(cx2, cx3);
	py = min2(cy2, cy3);
	qx = max2(cx2, cx3);
	qy = max2(cy2, cy3);

	sx = min2(sx, px);
	sy = min2(sy, py);
	bx = max2(bx, qx);
	by = max2(by, qy);
    }
    if (closed_spline(s)) {
	*xmin = round(sx) - half_wd;
	*ymin = round(sy) - half_wd;
	*xmax = round(bx) + half_wd;
	*ymax = round(by) + half_wd;
    } else {
	*xmin = round(min2(sx, x2)) - half_wd;
	*ymin = round(min2(sy, y2)) - half_wd;
	*xmax = round(max2(bx, x2)) + half_wd;
	*ymax = round(max2(by, y2)) + half_wd;
    }
}

/* This procedure calculates the bounding box for text that is displayed
   horizontally or vertically (all text on the canvas in otherwords)
   Use text_bound_actual() to decide whether or not text would be off
   the PRINTED page (if rotated) */

text_bound(t, xmin, ymin, xmax, ymax)
    F_text	   *t;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    int		    length, dx, dy, mx, my, dum;
    double	    angle;

    angle = t->angle;
    /* fix the angle to one of four - 0, 90, 180 or 270 */
    if (angle < M_PI_2 - 0.001)
	angle = 0.0;
    else if (angle < M_PI - 0.001)
	angle = M_PI_2;
    else if (angle < 3*M_PI_2 - 0.001)
	angle = M_PI;
    else
	angle = 3*M_PI_2;
    text_bound_actual(t, angle, xmin, ymin, xmax, ymax,
		  &dum, &dum, &dum, &dum, &dum, &dum, &dum, &dum);
}

/* this procedure calculates the bouding box for text ASSUMING that it 
   will be DISPLAYED rotated (if it has any rotation angle).
   The actual corners of the rectangle are returned in (rx1,ry1)...(rx4,ry4)
   The min and max x and y are returned in (xmin, ymin) (xmax, ymax)
*/

text_bound_actual(t, angle, xmin, ymin, xmax, ymax, 
		  rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4)
    F_text	   *t;
    double	    angle;
    int		   *xmin, *ymin, *xmax, *ymax;
    int		   *rx1,*ry1, *rx2,*ry2, *rx3,*ry3, *rx4,*ry4;
{
    int		    h, l;
    int		    x1,y1, x2,y2, x3,y3, x4,y4;
    double	    cost, sint;
    double	    lcost, lsint, hcost, hsint;

    l = text_length(t);
    h = t->height;
    cost = cos((double)angle);
    sint = sin((double)angle);
    lcost = round(l*cost);
    lsint = round(l*sint);
    hcost = round(h*cost);
    hsint = round(h*sint);
    x1 = t->base_x;
    y1 = t->base_y;
    if (t->type == T_CENTER_JUSTIFIED) {
	x1 = t->base_x - round((l/2)*cost);
	y1 = t->base_y + round((l/2)*sint);
	x2 = x1 + lcost;
	y2 = y1 - lsint;
    }
    else if (t->type == T_RIGHT_JUSTIFIED) {
	x1 = t->base_x - lcost;
	y1 = t->base_y + lsint;
	x2 = t->base_x;
	y2 = t->base_y;
    }
    else {
	x2 = x1 + lcost;
	y2 = y1 - lsint;
    }
    x4 = x1 - hsint;
    y4 = y1 - hcost;
    x3 = x2 - hsint;
    y3 = y2 - hcost;

    *xmin = min2(x1,min2(x2,min2(x3,x4)));
    *xmax = max2(x1,max2(x2,max2(x3,x4)));
    *ymin = min2(y1,min2(y2,min2(y3,y4)));
    *ymax = max2(y1,max2(y2,max2(y3,y4)));
    *rx1=x1; *ry1=y1;
    *rx2=x2; *ry2=y2;
    *rx3=x3; *ry3=y3;
    *rx4=x4; *ry4=y4;
}

/* this procedure calculates the union of the two types of bounding boxes */
/* this is usually called by the redisplay code which needs the bounding
   rectangle if the user is displaying the textoutline */

text_bound_both(t, xmin, ymin, xmax, ymax,
		  rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4)
    F_text	  *t;
    int		  *xmin, *ymin, *xmax, *ymax;
    int		   *rx1,*ry1, *rx2,*ry2, *rx3,*ry3, *rx4,*ry4;
{
    int		   xmin1, ymin1, xmax1, ymax1;
    int		   xmin2, ymin2, xmax2, ymax2;
    int		   dum;
    text_bound_actual(t, t->angle, &xmin1, &ymin1, &xmax1, &ymax1, 
		  rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4);
    text_bound(t, &xmin2, &ymin2, &xmax2, &ymax2);
    *xmin = min2(xmin1,xmin2);
    *xmax = max2(xmax1,xmax2);
    *ymin = min2(ymin1,ymin2);
    *ymax = max2(ymax1,ymax2);
}

static void
points_bound(points, half_wd, xmin, ymin, xmax, ymax)
    F_point	   *points;
    int		    half_wd;
    int		   *xmin, *ymin, *xmax, *ymax;
{
    int		    bx, by, sx, sy;
    F_point	   *p;

    bx = sx = points->x;
    by = sy = points->y;
    for (p = points->next; p != NULL; p = p->next) {
	sx = min2(sx, p->x);
	sy = min2(sy, p->y);
	bx = max2(bx, p->x);
	by = max2(by, p->y);
    }
    *xmin = sx - half_wd;
    *ymin = sy - half_wd;
    *xmax = bx + half_wd;
    *ymax = by + half_wd;
}

int
overlapping(xmin1, ymin1, xmax1, ymax1, xmin2, ymin2, xmax2, ymax2)
    int		    xmin1, ymin1, xmax1, ymax1, xmin2, ymin2, xmax2, ymax2;
{
    if (xmin1 < xmin2)
	if (ymin1 < ymin2)
	    return (xmax1 >= xmin2 && ymax1 >= ymin2);
	else
	    return (xmax1 >= xmin2 && ymin1 <= ymax2);
    else if (ymin1 < ymin2)
	return (xmin1 <= xmax2 && ymax1 >= ymin2);
    else
	return (xmin1 <= xmax2 && ymin1 <= ymax2);
}
