import java.awt.Color;

/**
 * TurtlePixels is an interface that a class implements when
 * it wants to provide the pixels that make up the image of
 * a turtle.
 *
 * @author Guy Haas
 */

public abstract class TurtlePixels
{
   //
   // constants
   //
   private static final int MAX_TURTLE_HEIGHT = 100;
   private static final int MAX_TURTLE_WIDTH =  100;
   private static final int MIN_TURTLE_HEIGHT =   5;
   private static final int MIN_TURTLE_WIDTH =    5;
   private static final int BLACK_OPAQUE_PIXEL = 0xff000000;
   private static final int PIXEL_OPACITY_BITS = 0xff000000;
   private static final int WHITE_OPAQUE_PIXEL = 0xffffffff;


   // variables with class-wide scope
   //
   private boolean[] turtleFillMask;    // used to determine which pixels in
                                        // baseTurtlePixels[] change when the
                                        // pen color changes
   private Color turtleColor;		// inner pixels Color
   private float turtleHeading;         // radians in conventional/AWT manner
   private int pixRectSideSize;         // this needs to be computed such
                                        // that it is larger than either the
                                        // turtle's height or the turtle's
                                        // width so the corners of the image
                                        // of the turtle are not clipped when
                                        // drawn at 45 degree angles
   private int turtleHeight;            // the height of the turtle as given
                                        // in the constructor
   private int turtleWidth;             // the width of the turtle as given
                                        // in the constructor
   private int[] baseTurtlePixels;      // pixel array for the turtle in an
                                        // EASTern orientation, i.e. headed
                                        // along the positive X axis
   private int[] turtlePixRect;         // pixel array for current turtle's
                                        // image - if it's visible.  it is
                                        // oriented in the current heading
                                        // and its body is filled with the
                                        // current color


   //
   // TurtlePixels Constructor
   //
   public TurtlePixels( int width, int height, Color color, float heading )
   {
      if ( height > MAX_TURTLE_HEIGHT )
         height = MAX_TURTLE_HEIGHT;
      if ( height < MIN_TURTLE_HEIGHT )
         height = MIN_TURTLE_HEIGHT;
      if ( width > MAX_TURTLE_WIDTH )
         width = MAX_TURTLE_WIDTH;
      if ( width < MIN_TURTLE_WIDTH )
         width = MIN_TURTLE_WIDTH;
      turtleHeight = height;
      turtleWidth = width;
      if ( height >= width )
         pixRectSideSize = height + height/4;
      else
         pixRectSideSize = width + width/4;
      if ( pixRectSideSize % 2 != 0 )
         pixRectSideSize++;
      baseTurtlePixels = new int[pixRectSideSize * pixRectSideSize];
      for (int pixIdx=0; pixIdx < baseTurtlePixels.length; pixIdx++)
         baseTurtlePixels[pixIdx] = WHITE_OPAQUE_PIXEL;
      initTurtlePixels( baseTurtlePixels, pixRectSideSize );
      clearToTurtleEdge();
      buildTurtleFillMask();
      turtleColor = color;
      int pixel = turtleColor.getRGB();
      pixel |= PIXEL_OPACITY_BITS;
      for (int pixIdx=0; pixIdx < baseTurtlePixels.length; pixIdx++)
         if ( turtleFillMask[pixIdx] == true )
            baseTurtlePixels[pixIdx] = pixel;
      turtleHeading = heading;
      updateTurtlePixels( heading );

   } // end TurtlePixels()


   // Build a mask of pixels that are to be changed when
   // the foreground color is changed.  Starting with
   // the center pixel of the turtle image, a flood-fill
   // algorithm is used to identify all pixels with the
   // same color value.  These pixels are set to true in
   // a mask array. colorTurtle() uses this mask to change
   // the color of the turtle.
   //
   // Although baseTurtlePixels is manipulated as an array
   // with a single index, it is really a two dimensional
   // array, a series of rows of pixels.  Each row is
   // pixRectSideSize pixels in width.  This explains why
   // pixelIndex = rowNumber * pixRectSideSize + columnNumber
   // is used to calculate a pixel's index.
   //
   private void buildTurtleFillMask()
   {
      float center = ((float)pixRectSideSize ) / 2.0F;
      int row = Math.round(center) - 1;
      int column = row;
      int color = baseTurtlePixels[ row * pixRectSideSize + column ];
      turtleFillMask = new boolean[pixRectSideSize * pixRectSideSize];
      floodFill( row, column, color );

   } // end buildTurtleFillMask


