import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.awt.Rectangle;

/*
 * This class implements a TurtleGraphics Line graphics Operation.
 * A line of a specified penWidth is drawn between two TurtleSpace
 * points.
 *
 * *NOTE* The points supplied to a TGLineOp constructor are arranged
 * such that lines are always drawn left to right (increasing X
 * values). This consistency is necessary to insure identical pixels 
 * are painted for fat lines that should be identical lines, say
 * erasing a fat black line by drawing an identical white line over
 * it, e.g., (SETPS 2 FORWARD 100 WAIT 1000 SETC 0 BACK 100). For an
 * angular line, rounding differences can cause the BACK to generate
 * a line that has a different width.  The above sequence with the
 * turtle heading at 45 degrees is a good example.
 *
 * @author Guy Haas
 */

class TGLineOp implements ImageObserver, TGGraphicsOp
{

   private Color color;
   private double heading;
   private int penWidth;
   private int[] xPoints;
   private int[] yPoints;
   private TGPoint p1, p2;


   //
   // constructor
   //
   public TGLineOp(TGPoint pt1, TGPoint pt2, double hd, Color color, int wid)
   {
      if ( pt1.xFloatValue() <= pt2.xFloatValue() )
      {
         this.p1 = pt1;
         this.p2 = pt2;
         this.heading = hd;
      }
      else
      {
         this.p1 = pt2;
         this.p2 = pt1;
         this.heading = hd - Math.PI;
         if ( this.heading < 0 )
            this.heading += (2 * Math.PI);
      }
      this.color = color;
      this.penWidth = wid;
   }


   //
   // ImageObserver interface methods
   //
   public boolean imageUpdate(Image img, int flags, int x, int y, int wd, int ht)
   {
      System.err.println( "TGLineOp.imageUpdate: got here!" );
      return true;
   }


   private Rectangle drawHorizontalFatLine(Graphics g, int canvasHeight, int canvasWidth)
   {
      float hafWid =  ((float)penWidth) / 2.0F;
      int y = p1.imageY( hafWid, canvasHeight );
      int p1X = p1.imageX(canvasWidth);
      int p2X = p2.imageX(canvasWidth);
      int x = (p1X < p2X) ? p1X : p2X;
      int lineWidth = Math.abs( p1X - p2X );
      g.setClip( x, y, lineWidth, penWidth );
      g.fillRect( x, y, lineWidth, penWidth );
      return new Rectangle(x, y, lineWidth, penWidth);

   } // end drawHorizontalFatLine()


   // drawFatLine - the turtle's pen is wider than 1 pixel
   // but... the special case of movement of only a single pixel (e.g. "fd 1")
   //        must be handled
   private Rectangle drawFatLine( Graphics g, int canvasHeight, int canvasWidth )
   {
      double qtrDegRads = Math.PI / 720.0;
      if ( heading < qtrDegRads || heading > (Math.PI*2.0 - qtrDegRads) )
         return drawHorizontalFatLine( g, canvasHeight, canvasWidth );
      if ( heading > (Math.PI/2.0 - qtrDegRads) && heading < (Math.PI + Math.PI/2.0 + qtrDegRads) )
         return drawVerticalFatLine( g, canvasHeight, canvasWidth );
      float xDf = Math.abs( p1.xFloatValue() - p2.xFloatValue() );
      float yDf = Math.abs( p1.yFloatValue() - p2.yFloatValue() );
      if ( xDf < 1.5F && yDf < 1.5F )
         return drawOnePixelFatLine(g, canvasHeight, canvasWidth);
      int p1X = p1.imageX( canvasWidth );
      int p1Y = p1.imageY( canvasHeight );
      int p2X = p2.imageX( canvasWidth );
      int p2Y = p2.imageY( canvasHeight );
      // compute the width end points of perpendicular line at p1
      double hafWid = ((double) penWidth) / 2.0;
      double perpLineHead = heading + (Math.PI / 2.0);
      if ( perpLineHead > 2.0 * Math.PI )
         perpLineHead -= 2.0 * Math.PI;
      TGPoint point = p1.otherEndPoint( perpLineHead, hafWid );
      int p1LeftX = point.imageX( canvasWidth );
      int p1LeftDX = p1LeftX - p1X;
      int p1LeftY = point.imageY( canvasHeight );
      int p1LeftDY = p1LeftY - p1Y;
      perpLineHead = heading - (Math.PI / 2.0);
      if ( perpLineHead < 0.0 )
         perpLineHead += 2.0 * Math.PI;
      point = p1.otherEndPoint( perpLineHead, hafWid );
      int p1RightX = point.imageX( canvasWidth );
      int p1RightDX = p1RightX - p1X;
      int p1RightY = point.imageY( canvasHeight );
      int p1RightDY = p1RightY - p1Y;
      if ( xPoints == null )
      {
         xPoints = new int[4];
         yPoints = new int[4];
      }
      xPoints[0] = p1LeftX;
      yPoints[0] = p1LeftY;
      xPoints[1] = p1RightX;
      yPoints[1] = p1RightY;
      xPoints[2] = p2X + p1RightDX;
      yPoints[2] = p2Y + p1RightDY;
      xPoints[3] = p2X + p1LeftDX;
      yPoints[3] = p2Y + p1LeftDY;
      int crX = min( xPoints );
      int crWidth = Math.abs( crX - max(xPoints) ) + 1;
      int crY = min( yPoints );
      int crHeight = Math.abs( crY - max(yPoints) ) + 1;
      g.setClip( crX, crY, crWidth, crHeight );
      g.fillPolygon( xPoints, yPoints, 4 );
      return new Rectangle( crX, crY, crWidth, crHeight );

   } // end drawFatLine()


