/* Copyright (C) 2000  Free Software Foundation

   This file is part of libjava.

This software is copyrighted work licensed under the terms of the
Libjava License.  Please consult the file "LIBJAVA_LICENSE" for
details.  */

package java.awt.geom;
import java.awt.*;
import java.io.Serializable;

/**
 * @author Tom Tromey <tromey@cygnus.com>
 * @date April 16, 2000
 */

/* Status: mostly complete.  Search for fixme to see problems.
   Also, TYPE_ returns are not handled correctly.  */

public class AffineTransform implements Cloneable, Serializable
{
  static final int TYPE_IDENTITY = 0;
  static final int TYPE_FLIP = 64;
  static final int TYPE_GENERAL_ROTATION = 16;
  static final int TYPE_GENERAL_SCALE = 4;
  static final int TYPE_GENERAL_TRANSFORM = 32;
  static final int TYPE_MASK_ROTATION = 24;
  static final int TYPE_MASK_SCALE = 6;
  static final int TYPE_QUADRANT_ROTATION = 8;
  static final int TYPE_TRANSLATION = 1;
  static final int TYPE_UNIFORM_SCALE = 2;

  public AffineTransform ()
  {
    setToIdentity ();
  }

  public AffineTransform (AffineTransform tx)
  {
    setTransform (tx);
  }

  public AffineTransform (float m00, float m10,
			  float m01, float m11,
			  float m02, float m12)
  {
    this.m00 = m00;
    this.m10 = m10;
    this.m01 = m01;
    this.m11 = m11;
    this.m02 = m02;
    this.m12 = m12;
    this.type = 0; // fixme;
  }

  public AffineTransform (float[] flatmatrix)
  {
    m00 = flatmatrix[0];
    m10 = flatmatrix[1];
    m01 = flatmatrix[2];
    m11 = flatmatrix[3];
    if (flatmatrix.length >= 6)
      {
	m02 = flatmatrix[4];
	m12 = flatmatrix[5];
      }
  }

  public AffineTransform (double m00, double m10, double m01,
			  double m11, double m02, double m12)
  {
    this.m00 = m00;
    this.m10 = m10;
    this.m01 = m01;
    this.m11 = m11;
    this.m02 = m02;
    this.m12 = m12;
    this.type = TYPE_GENERAL_TRANSFORM;
  }

  public AffineTransform (double[] flatmatrix)
  {
    m00 = flatmatrix[0];
    m10 = flatmatrix[1];
    m01 = flatmatrix[2];
    m11 = flatmatrix[3];
    if (flatmatrix.length >= 6)
      {
	m02 = flatmatrix[4];
	m12 = flatmatrix[5];
      }
  }

  public static AffineTransform getTranslateInstance (double tx, double ty)
  {
    AffineTransform t = new AffineTransform ();
    t.setToTranslation (tx, ty);
    return t;
  }

  public static AffineTransform getRotateInstance (double theta)
  {
    AffineTransform t = new AffineTransform ();
    t.setToRotation (theta);
    return t;
  }

  public static AffineTransform getRotateInstance (double theta,
						   double x, double y)
  {
    AffineTransform t = new AffineTransform ();
    t.rotate (theta, x, y);
    return t;
  }

  public static AffineTransform getScaleInstance (double sx, double sy)
  {
    AffineTransform t = new AffineTransform ();
    t.setToScale (sx, sy);
    return t;
  }

  public static AffineTransform getShearInstance (double shx, double shy)
  {
    AffineTransform t = new AffineTransform ();
    t.setToShear (shx, shy);
    return t;
  }

  public int getType ()
  {
    return type;
  }

  public double getDeterminant ()
  {
    return m00 * m11 - m01 * m10;
  }

  public void getMatrix (double[] flatmatrix)
  {
    flatmatrix[0] = m00;
    flatmatrix[1] = m10;
    flatmatrix[2] = m01;
    flatmatrix[3] = m11;
    if (flatmatrix.length >= 6)
      {
	flatmatrix[4] = m02;
	flatmatrix[5] = m12;
      }
  }

