

public class Isometry2
{
    public Complex T, P;
    public int R;

    public Isometry2(Complex T, Complex P, int R)
    {
        this.T = T;
        this.P = P;
        this.R = R;
    }

    public Isometry2()
    {
        this.T = new Complex();
        this.P = new Complex();
    }



    public Isometry2(Isometry2 from)
    {
        this.T = new Complex(from.T);
        this.P = new Complex(from.P);
        this.R = from.R;
    }

    public Isometry2 set(Isometry2 from)
    {
        T.set(from.T);
        P.set(from.P);
        R = from.R;
        return this;
    }






    public Complex apply(Complex _z, Complex result)
    {






        Complex temp[] = new Complex[8];
        {
            int i, n = temp.length;
            for (i = 0; (i) < (n); ++i)
                temp[i] = new Complex();
        }
        Complex z = temp[0];

        z.set(_z);
        if (R < 0)
            z.y = -z.y;

        return Complex.div(
                   Complex.add(
                       Complex.mul(T,z, temp[1]),
                       P,
                       temp[2]),
                   Complex.add(
                       Complex.mul(
                           Complex.mul(
                               Complex.conj(P, temp[3]),
                               T,
                               temp[4]),
                           z,
                           temp[5]),
                       temp[6].set(1., 0.),
                       temp[7]),
                   result);
    }

    public Complex apply(Complex z)
    {
        return apply(z, new Complex());
    }

    public double[ ] apply(double x, double y, double result[ ])
    {
        Complex in = new Complex(x,y);
        Complex out = new Complex();
        apply(in,out);
        result[0] = out.x;
        result[1] = out.y;
        return result;
    }

    public double[ ] apply(double z[ ], double result[ ])
    {
        return apply(z[0],z[1],result);
    }

    public double[ ] apply(double z[ ])
    {
        return apply(z[0], z[1], new double[2]);
    }




    public static Isometry2 mul(Isometry2 F1, Isometry2 _F0, Isometry2 result)
    {

        Complex temp[] = new Complex[13];
        {
            int i, n = temp.length;
            for (i = 0; (i) < (n); ++i)
                temp[i] = new Complex();
        }

        Isometry2 F0 = new Isometry2(_F0);

        if (F1.R < 0)
        {
            F0.T.y = -F0.T.y;
            F0.P.y = -F0.P.y;
        }

        Complex denom = Complex.add(
                            Complex.mul(
                                Complex.mul(F1.T,
                                            F0.P,
                                            temp[2]),
                                Complex.conj(F1.P,
                                             temp[3]),
                                temp[4]),
                            Complex.one,
                            temp[5]);
        result.R = F1.R * F0.R;
        Complex.div(
            Complex.add(
                Complex.mul(
                    F1.T,
                    F0.P,
                    temp[6]),
                 F1.P,
                 temp[7]),
            denom,
            result.P);
        Complex.div(
                Complex.add(
                    Complex.mul(F0.T,
                                F1.T,
                                temp[8]),
                    Complex.mul(
                        Complex.mul(F0.T,
                                    Complex.conj(F0.P, temp[9]),
                                    temp[10]),
                        F1.P,
                        temp[11]),
                    temp[12]),
            denom,
            result.T);




        double invLenT = 1. / ((1. + result.T.x*result.T.x
                                   + result.T.y*result.T.y) * .5);
        result.T.x *= invLenT;
        result.T.y *= invLenT;

        return result;
    }



    public static Isometry2 mul(Isometry2 F1, Isometry2 F0)
    {
        return mul(F1,F0, new Isometry2());
    }

    public static Isometry2 pow(Isometry2 F, int e)
    {
        if (e > 0)
        {
            Isometry2 temp = pow(F, e/2);
            if ((e % 2) == 0)
                return Isometry2.mul(temp, temp);
            else
                return Isometry2.mul(Isometry2.mul(F,temp),temp);
        }
        else if (e < 0)
        {
            return pow(F.inverse(), -e);
        }
        else
        {
            return Isometry2.identity;
        }
    }


    public Isometry2 inverse()
    {





        Isometry2 F0 = new Isometry2(Complex.one,Complex.neg(P),1);
        Isometry2 F1 = new Isometry2(Complex.conj(T),Complex.zero,1);
        Isometry2 F2 = new Isometry2(Complex.one,Complex.zero,R);
        return Isometry2.mul(Isometry2.mul(F2,F1),F0);
    }

    public static Isometry2 pureRotation(double angle)
    {
        return new Isometry2(new Complex(Math.cos(angle),Math.sin(angle)),
                             Complex.zero,
                             1);

    }