   // circles have an eight-way symmetry to them
   // only the points on a 45 degree segment (one eighth) of a circle
   // need to be computed - the rest are reflections of this point
   // given a center-point and deltas to a point on the circle, fill
   // eight points on the circle with a supplied value
   // 
   private void circlePixels( int ctrX, int ctrY, int dX, int dY, int value )
   {
      fillPixel(  ctrX + dX,  ctrY + dY, value );
      fillPixel(  ctrX + dY,  ctrY + dX, value );
      fillPixel(  ctrX + dY,  ctrY - dX, value );
      fillPixel(  ctrX + dX,  ctrY - dY, value );
      fillPixel(  ctrX - dX,  ctrY - dY, value );
      fillPixel(  ctrX - dY,  ctrY - dX, value );
      fillPixel(  ctrX - dY,  ctrY + dX, value );
      fillPixel(  ctrX - dX,  ctrY + dY, value );

   } // end circlePixels


   // clearToTurtleEdge - set all white pixels outside the
   // edge of the turtle to be transparent
   //
   private void clearToTurtleEdge()
   {
      for (int y=0; y < pixRectSideSize; y++ )
         for (int x=0; x < pixRectSideSize; x++ )
         {
            int i = y * pixRectSideSize + x;
            if ( (baseTurtlePixels[i] & 0x00ffffff) == 0x00ffffff )
               baseTurtlePixels[i] = 0;
            else
               break;
         }
      for (int y=0; y < pixRectSideSize; y++ )
         for (int x=pixRectSideSize-1; x >= 0; x-- )
         {
            int i = y * pixRectSideSize + x;
            if ( (baseTurtlePixels[i] & 0x00ffffff) == 0x00ffffff )
               baseTurtlePixels[i] = 0;
            else
               break;
         }
      for (int x=0; x < pixRectSideSize; x++ )
         for (int y=0; y < pixRectSideSize; y++ )
         {
            int i = y * pixRectSideSize + x;
            if ( baseTurtlePixels[i]  == 0 )
               continue;
            if ( (baseTurtlePixels[i] & 0x00ffffff) == 0x00ffffff )
               baseTurtlePixels[i] = 0;
            else
               break;
         }
      for (int x=0; x < pixRectSideSize; x++ )
         for (int y=pixRectSideSize-1; y >= 0; y-- )
         {
            int i = y * pixRectSideSize + x;
            if ( baseTurtlePixels[i]  == 0 )
               continue;
            if ( (baseTurtlePixels[i] & 0x00ffffff) == 0x00ffffff )
               baseTurtlePixels[i] = 0;
            else
               break;
         }

   } // end clearToTurtleEdge()


   // NOTE: x1 MUST BE <= x2
   //
   private void fillHorizLine( int x1, int x2, int y, int value )
   {
      for (int x=x1; x <= x2; x++)
         fillPixel( x, y, value );

   } // end fillHorizLine()


   // NOTE: y1 MUST BE <= y2
   //
   private void fillVertLine( int x, int y1, int y2, int value )
   {
      for (int y=y1; y <= y2; y++)
         fillPixel( x, y, value );

   } // end fillVertLine()


   // Fill in pixels that make up a line where X is increasing in
   // integer units while Y values are changing by a specified delta.
   // NOTE: x0 MUST BE <= x1
   //
   private void fillXUnitLine(int x0, int y0, int x1, int y1, int value)
   {
      //System.out.print("TurtlePixels.fillXUnitLine: x0="+x0+", y0="+y0);
      //System.out.println(", x1="+x1+", y1="+y1);
      int dX = x1 - x0;
      boolean negSlope = false;
      int dY = y1 - y0;
      int y = y0;
      if  ( dY < 0 )
      {
         negSlope = true;
         dY = -dY;
      }
      int d = 2 * dY - dX;
      int incrE = 2 * dY;
      int incrNE = 2 * (dY - dX);
      fillPixel( x0, y0, value );
      while ( x0 < x1 )
      {
         x0++;
         if ( d <= 0 )
            d += incrE;
         else
         {
            d += incrNE;
            y++;
         }
         if ( negSlope )
            fillPixel( x0, y0-(y-y0), value );
         else
            fillPixel( x0, y, value );
      }

   } // end fillXUnitLine()


   // Fill in pixels that make up a line where Y is increasing in
   // integer units while X values are changing by a specified delta.
   // NOTE: y1 MUST BE <= y2
   //
   private void fillYUnitLine(int x0, int y0, int  x1, int  y1, int value)
   {
      //System.out.print("TurtlePixels.fillYUnitLine: x0="+x0+", y0="+y0);
      //System.out.println(", x1="+x1+", y1="+y1);
      boolean negSlope = false;
      int dX = x1 - x0;
      int x = x0;
      if  ( dX < 0 )
      {
         negSlope = true;
         dX = -dX;
      }
      int dY = y1 - y0;
      int d = 2 * dX - dY;
      int incrE = 2 * dX;
      int incrNE = 2 * (dX - dY);
      fillPixel( x0, y0, value );
      while ( y0 < y1 )
      {
         y0++;
         if ( d <= 0 )
            d += incrE;
         else
         {
            d += incrNE;
            x++;
         }
         if ( negSlope )
            fillPixel( x0-(x-x0), y0, value );
         else
            fillPixel( x, y0, value );
      }

   } // end fillYUnitLine()


