// File: FigAltEdge.java
// Classes: FigAltEdge
// Original Authors: Kedar Patankar
// $Id: FigAltEdge.java,v 1.1.1.1 1997/02/27 20:52:34 chandra Exp $

package uci.graphedit;

import java.awt.Point;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Polygon;
import java.util.Hashtable;

public class FigAltEdge extends Fig {

  public static final int FUDGE_FACTOR = 4;
  private Point _destIntersection,_sourceIntersection;
  private Point _destShaftend;
  double ArrowAngle=Math.PI/7.0;
  double ArrowShaft = 12;
  private double angle=0.0;


  public FigAltEdge(Integer x, Integer y, Integer r_width, Integer r_height, Color l_color)
  {
	this(x.intValue(), y.intValue(),  r_width.intValue(),  r_height.intValue(), l_color);
  }

  public FigAltEdge(int x, int y, int r_width, int r_height, Color l_color)
  {
	super(x, y,  r_width,  r_height, l_color, null, 2);
  }

  /** Construct a new FigAltEdge with the given coordinates and attributes. */
  public FigAltEdge(int x,int y,int r_width,int r_height, Hashtable gAttrs){
    super(x, y, r_width, r_height, Color.black, null, 2);
    setGraphicAttributes(gAttrs);
  }

  public void set_position(Point src, Point dst)
  {
	  position().x = src.x;
	  position().y = src.y;
	  objectWidth = dst.x;
	  objectHeight = dst.y;
  }


  public void draw (Graphics g, Color l_color,Point s,Point d) 
  { 
	  Color old = objectLineColor;
	  objectLineColor = l_color;
	  draw(g,s,d);
	  objectLineColor = old;
  }
	  
  public void draw(Graphics g,Point sourceSize,Point destSize)
  {
	  Point source=position();
	  Point dest=new Point(objectWidth,objectHeight);
	  _destIntersection=findIntersection(source,dest,destSize);
	  if (_destIntersection==null) { return;}
	  int x,y;
	  if((source.x-dest.x)>=0)
	  {
		  x=(int)(_destIntersection.x+(ArrowShaft*Math.cos(angle)));
		  y=(int)(_destIntersection.y-(ArrowShaft*Math.sin(angle)));
		  _destShaftend=new Point(x,y);
	  }
	  else
	  {
		  x=(int)(_destIntersection.x-(ArrowShaft*Math.cos(angle)));
		  y=(int)(_destIntersection.y+(ArrowShaft*Math.sin(angle)));
		  _destShaftend=new Point(x,y);
	  }
	  
	  _sourceIntersection=findIntersection(dest,source,sourceSize);
	  if (_sourceIntersection==null) { return;}
	  draw(g);
  }

  private Point findIntersection(Point source,Point dest,Point destSize)
  {
	  double dX = source.x - dest.x;
	  double dY = dest.y - source.y;
	  double x1 = dest.x;
	  double y1 = dest.y;
	  double width = destSize.x/2.0;
	  double height = destSize.y/2.0;

	  Point intersection=null;
	  if((dX==0) && (dY==0))
		  return intersection;
	  if(dX!=0)
		  angle=Math.atan(dY/dX);
	  else 
	  {
		  if(dY > 0)
			  angle=Math.PI/2.0;
		  else
			  angle= -Math.PI/2.0;
	  }

	  double limit = Math.atan(height/width);

	  if (dX>=0)							// The right half of the rectangle
	  {
		  int x,y;
		  if(Math.abs(angle)<=limit)		// covers   -limit<= angle <=limit
		  {
			  x=(int)(x1+width);
			  y=(int)(y1-(width*dY/dX));
			  intersection = new Point(x,y);
		  }
		  else 
		  if(angle > limit)					// covers   angle <= 90
		  {
			  x=(int)(x1+(height*dX/dY));
			  y=(int)(y1-height);
			  intersection = new Point(x,y);
		  }
		  else								// covers   -90 <= angle
		  {
			  x=(int)(x1-(height*dX/dY));
			  y=(int)(y1+height);
			  intersection = new Point(x,y);
		  }
		  
		  return intersection;
	  }
	  else									// The left half of the rectangle
	  {
		  int x,y;
		  if(Math.abs(angle)<=limit)		// covers   -limit<= angle <=limit
		  {
			  x=(int)(x1-width);
			  y=(int)(y1+(width*dY/dX));
			  intersection = new Point(x,y);
		  }
		  else 
		  if(angle > limit)					// covers   angle <= 90
		  {
			  x=(int)(x1-(height*dX/dY));
			  y=(int)(y1+height);
			  intersection = new Point(x,y);
		  }
		  else								// covers   -90 <= angle
		  {
			  x=(int)(x1+(height*dX/dY));
			  y=(int)(y1-height);
			  intersection = new Point(x,y);
		  }
		  return intersection;
	  }
  }


  /** draw this line object */
  public void draw(Graphics g)
  {
	g.setColor(objectLineColor);
	g.drawLine(_sourceIntersection.x, _sourceIntersection.y, 
		_destIntersection.x,_destIntersection.y);
	drawArrowHead(g);
  }