  public double getScaleX ()
  {
    return m00;
  }

  public double getScaleY ()
  {
    return m11;
  }

  public double getShearX ()
  {
    return m01;
  }

  public double getShearY ()
  {
    return m10;
  }

  public double getTranslateX ()
  {
    return m02;
  }

  public double getTranslateY ()
  {
    return m12;
  }

  public void translate (double tx, double ty)
  {
    m02 += tx * m00 + ty * m01;
    m12 += tx * m10 + ty * m11;
  }

  public void rotate (double theta)
  {
    double c = Math.cos (theta);
    double s = Math.sin (theta);
    double n00 = m00 *  c + m01 * s;
    double n01 = m00 * -s + m01 * c;
    double n10 = m10 *  c + m11 * s;
    double n11 = m10 * -s + m11 * c;

    m00 = n00;
    m01 = n01;
    m10 = n10;
    m11 = n11;
  }

  public void rotate (double theta, double x, double y)
  {
    translate (x, y);
    rotate (theta);
    translate (-x, -y);
  }

  public void scale (double sx, double sy)
  {
    m00 *= sx;
    m01 *= sy;
    m10 *= sx;
    m11 *= sy;
  }

  public void shear (double shx, double shy)
  {
    double n00 = m00 + shx * m01;
    double n01 = shx * m00 + m01;
    double n10 = m10 * shy + m11;
    double n11 = shx * m10 + m11;

    m00 = n00;
    m01 = n01;
    m10 = n10;
    m11 = n11;
  }

  public void setToIdentity ()
  {
    m00 = m11 = 1;
    m01 = m02 = m10 = m12 = 0;
    type = TYPE_IDENTITY;
  }

  public void setToTranslation (double tx, double ty)
  {
    m00 = m11 = 1;
    m01 = m10 = 0;
    m02 = tx;
    m12 = ty;
    type = TYPE_TRANSLATION;
  }

  public void setToRotation (double theta)
  {
    double c = Math.cos (theta);
    double s = Math.sin (theta);

    m00 = c;
    m01 = -s;
    m02 = 0;
    m10 = s;
    m11 = c;
    m12 = 0;
    type = TYPE_GENERAL_ROTATION;
  }

  public void setToScale (double sx, double sy)
  {
    m00 = sx;
    m01 = m02 = m10 = m12 = 0;
    m11 = sy;
    type = (sx == sy) ? TYPE_UNIFORM_SCALE : TYPE_GENERAL_SCALE;
  }

  public void setToShear (double shx, double shy)
  {
    m00 = m11 = 1;
    m01 = shx;
    m10 = shy;
    m02 = m12 = 0;
    type = 0;			// FIXME
  }

  public void setTransform (AffineTransform tx)
  {
    m00 = tx.m00;
    m01 = tx.m01;
    m02 = tx.m02;
    m10 = tx.m10;
    m11 = tx.m11;
    m12 = tx.m12;
    type = tx.type;
  }

  public void setTransform (double m00, double m10, double m01,
			    double m11, double m02, double m12)
  {
    this.m00 = m00;
    this.m10 = m10;
    this.m01 = m01;
    this.m11 = m11;
    this.m02 = m02;
    this.m12 = m12;
    this.type = 0;		// FIXME
  }

  public void concatentate (AffineTransform tx)
  {
    double n00 = m00 * tx.m00 + m01 * tx.m10;
    double n01 = m00 * tx.m01 + m01 * tx.m11;
    double n02 = m00 * tx.m02 + m01 * tx.m12 + m02;
    double n10 = m10 * tx.m00 + m11 * tx.m10;
    double n11 = m10 * tx.m01 + m11 * tx.m11;
    double n12 = m10 * tx.m02 + m11 * tx.m12 + m12;

    m00 = n00;
    m01 = n01;
    m02 = n02;
    m10 = n10;
    m11 = n11;
    m12 = n12;
  }