   private void floodFill( int row, int column, int color )
   {
      int pixIdx = row * pixRectSideSize + column;
      if ( baseTurtlePixels[pixIdx] == color && !turtleFillMask[pixIdx] )
      {
         turtleFillMask[pixIdx] = true;
         if ( row - 1 >= 0 )
            floodFill( row - 1, column, color );
         if ( row + 1 < pixRectSideSize )
            floodFill( row + 1, column, color );
         if ( column - 1 >= 0 )
            floodFill( row, column - 1, color );
         if ( column + 1 < pixRectSideSize )
            floodFill( row, column + 1, color );
      }

   } // end floodFill


   //private void printPixels(String what, int[] pixels, int width, int height)
   //{
   //   for ( int y=0; y < height; y++ )
   //   {
   //      for ( int x=0; x < width; x++ )
   //      {
   //         int i = y * width + x;
   //         int a = (pixels[i] >> 24) & 0xff;
   //         int r = (pixels[i] >> 16) & 0xff;
   //         int g = (pixels[i] >> 8) & 0xff;
   //         int b = pixels[i] & 0xff;
   //         System.out.println(what+"["+i+"] a="+a+", r="+r+", g="+g+", b="+b);
   //      }
   //      System.out.println( "----------" );
   //   }
   //
   //} // end printPixels()


   private float slope( int x1, int y1, int x2, int y2 )
   { return ((float) y2 - y1) / ((float) x2 - x1); }


   // fill turtlePixRect[] to reflect the specified heading (in radians)
   // which is the amount of rotation needed since baseTurtlePixels[] is
   // aligned to 0.0.  i use a 'Reverse-Rotation' algorithm, computing
   // which pixel in the original image maps to every pixel in the new
   // image (vs projecting forward) to avoid holes due to rounding errors.
   // since i'm rotating around the center of the image (not its origin),
   // three steps are needed:
   // (1) translate origin to center,
   // (2) reverse rotatation of pixel x,y, and
   // (3) translate back to initial origin.
   //
   private void updateTurtlePixels( float heading )
   {
      if ( turtlePixRect == null )
         turtlePixRect = new int[pixRectSideSize * pixRectSideSize];
      if ( heading == 0.0F )
         // no translation if heading aligned with orig image
         for (int i=0; i < pixRectSideSize * pixRectSideSize; i++)
            turtlePixRect[i] = baseTurtlePixels[i];
      else
      {
         // int center = Math.round(((float)pixRectSideSize ) / 2.0F) - 1;
         int center = pixRectSideSize / 2;
         double theta = (double) heading;
         double cosTheta = Math.cos( theta );
         double sinTheta = Math.sin( theta );

         // first zap destination, make all pixels transparent
         for (int y=0; y < pixRectSideSize; y++)
            for (int x=0; x < pixRectSideSize; x++)
               turtlePixRect[y * pixRectSideSize + x] = 0;
         for (int row=0; row < pixRectSideSize; row++)
         {
            int rowIdx = row * pixRectSideSize;
            int rowPrime = 2*(row - center) + 1;
            for (int col=0; col < pixRectSideSize; col++)
            {
               int colPrime = 2*(col - center) + 1;
               int srcX = (int) Math.rint(colPrime*cosTheta - rowPrime*sinTheta);
               srcX =  (srcX - 1) / 2 + center;
               int srcY = (int) Math.rint(colPrime*sinTheta + rowPrime*cosTheta);
               srcY =  (srcY - 1) / 2 + center;
               if ( srcX < 0 || srcX >= pixRectSideSize )
                  continue;
               if ( srcY < 0 || srcY >= pixRectSideSize )
                  continue;
               int pixelIndex = srcY * pixRectSideSize + srcX;
               int pixel = baseTurtlePixels[ pixelIndex ];
               turtlePixRect[rowIdx + col] = pixel;
            }
         }
      }

   } //end updateTurtlePixels()


   // ---------------------------------------------------------------
   // Methods available outside the this class, sorted alphabetically
   // ---------------------------------------------------------------

