/*
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.geom.transform;

import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.Shape;
import com.sun.javafx.geom.Vec3d;

/**
 *
 */
public abstract class BaseTransform implements CanTransformVec3d{
    public static final BaseTransform IDENTITY_TRANSFORM = new Identity();

    public static enum Degree {
        IDENTITY,
        TRANSLATE_2D,
        AFFINE_2D,
        TRANSLATE_3D,
        AFFINE_3D,
    }

    /*
     * This constant is only useful for a cached type field.
     * It indicates that the type has been decached and must be recalculated.
     */
    protected static final int TYPE_UNKNOWN = -1;

    /**
     * This constant indicates that the transform defined by this object
     * is an identity transform.
     * An identity transform is one in which the output coordinates are
     * always the same as the input coordinates.
     * If this transform is anything other than the identity transform,
     * the type will either be the constant GENERAL_TRANSFORM or a
     * combination of the appropriate flag bits for the various coordinate
     * conversions that this transform performs.
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_IDENTITY = 0;

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a translation in addition to the conversions indicated
     * by other flag bits.
     * A translation moves the coordinates by a constant amount in x
     * and y without changing the length or angle of vectors.
     * @see #TYPE_IDENTITY
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_TRANSLATION = 1;

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a uniform scale in addition to the conversions indicated
     * by other flag bits.
     * A uniform scale multiplies the length of vectors by the same amount
     * in both the x and y directions without changing the angle between
     * vectors.
     * This flag bit is mutually exclusive with the TYPE_GENERAL_SCALE flag.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_UNIFORM_SCALE = 2;

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a general scale in addition to the conversions indicated
     * by other flag bits.
     * A general scale multiplies the length of vectors by different
     * amounts in the x and y directions without changing the angle
     * between perpendicular vectors.
     * This flag bit is mutually exclusive with the TYPE_UNIFORM_SCALE flag.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_GENERAL_SCALE = 4;

    /**
     * This constant is a bit mask for any of the scale flag bits.
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     */
    public static final int TYPE_MASK_SCALE = (TYPE_UNIFORM_SCALE |
                           TYPE_GENERAL_SCALE);

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a mirror image flip about some axis which changes the
     * normally right handed coordinate system into a left handed
     * system in addition to the conversions indicated by other flag bits.
     * A right handed coordinate system is one where the positive X
     * axis rotates counterclockwise to overlay the positive Y axis
     * similar to the direction that the fingers on your right hand
     * curl when you stare end on at your thumb.
     * A left handed coordinate system is one where the positive X
     * axis rotates clockwise to overlay the positive Y axis similar
     * to the direction that the fingers on your left hand curl.
     * There is no mathematical way to determine the angle of the
     * original flipping or mirroring transformation since all angles
     * of flip are identical given an appropriate adjusting rotation.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_FLIP = 64;
    /* NOTE: TYPE_FLIP was added after GENERAL_TRANSFORM was in public
     * circulation and the flag bits could no longer be conveniently
     * renumbered without introducing binary incompatibility in outside
     * code.
     */

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a quadrant rotation by some multiple of 90 degrees in
     * addition to the conversions indicated by other flag bits.
     * A rotation changes the angles of vectors by the same amount
     * regardless of the original direction of the vector and without
     * changing the length of the vector.
     * This flag bit is mutually exclusive with the TYPE_GENERAL_ROTATION flag.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_QUADRANT_ROTATION = 8;

    /**
     * This flag bit indicates that the transform defined by this object
     * performs a rotation by an arbitrary angle in addition to the
     * conversions indicated by other flag bits.
     * A rotation changes the angles of vectors by the same amount
     * regardless of the original direction of the vector and without
     * changing the length of the vector.
     * This flag bit is mutually exclusive with the
     * TYPE_QUADRANT_ROTATION flag.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     * @see #getType
     */
    public static final int TYPE_GENERAL_ROTATION = 16;

    /**
     * This constant is a bit mask for any of the rotation flag bits.
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     */
    public static final int TYPE_MASK_ROTATION = (TYPE_QUADRANT_ROTATION |
                          TYPE_GENERAL_ROTATION);