  public void preConcatenate (AffineTransform tx)
  {
    double n00 = tx.m00 * m00 + tx.m01 * m10;
    double n01 = tx.m00 * m01 + tx.m01 * m11;
    double n02 = tx.m00 * m02 + tx.m01 * m12 + tx.m02;
    double n10 = tx.m10 * m00 + tx.m11 * m10;
    double n11 = tx.m10 * m01 + tx.m11 * m11;
    double n12 = tx.m10 * m02 + tx.m11 * m12 + tx.m12;

    m00 = n00;
    m01 = n01;
    m02 = n02;
    m10 = n10;
    m11 = n11;
    m12 = n12;
  }

  public AffineTransform createInverse ()
    throws NoninvertibleTransformException
  {
    double det = getDeterminant ();
    if (det == 0)
      throw new NoninvertibleTransformException ("can't invert transform");

    double i00 = m11 / det;
    double i01 = -m10 / det;
    double i02 = 0;
    double i10 = m01 / det;
    double i11 = -m00 / det;
    double i12 = 0;

    return new AffineTransform (i00, i01, i02,
				i10, i11, i12);
  }

  public Point2D transform (Point2D src, Point2D dst)
  {
    if (dst == null)
      dst = new Point2D.Double ();

    // We compute and set separately to correctly overwrite if
    // src==dst.
    double x = src.getX ();
    double y = src.getY ();
    double nx = m00 * x + m01 * y + m02;
    double ny = m10 * x + m11 * y + m12;

    dst.setLocation (nx, ny);

    return dst;
  }

  public void transform (Point2D[] src, int srcOff,
			 Point2D[] dst, int dstOff,
			 int num)
  {
    while (num-- > 0)
      {
	dst[dstOff] = transform (src[srcOff], dst[dstOff]);
	++srcOff;
	++dstOff;
      }
  }

  public void transform (float[] srcPts, int srcOff,
			 float[] dstPts, int dstOff,
			 int num)
  {
    while (num-- > 0)
      {
	float x = srcPts[srcOff];
	float y = srcPts[srcOff + 1];
	srcOff += 2;
	float nx = (float) (m00 * x + m01 * y + m02);
	float ny = (float) (m10 * x + m10 * y + m12);
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
      }
  }

  public void transform (double[] srcPts, int srcOff,
			 double[] dstPts, int dstOff,
			 int num)
  {
    while (num-- > 0)
      {
	double x = srcPts[srcOff];
	double y = srcPts[srcOff + 1];
	srcOff += 2;
	double nx = m00 * x + m01 * y + m02;
	double ny = m10 * x + m10 * y + m12;
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
      }
  }

  public void transform (float[] srcPts, int srcOff,
			 double[] dstPts, int dstOff,
			 int num)
  {
    while (num-- > 0)
      {
	float x = srcPts[srcOff];
	float y = srcPts[srcOff + 1];
	srcOff += 2;
	double nx = m00 * x + m01 * y + m02;
	double ny = m10 * x + m10 * y + m12;
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
      }
  }

  public void transform (double[] srcPts, int srcOff,
			 float[] dstPts, int dstOff,
			 int num)
  {
    while (num-- > 0)
      {
	double x = srcPts[srcOff];
	double y = srcPts[srcOff + 1];
	srcOff += 2;
	float nx = (float) (m00 * x + m01 * y + m02);
	float ny = (float) (m10 * x + m10 * y + m12);
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
      }
  }

  public Point2D inverseTransform (Point2D src, Point2D dst)
    throws NoninvertibleTransformException
  {
    double det = getDeterminant ();
    if (det == 0)
      throw new NoninvertibleTransformException ("couldn't invert transform");

    if (dst == null)
      dst = new Point2D.Double ();
    double x = src.getX ();
    double y = src.getY ();
    double nx = (m11 * x + - m10 * y) / det;
    double ny = (m01 * x + - m00 * y) / det;
    dst.setLocation (nx, ny);
    return dst;
  }