   /**
    * fill pixels along the circumference of a circle
    *
    * This is an implementation of the simplest form of the midpoint
    * algorithm as described in Computer Graphics, Foley and van Dam 
    */
   public void fillCirclePixels( int ctrX, int ctrY, int radius, int value )
   {
      int deltaX = 0;
      int deltaY = radius;
      float midptVal = 5.0F / 4.0F - (float) radius;
      circlePixels( ctrX, ctrY, deltaX, deltaY, value );
      while ( deltaY > deltaX )
      {
         if ( midptVal < 0 )
         {
            midptVal += 2.0F * (float)deltaX + 3.0F;
            deltaX++;
         }
         else
         {
            midptVal += 2.0F * (float)(deltaX-deltaY) + 5.0F;
            deltaX++;
            deltaY--;
         }
         circlePixels( ctrX, ctrY, deltaX, deltaY, value );
      }

   } // end fillCirclePixels()


   public void fillLinePixels( int x0, int y0, int x1, int y1, int value )
   {
      //System.out.print("TurtlePixels.fillLinePixels: x0="+x0+", y0="+y0);
      //System.out.println(", x1="+x1+", y1="+y1);
      if ( x0 == x1 )
         if ( y0 <= y1 )
            fillVertLine( x0, y0, y1, value );
         else
            fillVertLine( x0, y1, y0, value );
      else if ( y0 == y1 )
         if ( x0 <= x1 )
            fillHorizLine( x0, x1, y0, value );
         else
            fillHorizLine( x1, x0, y0, value );
      else if ( Math.abs(slope(x0,y0,x1,y1)) > 1.0F )
         if ( y0 < y1 )
            fillYUnitLine(x0, y0, x1, y1, value);
         else
            fillYUnitLine(x1, y1, x0, y0, value);
      else if ( x0 < x1 )
         fillXUnitLine(x0, y0, x1, y1, value);
      else
         fillXUnitLine(x1, y1, x0, y0, value);

   } // end fillLinePixels()


   /**
    * Fill the specified (x,y) pixel in the turtle's pixels with
    * value.  Used in child's initTurtlePixels() to paint the
    * turtle's image.
    *
    */
   public void fillPixel( int x, int y, int value )
   {
      String me = "TurtlePixels.fillPixel: ";
      String outOfBounds = " out of bounds";

      //System.out.println( me + "x=" + x  + ", y=" + y );
      if ( x < 0 || x >= pixRectSideSize )
         System.err.println( me + "x=" + x + outOfBounds );
      else if ( y < 0 || y >= pixRectSideSize )
         System.err.println( me + "y=" + y + outOfBounds );
      else
         baseTurtlePixels[ x + (y * pixRectSideSize) ] = value;

   } // end fillPixel()


   /**
    * Return a pointer to the turtle's pixels
    */
   public int[] getPixels()
   { return turtlePixRect; }


   /**
    * Return the size of a side of the turtle's pixRect
    */
   public int getSideSize()
   { return pixRectSideSize; }
   

   /**
    *  Return the Color of the turtle's inner pixels
    */
   public Color getTurtleColor()
   { return turtleColor; }


   /**
    * Return the turtle's current heading (radians)
    */
   public float getTurtleHeading()
   { return turtleHeading; }


   /**
    * Return the height of the turtle's image
    */
   public int getTurtleHeight()
   { return turtleHeight; }


   /**
    * Return the height of the turtle's image
    */
   public int getTurtleWidth()
   { return turtleWidth; }


   /**
    * Initializes the pixels that make up the turtle's image.
    *
    */
   public abstract void initTurtlePixels( int[] turtlePixRect,
                                          int pixRectSideSize );
   

   /**
    * Fill inner pixels of the turtle with the specified color.
    *
    */
   public boolean setTurtleColor( Color newColor )
   {
      if ( newColor != turtleColor )
      {
         int rgbVals = newColor.getRGB();
         for (int pixIdx=0; pixIdx < baseTurtlePixels.length; pixIdx++)
            if ( turtleFillMask[pixIdx] == true )
            {
               int pixel = baseTurtlePixels[pixIdx] & PIXEL_OPACITY_BITS;
               pixel += rgbVals;
               baseTurtlePixels[pixIdx] = pixel;
            }
         updateTurtlePixels( turtleHeading );
	 turtleColor = newColor;
         return true;
      }
      return false;

   } // end setTurtleColor()


   /**
    * Rotate the turtle to a specified heading (radians).
    */
   public boolean setTurtleHeading( float newHeading )
   {
      if ( this instanceof BallTurtle )
         return false;
      if ( Math.abs(turtleHeading - newHeading) > 0.001 )
      {
         turtleHeading = newHeading;
         updateTurtlePixels( newHeading );
         return true;
      }
      return false;

   } // end setTurtleHeading()


} // end class TurtlePixels