    /**
     * This constant indicates that the transform defined by this object
     * performs an arbitrary conversion of the input coordinates.
     * If this transform can be classified by any of the above constants,
     * the type will either be the constant TYPE_IDENTITY or a
     * combination of the appropriate flag bits for the various coordinate
     * conversions that this transform performs.
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_FLIP
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #getType
     */
    public static final int TYPE_GENERAL_TRANSFORM = 32;

    public static final int TYPE_AFFINE2D_MASK =
            (TYPE_TRANSLATION |
             TYPE_UNIFORM_SCALE |
             TYPE_GENERAL_SCALE |
             TYPE_QUADRANT_ROTATION |
             TYPE_GENERAL_ROTATION |
             TYPE_GENERAL_TRANSFORM |
             TYPE_FLIP);

    public static final int TYPE_AFFINE_3D = 128;

    /*
     * Convenience method used internally to throw exceptions when
     * an operation of degree higher than AFFINE_2D is attempted.
     */
    static void degreeError(Degree maxSupported) {
        throw new InternalError("does not support higher than "+
                                maxSupported+" operations");
    }

    public static BaseTransform getInstance(BaseTransform tx) {
        if (tx.isIdentity()) {
            return IDENTITY_TRANSFORM;
        } else if (tx.isTranslateOrIdentity()) {
            return new Translate2D(tx);
        } else if (tx.is2D()) {
            return new Affine2D(tx);
        }
        return new Affine3D(tx);
    }

    public static BaseTransform getInstance(double mxx, double mxy, double mxz, double mxt,
                                            double myx, double myy, double myz, double myt,
                                            double mzx, double mzy, double mzz, double mzt)
    {
        if (mxz == 0.0 && myz == 0.0 &&
            mzx == 0.0 && mzy == 0.0 && mzz == 1.0 && mzt == 0.0)
        {
            return getInstance(mxx, myx, mxy, myy, mxt, myt);
        } else {
            return new Affine3D(mxx, mxy, mxz, mxt,
                                myx, myy, myz, myt,
                                mzx, mzy, mzz, mzt);
        }
    }

    public static BaseTransform getInstance(double mxx, double myx,
                                            double mxy, double myy,
                                            double mxt, double myt)
    {
        if (mxx == 1.0 && myx == 0.0 && mxy == 0.0 && myy == 1.0) {
            return getTranslateInstance(mxt, myt);
        } else {
            return new Affine2D(mxx, myx, mxy, myy, mxt, myt);
        }
    }

    public static BaseTransform getTranslateInstance(double mxt, double myt) {
        if (mxt == 0.0 && myt == 0.0) {
            return IDENTITY_TRANSFORM;
        } else {
            return new Translate2D(mxt, myt);
        }
    }

    public static BaseTransform getScaleInstance(double mxx, double myy) {
        return getInstance(mxx, 0, 0, myy, 0, 0);
    }

    public static BaseTransform getRotateInstance(double theta, double x, double y) {
        Affine2D a = new Affine2D();
        a.setToRotation(theta, x, y);
        return a;
    }

    public abstract Degree getDegree();

    /**
     * Retrieves the flag bits describing the conversion properties of
     * this transform.
     * The return value is either one of the constants TYPE_IDENTITY
     * or TYPE_GENERAL_TRANSFORM, or a combination of the
     * appriopriate flag bits.
     * A valid combination of flag bits is an exclusive OR operation
     * that can combine
     * the TYPE_TRANSLATION flag bit
     * in addition to either of the
     * TYPE_UNIFORM_SCALE or TYPE_GENERAL_SCALE flag bits
     * as well as either of the
     * TYPE_QUADRANT_ROTATION or TYPE_GENERAL_ROTATION flag bits.
     * @return the OR combination of any of the indicated flags that
     * apply to this transform
     * @see #TYPE_IDENTITY
     * @see #TYPE_TRANSLATION
     * @see #TYPE_UNIFORM_SCALE
     * @see #TYPE_GENERAL_SCALE
     * @see #TYPE_QUADRANT_ROTATION
     * @see #TYPE_GENERAL_ROTATION
     * @see #TYPE_GENERAL_TRANSFORM
     */
    public abstract int getType();

    public abstract boolean isIdentity();
    public abstract boolean isTranslateOrIdentity();
    public abstract boolean is2D();

    public abstract double getDeterminant();