  public void inverseTransform (double[] srcPts, int srcOff,
				double[] dstPts, int dstOff,
				int num)
    throws NoninvertibleTransformException
  {
    double det = getDeterminant ();
    if (det == 0)
      throw new NoninvertibleTransformException ("couldn't invert transform");

    while (num-- > 0)
      {
	double x = srcPts[srcOff];
	double y = srcPts[srcOff + 1];
	double nx = (m11 * x + - m10 * y) / det;
	double ny = (m01 * x + - m00 * y) / det;
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
	srcOff += 2;
      }
  }

  public Point2D deltaTransform (Point2D src, Point2D dst)
  {
    if (dst == null)
      dst = new Point2D.Double ();
    double x = src.getX ();
    double y = src.getY ();
    double nx = m00 * x + m01 * y;
    double ny = m10 * x + m11 * y;
    dst.setLocation (nx, ny);
    return dst;
  }

  public void deltaTransform (double[] srcPts, int srcOff,
			      double[] dstPts, int dstOff,
			      int num)
  {
    while (num-- > 0)
      {
	double x = srcPts[srcOff];
	double y = srcPts[srcOff + 1];
	double nx = m00 * x + m01 * y;
	double ny = m10 * x + m11 * y;
	dstPts[dstOff] = nx;
	dstPts[dstOff + 1] = ny;
	dstOff += 2;
	srcOff += 2;
      }
  }

  public Shape createTransformedShape (Shape pSrc)
  {
    // FIXME
    return null;
  }

  public String toString ()
  {
    // FIXME
    return null;
  }

  public boolean isIdentity ()
  {
    return (m00 == 1 && m01 == 0 && m02 == 0
	    && m10 == 0 && m11 == 1 && m12 == 0);
  }

  public Object clone ()
  {
    return new AffineTransform (this);
  }

  public int hashCode ()
  {
    // FIXME
    return 23;
  }

  public boolean equals (Object obj)
  {
    if (! (obj instanceof AffineTransform))
      return false;
    AffineTransform t = (AffineTransform) obj;
    return (m00 == t.m00 && m01 == t.m01 && m02 == t.m02
	    && m10 == t.m10 && m11 == t.m11 && m12 == t.m12);
  }

  // This iterator is used to apply an AffineTransform to some other
  // iterator.  It is not private because we want to be able to access
  // it from the rest of this package.
  class Iterator implements PathIterator
  {
    // The iterator we are applied to.
    private PathIterator subIterator;

    public Iterator (PathIterator subIterator)
    {
      this.subIterator = subIterator;
    }

    public int currentSegment (double[] coords)
    {
      int r = subIterator.currentSegment (coords);
      int count = 0;

      switch (r)
	{
	case SEG_CUBICTO:
	  count = 3;
	  break;

	case SEG_QUADTO:
	  count = 2;
	  break;

	case SEG_LINETO:
	case SEG_MOVETO:
	  count = 1;
	  break;

	default:
	  // Error.  But how to report?
	case SEG_CLOSE:
	  break;
	}

      transform (coords, 0, coords, 0, count);

      return r;
    }

    public int currentSegment (float[] coords)
    {
      int r = subIterator.currentSegment (coords);
      int count = 0;

      switch (r)
	{
	case SEG_CUBICTO:
	  count = 3;
	  break;

	case SEG_QUADTO:
	  count = 2;
	  break;

	case SEG_LINETO:
	case SEG_MOVETO:
	  count = 1;
	  break;

	default:
	  // Error.  But how to report?
	case SEG_CLOSE:
	  break;
	}

      transform (coords, 0, coords, 0, count);

      return r;
    }

    public int getWindingRule ()
    {
      return subIterator.getWindingRule ();
    }

    public boolean isDone ()
    {
      return subIterator.isDone ();
    }

    public void next ()
    {
      subIterator.next ();
    }
  }

  private double m00, m01, m02;
  private double m10, m11, m12;
  private int type;
}