    public static Isometry2
    pureTranslation(double x1, double y1)
    {
        return new Isometry2(Complex.one,
                             new Complex(x1,y1),
                             1);
    }

    public static Isometry2
    pureTranslation(Complex p0, Complex p1)
    {

        Complex A = Complex.sub(p1,p0);
        Complex B = Complex.mul(p1,p0);
        double denom = 1. - (B.x*B.x + B.y*B.y);
        Isometry2 result = new Isometry2(
                                   Complex.one,
                                   new Complex((A.x*(1+B.x)+A.y*B.y)/denom,
                                               (A.y*(1-B.x)+A.x*B.y)/denom),
                                   1);
        return result;
    }


    public static Isometry2
    reflectionAcrossLine(Complex l0, Complex l1)
    {
        Isometry2 takel0ToOrigin = Isometry2.pureTranslation(-l0.x,-l0.y);
        Isometry2 takeOriginTol0 = Isometry2.pureTranslation(l0.x,l0.y);

        Complex l1_ = takel0ToOrigin.apply(l1);
        double angle = Math.atan2(l1_.y, l1_.x);
        Isometry2 rotatel1_ToXAxis = Isometry2.pureRotation(-angle);
        Isometry2 rotateXAxisTol1_ = Isometry2.pureRotation(angle);
        Isometry2 reflectAboutXAxis = new Isometry2(Complex.one,
                                                    Complex.zero,
                                                    -1);

        Isometry2 result = takel0ToOrigin;
        result = Isometry2.mul(rotatel1_ToXAxis, result);
        result = Isometry2.mul(reflectAboutXAxis, result);
        result = Isometry2.mul(rotateXAxisTol1_, result);
        result = Isometry2.mul(takeOriginTol0, result);
        return result;
    }
    public static Isometry2
    reflectionAcrossLine(double l0[ ], double l1[ ])
    {
        return reflectionAcrossLine(new Complex(l0), new Complex(l1));
    }

    boolean equals(Isometry2 other, double eps)
    {

        return R == other.R
            && T.x-other.T.x <= eps
            && other.T.x-T.x <= eps
            && T.y-other.T.y <= eps
            && other.T.y-T.y <= eps
            && P.x-other.P.x <= eps
            && other.P.x-P.x <= eps
            && P.y-other.P.y <= eps
            && other.P.y-P.y <= eps;
    }


    public boolean equals(Object other)
    {
        if (XXXdebug)
            System.out.println("!!!!!!!!!!!!!!!!!!!!!Isometry2.equals returning "+ equals((Isometry2)other, 1e-6));
        return equals((Isometry2)other, 1e-6);
    }

    public String toString()
    {
        return "[T=("+T.x+","+T.y+") P=("+P.x+","+P.y+") R="+R+"]";
    }
    static final Isometry2 identity = new Isometry2(Complex.one,Complex.zero,1);

    public int hashCode()
    {
        int hash = 0;
        int i = 0;
        hash = _accumulateHash(hash, _hashCode1(T.x), i++);
        hash = _accumulateHash(hash, _hashCode1(T.y), i++);
        hash = _accumulateHash(hash, _hashCode1(P.x), i++);
        hash = _accumulateHash(hash, _hashCode1(P.y), i++);
        hash = _accumulateHash(hash, R, i++);
        if (XXXdebug)
        {
            System.out.print("------------------> ");
            {System.out.print("    ");System.out.println("hash" + " = " + (hash));};
        }
        return hash;
    }
    private int _hashCode1(double x)
    {
        if (XXXdebug) System.out.println();
        if (XXXdebug) {System.out.print("    ");System.out.println("x" + " = " + (x));};
        x += uncommonConstantForHash;

        if (XXXdebug) {System.out.print("    ");System.out.println("x" + " = " + (x));};
        x -= (int)x;

        if (XXXdebug) {System.out.print("    ");System.out.println("x" + " = " + (x));};
        x *= (1<<16);
        if (XXXdebug) {System.out.print("    ");System.out.println("x" + " = " + (x));};
        if (XXXdebug) {System.out.print("    ");System.out.println("(int)x" + " = " + ((int)x));};

        return (int)x;
    }
    private int _accumulateHash(int hash, int incr, int i)
    {
        hash = hash * (((i&1)!=0)?11:13) + incr;
        return hash;
    }

    private static final double uncommonConstantForHash = Math.PI * Math.E / 4;

    public static boolean XXXdebug = false;
}