    public double getMxx() { return 1.0; }
    public double getMxy() { return 0.0; }
    public double getMxz() { return 0.0; }
    public double getMxt() { return 0.0; }
    public double getMyx() { return 0.0; }
    public double getMyy() { return 1.0; }
    public double getMyz() { return 0.0; }
    public double getMyt() { return 0.0; }
    public double getMzx() { return 0.0; }
    public double getMzy() { return 0.0; }
    public double getMzz() { return 1.0; }
    public double getMzt() { return 0.0; }

    public abstract Point2D transform(Point2D src, Point2D dst);
    public abstract Point2D inverseTransform(Point2D src, Point2D dst)
        throws NoninvertibleTransformException;
    public abstract Vec3d transform(Vec3d src, Vec3d dst);
    public abstract Vec3d deltaTransform(Vec3d src, Vec3d dst);
    public abstract Vec3d inverseTransform(Vec3d src, Vec3d dst)
        throws NoninvertibleTransformException;
    public abstract Vec3d inverseDeltaTransform(Vec3d src, Vec3d dst)
        throws NoninvertibleTransformException;

    public abstract void transform(float[] srcPts, int srcOff,
                                   float[] dstPts, int dstOff,
                                   int numPts);
    public abstract void transform(double[] srcPts, int srcOff,
                                   double[] dstPts, int dstOff,
                                   int numPts);
    public abstract void transform(float[] srcPts, int srcOff,
                                   double[] dstPts, int dstOff,
                                   int numPts);
    public abstract void transform(double[] srcPts, int srcOff,
                                   float[] dstPts, int dstOff,
                                   int numPts);
    public abstract void deltaTransform(float[] srcPts, int srcOff,
                                        float[] dstPts, int dstOff,
                                        int numPts);
    public abstract void deltaTransform(double[] srcPts, int srcOff,
                                        double[] dstPts, int dstOff,
                                        int numPts);
    public abstract void inverseTransform(float[] srcPts, int srcOff,
                                          float[] dstPts, int dstOff,
                                          int numPts)
        throws NoninvertibleTransformException;
    public abstract void inverseDeltaTransform(float[] srcPts, int srcOff,
                                               float[] dstPts, int dstOff,
                                               int numPts)
        throws NoninvertibleTransformException;
    public abstract void inverseTransform(double[] srcPts, int srcOff,
                                          double[] dstPts, int dstOff,
                                          int numPts)
        throws NoninvertibleTransformException;

    public abstract BaseBounds transform(BaseBounds bounds, BaseBounds result);
    public abstract void transform(Rectangle rect, Rectangle result);
    public abstract BaseBounds inverseTransform(BaseBounds bounds, BaseBounds result)
        throws NoninvertibleTransformException;
    public abstract void inverseTransform(Rectangle rect, Rectangle result)
        throws NoninvertibleTransformException;

    public abstract Shape createTransformedShape(Shape s);

    public abstract void setToIdentity();
    public abstract void setTransform(BaseTransform xform);

    /**
     * This function inverts the {@code BaseTransform} in place.  All
     * current implementations can support their own inverted form, and
     * that should likely remain true in the future as well.
     */
    public abstract void invert() throws NoninvertibleTransformException;

    /**
     * This function is only guaranteed to succeed if the transform is
     * of degree AFFINE2D or less and the matrix
     * parameters specified came from this same instance.  In practice,
     * they will also tend to succeed if they specify a transform of
     * Degree less than or equal to the Degree of this instance as well,
     * but the intent of this method is to restore the transform that
     * had been read out of this transform into local variables.
     */
    public abstract void restoreTransform(double mxx, double myx,
                                          double mxy, double myy,
                                          double mxt, double myt);

    /**
     * This function is only guaranteed to succeed if the matrix
     * parameters specified came from this same instance.  In practice,
     * they will also tend to succeed if they specify a transform of
     * Degree less than or equal to the Degree of this instance as well,
     * but the intent of this method is to restore the transform that
     * had been read out of this transform into local variables.
     */
    public abstract void restoreTransform(double mxx, double mxy, double mxz, double mxt,
                                          double myx, double myy, double myz, double myt,
                                          double mzx, double mzy, double mzz, double mzt);