   // draw a line perpendicular to current heading
   // with its midpoint at p1 (which equals p2)...
   private Rectangle drawOnePixelFatLine(Graphics g, int canvasHeight, int canvasWidth)
   {
      // compute the width end points of perpendicular line at p1
      double hafWid = ((double) penWidth) / 2.0;
      double perpLineHead = heading + (Math.PI / 2.0);
      if ( perpLineHead > 2.0 * Math.PI )
         perpLineHead -= 2.0 * Math.PI;
      TGPoint leftPt = p1.otherEndPoint( perpLineHead, hafWid );
      int ptLeftX = leftPt.imageX( canvasWidth );
      int ptLeftY = leftPt.imageY( canvasHeight );
      perpLineHead = heading - (Math.PI / 2.0);
      if ( perpLineHead < 0.0 )
         perpLineHead += 2.0 * Math.PI;
      TGPoint rightPt = leftPt.otherEndPoint( perpLineHead, (double)penWidth );
      int ptRightX = rightPt.imageX( canvasWidth );
      int ptRightY = rightPt.imageY( canvasHeight );
      int crX = ptLeftX < ptRightX ? ptLeftX : ptRightX;
      int crWidth = Math.abs(ptLeftX - ptRightX) + 1;
      int crY = ptLeftY < ptRightY ? ptLeftY : ptRightY;
      int crHeight = Math.abs(ptLeftY - ptRightY) + 1;
      g.setClip( crX, crY, crWidth, crHeight );
      g.drawLine( ptLeftX, ptLeftY, ptRightX, ptRightY );
      return new Rectangle( crX, crY, crWidth, crHeight );

   } // end drawOnePixelFatLine()


   private Rectangle drawVerticalFatLine( Graphics g, int canvasHeight, int canvasWidth )
   {
      float hafWid =  ((float)penWidth) / 2.0F;
      int x = p1.imageX( -hafWid, canvasWidth );
      int p1Y = p1.imageY(canvasHeight);
      int p2Y = p2.imageY(canvasHeight);
      int y = (p1Y < p2Y) ? p1Y : p2Y;
      int height = Math.abs( p1Y - p2Y );
      g.setClip( x, y, penWidth, height );
      g.fillRect( x, y, penWidth, height );
      return new Rectangle(x, y, penWidth, height);

   } // end drawVerticalFatLine()


   private int max( int[] ary )
   {
      int num = ary[0];
      for ( int i=1; i < ary.length; i++ )
         if ( ary[i] > num )
            num = ary[i];
      return num;

   } // end max()


   private int min( int[] ary )
   {
      int num = ary[0];
      for ( int i=1; i < ary.length; i++ )
         if ( ary[i] < num )
            num = ary[i];
      return num;

   } // end min()


   public Rectangle doIt( Image inMemoryImage )
   {
      int crX, crY, crHeight, crWidth;
      Rectangle clipRect;
      int imageWidth = inMemoryImage.getWidth( this );
      if ( imageWidth < 0 )
         return null;
      int imageHeight = inMemoryImage.getHeight( this );
      if ( imageHeight < 0 )
         return null;
      Graphics g = inMemoryImage.getGraphics();
      g.setColor(color);
      if ( penWidth == 1 )
      {
         int p1X = p1.imageX( imageWidth );
         int p1Y = p1.imageY( imageHeight );
         int p2X = p2.imageX( imageWidth );
         int p2Y = p2.imageY( imageHeight );
         crX = p1X < p2X ? p1X : p2X;
         crWidth = Math.abs( p1X - p2X ) + 1;
         crY = p1Y < p2Y ? p1Y : p2Y;
         crHeight = Math.abs( p1Y - p2Y ) + 1;
         g.setClip( crX, crY, crWidth, crHeight );
         g.drawLine( p1X, p1Y, p2X, p2Y );
         clipRect = new Rectangle( crX, crY, crWidth, crHeight );
      }
      else
         clipRect = drawFatLine( g, imageHeight, imageWidth );
      g.dispose();
      return clipRect;

   } // end doIt()


   public Color getColor()
   { return color; }


   public String toString()
   {
      return "TGLineOp[color="+color+",width="+penWidth+",p1="+p1+",p2="+p2+"]";
   }

} // end class TGLineOp