  public void drawArrowHead(Graphics g)
  {
	Point p1=new Point(0,0);
	Point p2=new Point(0,0);
	
	int x1=(int) (
					( _destIntersection.x )+ 
					(_destShaftend.x - _destIntersection.x)*Math.cos(ArrowAngle) -
					(_destShaftend.y - _destIntersection.y)*Math.sin(ArrowAngle)
				  );
	int y1=(int) (
					( _destIntersection.y )+ 
					(_destShaftend.x - _destIntersection.x)*Math.sin(ArrowAngle) +
					(_destShaftend.y - _destIntersection.y)*Math.cos(ArrowAngle)
				  );

	p1=new Point(x1,y1);

	int x2=(int) (
					( _destIntersection.x )+ 
					(_destShaftend.x - _destIntersection.x)*Math.cos(ArrowAngle) +
					(_destShaftend.y - _destIntersection.y)*Math.sin(ArrowAngle)
				  );
	int y2=(int) (
					( _destIntersection.y )- 
					(_destShaftend.x - _destIntersection.x)*Math.sin(ArrowAngle) +
					(_destShaftend.y - _destIntersection.y)*Math.cos(ArrowAngle)
				  );

	p2=new Point(x2,y2);

	  Polygon arrowHead = new Polygon();
	  arrowHead.addPoint(_destIntersection.x,_destIntersection.y);
	  arrowHead.addPoint(p1.x,p1.y);
	  arrowHead.addPoint(p2.x,p2.y);
	  Color c=g.getColor();
	  g.setColor(Color.white);
	  g.fillPolygon(arrowHead);
	  g.setColor(c);
	g.drawLine(_destIntersection.x,_destIntersection.y,p1.x,p1.y);

	g.drawLine(_destIntersection.x,_destIntersection.y,p2.x,p2.y);
	  g.drawLine(p1.x,p1.y,p2.x,p2.y);
  }
  /** draw this line when it is selected. */
  public void drawSelected(Graphics g)
  {
    _handleRects[0].x = position().x - 3;
    _handleRects[0].y = position().y  - 3;
    _handleRects[1].x = objectWidth - 3;
    _handleRects[1].y = objectHeight - 3;
    drawHandles(g);
  }

  /** When this Fig is selected, the Editor should use an instance of
   * SelectionHandles to record that fact. */
  public Selection selectionObject() { return new SelectionHandles(this); }


  /** Reply true iff the given point is "near" the line. Nearness
   * allows the user to more easily select the line with the
   * mouse. Needs-More-Work: I should probably have two functions
   * inside() which gives a strict geometric version, and near() which
   * is for selection by mouse clicks. */
  public boolean inside(int x, int y)
  {
    return intersects(new Rectangle(x - GRIP_MARGIN, y - GRIP_MARGIN,
				    2 * GRIP_MARGIN, 2 * GRIP_MARGIN));
  }


  /** Reply true if the given point is counter-clockwise from the
  * vector defined by the position of this line and its endpoint. This
  * is used as in determining intersection between lines and
  * rectangles. Taken from Algorithms in C by Sedgewick, page
  * 350. */
  int counterClockWise(int x, int y)
  {
    Point p0 = position();
    Point p1 = new Point(objectWidth, objectHeight);
    Point p2 = new Point(x, y); /* the point to test */
    int dx1 = p1.x - p0.x;
    int dy1 = p1.y - p0.y;
    int dx2 = p2.x - p0.x;
    int dy2 = p2.y - p0.y;
    if (dx1*dy2 > dy1*dx2) return +1;
    if (dx1*dy2 < dy1*dx2) return -1;
    if ((dx1*dx2 < 0) || (dy1*dy2 < 0)) return -1;
    if ((dx1*dx1+dy1*dy1) < (dx2*dx2+dy2*dy2)) return +1;
    return 0;
  }

  /** Reply true iff this line passes through, or is even partly
  * inside the given rectangle, or if any corner of the rect is on the
  * line. What happens if the line runs along one edge of the rect,
  * but not all the way to either corner? */
  public boolean intersects(Rectangle r)
  {
    if (! r.intersects(getBoundingBox())) return false;

    int ccw1 = counterClockWise(r.x, r.y);
    int ccw2 = counterClockWise(r.x, r.y + r.height);
    int ccw3 = counterClockWise(r.x + r.width, r.y);
    int ccw4 = counterClockWise(r.x + r.width, r.y + r.height);

    // reply true iff any of the points are on opposite sides of the
    // line, or if any of them are on the line

    return  ((ccw1 ==  1 || ccw2 ==  1 || ccw3 ==  1 || ccw4 ==  1) &&
	     (ccw1 == -1 || ccw2 == -1 || ccw3 == -1 || ccw4 == -1) ||
	      ccw1 ==  0 || ccw2 ==  0 || ccw3 ==  0 || ccw4 ==  0);
  }


  /** Reply a rect that encloses this line. I add a few pixels on
   * either side so that the rect will have non-zero area. */
  public Rectangle getBoundingBox()
  {
    Point p = position();
    return new Rectangle(Math.min(p.x, objectWidth) - 1,
			 Math.min(p.y, objectHeight) - 1,
			 Math.max(p.x, objectWidth) -
			 Math.min(p.x, objectWidth) + 2,
			 Math.max(p.y, objectHeight) -
			 Math.min(p.y, objectHeight) + 2);
  }


} /* end class FigAltEdge */