    public abstract BaseTransform deriveWithTranslation(double mxt, double myt);
    public abstract BaseTransform deriveWithTranslation(double mxt, double myt, double mzt);
    public abstract BaseTransform deriveWithScale(double mxx, double myy, double mzz);
    public abstract BaseTransform deriveWithRotation(double theta, double axisX, double axisY, double axisZ);
    public abstract BaseTransform deriveWithPreTranslation(double mxt, double myt);
    public abstract BaseTransform deriveWithConcatenation(double mxx, double myx,
                                                          double mxy, double myy,
                                                          double mxt, double myt);
    public abstract BaseTransform deriveWithConcatenation(
            double mxx, double mxy, double mxz, double mxt,
            double myx, double myy, double myz, double myt,
            double mzx, double mzy, double mzz, double mzt);
    public abstract BaseTransform deriveWithPreConcatenation(BaseTransform transform);
    public abstract BaseTransform deriveWithConcatenation(BaseTransform tx);
    public abstract BaseTransform deriveWithNewTransform(BaseTransform tx);

    /**
     * This function always returns a new object, unless the transform
     * is an identity transform in which case it might return the
     * {@code Identity} singleton.
     * @return a new transform representing the inverse of this transform.
     */
    public abstract BaseTransform createInverse()
        throws NoninvertibleTransformException;

    public abstract BaseTransform copy();

    /**
     * Returns the hashcode for this transform.
     * @return      a hash code for this transform.
     */
    @Override
    public int hashCode() {
        if (isIdentity()) return 0;
        long bits = 0;
        bits = bits * 31 + Double.doubleToLongBits(getMzz());
        bits = bits * 31 + Double.doubleToLongBits(getMzy());
        bits = bits * 31 + Double.doubleToLongBits(getMzx());
        bits = bits * 31 + Double.doubleToLongBits(getMyz());
        bits = bits * 31 + Double.doubleToLongBits(getMxz());
        bits = bits * 31 + Double.doubleToLongBits(getMyy());
        bits = bits * 31 + Double.doubleToLongBits(getMyx());
        bits = bits * 31 + Double.doubleToLongBits(getMxy());
        bits = bits * 31 + Double.doubleToLongBits(getMxx());
        bits = bits * 31 + Double.doubleToLongBits(getMzt());
        bits = bits * 31 + Double.doubleToLongBits(getMyt());
        bits = bits * 31 + Double.doubleToLongBits(getMxt());
        return (((int) bits) ^ ((int) (bits >> 32)));
    }

    /**
     * Returns <code>true</code> if this <code>BaseTransform</code>
     * represents the same coordinate transform as the specified
     * argument.
     * @param obj the <code>Object</code> to test for equality with this
     * <code>BaseTransform</code>
     * @return <code>true</code> if <code>obj</code> equals this
     * <code>BaseTransform</code> object; <code>false</code> otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof BaseTransform)) {
            return false;
        }

        BaseTransform a = (BaseTransform) obj;

        return (getMxx() == a.getMxx() &&
                getMxy() == a.getMxy() &&
                getMxz() == a.getMxz() &&
                getMxt() == a.getMxt() &&
                getMyx() == a.getMyx() &&
                getMyy() == a.getMyy() &&
                getMyz() == a.getMyz() &&
                getMyt() == a.getMyt() &&
                getMzx() == a.getMzx() &&
                getMzy() == a.getMzy() &&
                getMzz() == a.getMzz() &&
                getMzt() == a.getMzt());
    }

    static Point2D makePoint(Point2D src, Point2D dst) {
        if (dst == null) {
            dst = new Point2D();
        }
        return dst;
    }

    static final double EPSILON_ABSOLUTE = 1.0e-5;

    public static boolean almostZero(double a) {
        return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
    }

    /**
     * Returns the matrix elements and degree of this transform as a string.
     * @return  the matrix elements and degree of this transform
     */
    @Override
    public String toString() {
        return "Matrix: degree " + getDegree() + "\n" +
                getMxx() + ", " + getMxy() + ", " + getMxz() + ", " + getMxt() + "\n" +
                getMyx() + ", " + getMyy() + ", " + getMyz() + ", " + getMyt() + "\n" +
                getMzx() + ", " + getMzy() + ", " + getMzz() + ", " + getMzt() + "\n";
    }
}
