package geomExtension;

import util.Util;
import java.awt.*;
import java.awt.geom.*;
import java.text.*;

public class Segment2D{
    int type=0;
    private Shape shape=null;
    private AffineTransform affineTransform=null;
    public static final int MOVETO=0;
    public static final int LINE=1;
    public static final int ARC=2;
    public static final int CUBIC=3;
    public static final int QUAD=4;
    public static final String[] codeStr={"MOVETO","LINE","ARC","CUBIC","QUAD"};
    double pai=Math.PI;
    double largeNumber=1e+5;
    double eps4=1e-4;
    int debug=0;

    public Segment2D(int type, Shape shape){
        this.type=type;
        this.shape=shape;
        this.affineTransform=null;
    }

    public Segment2D(int type, Shape shape, AffineTransform affineTransform){
        this.type=type;
        this.shape=shape;
        this.affineTransform=affineTransform;
    }

    public int getType(){
        return this.type;
    }
    
    public boolean isAffineTransform(){
        return (this.affineTransform!=null);
    }

    public  AffineTransform getAffineTransform(){
        return this.affineTransform;
    }
/*
    public Shape getShape(){
        return this.shape;
    }
*/
    public Shape getShape(){
        Shape shape=this.shape;
        if(this.type==ARC&&this.affineTransform!=null){
            shape=this.affineTransform.createTransformedShape(this.shape);
        }
        return shape;
    }
/*
    public void setShape(Shape shape){
        this.shape=shape;
    }
*/
    public Shape getAwtGeom(){
        return this.shape;
    }

    public Point2D getP(double t){
        if(t<-eps4) t=0d;
        if(t>1d+eps4) t=1d;
        
        double X=0, Y=0;
        if(this.type==MOVETO) {
            System.out.println("*** ERROR Segment2D.getP, MOVETO segment, return null");
            return null;
        }
        
        if(this.type==LINE) {
            Line2D line=(Line2D)this.shape;
            X=t*(line.getX2()-line.getX1())+line.getX1();
            Y=t*(line.getY2()-line.getY1())+line.getY1();
        }
        
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double x=arc.getX(), y=arc.getY(), w=arc.getWidth(), h=arc.getHeight();
            double angle=(arc.getAngleStart()+t*arc.getAngleExtent())*this.pai/180;
            X=0.5*w*Math.cos(angle)+x+0.5*w;
            Y=-0.5*h*Math.sin(angle)+y+0.5*h;
            if(this.affineTransform!=null){
                double[] matrix=new double[6];
                this.affineTransform.getMatrix(matrix);
                x=X; y=Y;
                X=matrix[0]*x+matrix[2]*y+matrix[4];
                Y=matrix[1]*x+matrix[3]*y+matrix[5];
            }
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlP1();
            Point2D Q2=curve.getCtrlP2();
            Point2D Q3=curve.getP2();
            double X0=(1-t)*(1-t)*(1-t);
            double X1=3*(1-t)*(1-t)*t;
            double X2=3*(1-t)*t*t;
            double X3=t*t*t;
            X = X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX()+X3*Q3.getX();
            Y = X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY()+X3*Q3.getY();
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            double X0=(1-t)*(1-t);
            double X1=2*(1-t)*t;
            double X2=t*t;
            X = X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX();
            Y = X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY();
        }
        Point2D P=new Point2D.Double(X,Y);
        return P;
    }

    public Vector2D getTangent(double t){
        if(t<-eps4) t=0d;
        if(t>1d+eps4) t=1d;
        Vector2D tangent=null;
        if(this.type==MOVETO) {
            System.out.println("*** ERROR Segment2D.getTangent, MOVETO segment, return null");
            return null;
        }
        
        if(this.type==LINE) {
            Line2D line=(Line2D)this.shape;
            tangent=Vector2D.sub(line.getP2(), line.getP1());
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double x=arc.getX(), y=arc.getY(), w=arc.getWidth(), h=arc.getHeight();
            double coeff=arc.getAngleExtent()*this.pai/180;
            double angle=coeff*t+arc.getAngleStart()*this.pai/180;
            double Tx=-0.5*w*coeff*Math.sin(angle);
            double Ty=-0.5*h*coeff*Math.cos(angle);
            if(this.affineTransform!=null){
                double[] matrix=new double[6];
                this.affineTransform.getMatrix(matrix);
                x=Tx; y=Ty;
                Tx=matrix[0]*x+matrix[2]*y;
                Ty=matrix[1]*x+matrix[3]*y;
            }
            tangent=new Vector2D(Tx,Ty);
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlP1();
            Point2D Q2=curve.getCtrlP2();
            Point2D Q3=curve.getP2();
            double X0=-3*(1-t)*(1-t);
            double X1=3*(1-t)*(1-3*t);
            double X2=3*(2-3*t)*t;
            double X3=3*t*t;
            double Tx=X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX()+X3*Q3.getX();
            double Ty=X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY()+X3*Q3.getY();
            tangent=new Vector2D(Tx,Ty);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            double X0=-2*(1-t);
            double X1=2*(1-2*t);
            double X2=2*t;
            double Tx=X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX();
            double Ty=X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY();
            tangent=new Vector2D(Tx,Ty);
        }
        return tangent;
    }
    
    public Vector2D getTangentDerivative(double t){
        if(t<-eps4) t=0d;
        if(t>1d+eps4) t=1d;
        Vector2D derivative=null;
        if(this.type==MOVETO) {
            System.out.println("*** ERROR Segment2D.getTangentDerivative, MOVETO segment, return null");
            return null;
        }
        
        if(this.type==LINE) {
            derivative=new Vector2D(0,0);
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double x=arc.getX(), y=arc.getY(), w=arc.getWidth(), h=arc.getHeight();
            double coeff=arc.getAngleExtent()*this.pai/180;
            double angle=coeff*t+arc.getAngleStart()*this.pai/180;
            double DTx=-0.5*w*coeff*coeff*Math.cos(angle);
            double DTy=0.5*h*coeff*coeff*Math.sin(angle);
            if(this.affineTransform!=null){
                double[] matrix=new double[6];
                this.affineTransform.getMatrix(matrix);
                x=DTx; y=DTy;
                DTx=matrix[0]*x+matrix[2]*y;
                DTy=matrix[1]*x+matrix[3]*y;
            }
            derivative=new Vector2D(DTx,DTy);
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlP1();
            Point2D Q2=curve.getCtrlP2();
            Point2D Q3=curve.getP2();
            double X0=6*(1-t);
            double X1=-6*(2-3*t);
            double X2=6*(1-3*t);
            double X3=6*t;
            double DTx=X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX()+X3*Q3.getX();
            double DTy=X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY()+X3*Q3.getY();
            derivative=new Vector2D(DTx,DTy);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            double X0=2;
            double X1=-4;
            double X2=2;
            double DTx=X0*Q0.getX()+X1*Q1.getX()+X2*Q2.getX();
            double DTy=X0*Q0.getY()+X1*Q1.getY()+X2*Q2.getY();
            derivative=new Vector2D(DTx,DTy);
        }
        return derivative;
    }
    
    public double getSegmentLength(double t1, double t2){
        double length=0;
        
        if(this.type==MOVETO) {
            //System.out.println("*** ERROR Segment2D.getCurveLength, MOVETO segment, return 0.0");
            return 0.0;
        }
        if(this.type==LINE) {
            length=Vector2D.dist(new Vector2D(this.getP(t1)),new Vector2D(this.getP(t2)));
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double angleExtent=arc.getAngleExtent();
            double anglePich=3d;
            int div=(int)(angleExtent/anglePich);
            if(div<1) div=1;
            double delta=(t2-t1)/div;
            double t=t1;
            Vector2D p1=new Vector2D(this.getP(t1));
            for(int i=0;i<div;i++){
                Vector2D p2=new Vector2D(this.getP(t+delta));
                length+=Vector2D.dist(p1, p2);
                t=t+delta;
                p1=(Vector2D)p2.clone();
            }
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Vector2D[] Q=new Vector2D[4];
            Q[0]=new Vector2D(curve.getP1());
            Q[1]=new Vector2D(curve.getCtrlP1());
            Q[2]=new Vector2D(curve.getCtrlP2());
            Q[3]=new Vector2D(curve.getP2());
            for(int i=0;i<3;i++) length+=Vector2D.dist(Q[i], Q[i+1]);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Vector2D[] Q=new Vector2D[3];
            Q[0]=new Vector2D(curve.getP1());
            Q[1]=new Vector2D(curve.getCtrlPt());
            Q[2]=new Vector2D(curve.getP2());
            for(int i=0;i<2;i++) length+=Vector2D.dist(Q[i], Q[i+1]);
        }
        return length;
    }

    public Rectangle2D getBoundingBox(){
        Rectangle2D box=null;
        if(this.type==MOVETO) {
            System.out.println("*** ERROR Segment2D.getBoundingBox, MOVETO segment, return null");
            return null;
        }
        if(this.type==LINE||this.type==ARC) {
            Shape shape=this.getShape();
            return shape.getBounds2D();
        }
        if(this.type==CUBIC) {
            int div=4;
            double dlt=1.0/(double)div;
            double xmin=1.0e+4, ymin=xmin, xmax=-xmin, ymax=-xmin;
            for(int i=0;i<div;i++){
                Segment2D segment=this.trimSegment(dlt*i, dlt*(i+1));
                CubicCurve2D curve=(CubicCurve2D)segment.shape;
                Point2D[] Q=new Point2D[4];
                Q[0]=curve.getP1();
                Q[1]=curve.getCtrlP1();
                Q[2]=curve.getCtrlP2();
                Q[3]=curve.getP2();
                for(int j=0;j<4;j++) {
                    double x=Q[j].getX();
                    double y=Q[j].getY();
                    if(x<xmin) xmin=x;
                    if(x>xmax) xmax=x;
                    if(y<ymin) ymin=y;
                    if(y>ymax) ymax=y;
                }
            }
            box=new Rectangle2D.Double(xmin, ymin, xmax-xmin, ymax-ymin);
        }
        if(this.type==QUAD) {
            int div=4;
            double dlt=1.0/(double)div;
            double xmin=1.0e+4, ymin=xmin, xmax=-xmin, ymax=-xmin;
            for(int i=0;i<div;i++){
                Segment2D segment=this.trimSegment(dlt*i, dlt*(i+1));
                QuadCurve2D curve=(QuadCurve2D)segment.shape;
                Point2D[] Q=new Point2D[3];
                Q[0]=curve.getP1();
                Q[1]=curve.getCtrlPt();
                Q[2]=curve.getP2();
                for(int j=0;j<3;j++) {
                    double x=Q[j].getX();
                    double y=Q[j].getY();
                    if(x<xmin) xmin=x;
                    if(x>xmax) xmax=x;
                    if(y<ymin) ymin=y;
                    if(y>ymax) ymax=y;
                }
            }
            box=new Rectangle2D.Double(xmin, ymin, xmax-xmin, ymax-ymin);
        }
        return box;
    }

    public Rectangle2D getBoundingBox(double t1, double t2){
        if(t1<-this.eps4){
            System.err.println("*** Error in Segment2D.getBoundingBox: t1 parameter out of bound, t1="+t1);
            t1=0;
        }
        if(t2>1d+this.eps4){
            System.err.println("*** Error in Segment2D.getBoundingBox: t2 out of bound, t2="+t2);
            t2=1d;
        }
        Segment2D trimmedSegment=this.trimSegment(t1, t2);
        return trimmedSegment.getBoundingBox();
    }
	
//**** resizeSegment
    public Segment2D resizeSegment(Rectangle2D oldBox, Rectangle2D newBox){
        if(debug>0) System.out.println("++Segment2D.resizeSegment oldBox="+Util.Rect(oldBox)+
                ", newBox="+Util.Rect(newBox));
        double x=oldBox.getX();
        double y=oldBox.getY();
        double w=oldBox.getWidth();
        double h=oldBox.getHeight();
        double X=newBox.getX();
        double Y=newBox.getY();
        double scaleX=1.0;
        double scaleY=1.0;
        if(w>0) scaleX=newBox.getWidth()/w;
        if(h>0) scaleY=newBox.getHeight()/h;
        Segment2D newSegment=null;
        if(this.type==MOVETO) {
            newSegment=new Segment2D(this.type, null, null);
        }
        
        if(this.type==LINE) {
            Line2D line=(Line2D)this.getShape();
            double x0=scaleX*(line.getP1().getX()-x)+X;
            double y0=scaleY*(line.getP1().getY()-y)+Y;
            double x1=scaleX*(line.getP2().getX()-x)+X;
            double y1=scaleY*(line.getP2().getY()-y)+Y;
            Line2D newLine=new Line2D.Double(x0, y0, x1, y1);
            newSegment=new Segment2D(LINE, newLine);
        }
        if(this.type==ARC) {
            if(this.affineTransform==null){
                Arc2D arc=(Arc2D)this.shape;
                double start=arc.getAngleStart();
                double extent=arc.getAngleExtent();
                double x0=scaleX*(arc.getX()-x)+X;
                double y0=scaleY*(arc.getY()-y)+Y;
                Arc2D newArc=new Arc2D.Double(x0, y0, scaleX*arc.getWidth(), scaleY*arc.getHeight(), 
                        start, extent, Arc2D.OPEN);
                newSegment=new Segment2D(ARC, newArc);
            } else{
                Matrix2D matrix=Matrix2D.getResizeMatrix(oldBox, newBox);
                Matrix2D oldMatrix=new Matrix2D();
                oldMatrix.setAffineTransform(this.affineTransform);
                Matrix2D newMatrix=Matrix2D.multMatrix(matrix, oldMatrix);
                AffineTransform newAffine=newMatrix.getAffineTransform();
                newSegment=new Segment2D(ARC, this.shape, newAffine);
            }
        }

        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.getShape();
            Point2D[] Q=new Point2D[4];
            Q[0]=curve.getP1();
            Q[1]=curve.getCtrlP1();
            Q[2]=curve.getCtrlP2();
            Q[3]=curve.getP2();
            for(int i=0;i<4;i++) {
                Q[i].setLocation(scaleX*(Q[i].getX()-x)+X, scaleY*(Q[i].getY()-y)+Y);
            }
            CubicCurve2D newCurve=new CubicCurve2D.Double(Q[0].getX(), Q[0].getY(), Q[1].getX(),Q[1].getY(), 
                    Q[2].getX(),Q[2].getY(), Q[3].getX(), Q[3].getY());
            newSegment=new Segment2D(CUBIC, newCurve);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.getShape();
            Point2D[] Q=new Point2D[3];
            Q[0]=curve.getP1();
            Q[1]=curve.getCtrlPt();
            Q[2]=curve.getP2();
            for(int i=0;i<3;i++) {
                Q[i].setLocation(scaleX*(Q[i].getX()-x)+X, scaleY*(Q[i].getY()-y)+Y);
            }
            QuadCurve2D newCurve=new QuadCurve2D.Double(Q[0].getX(), Q[0].getY(), Q[1].getX(),Q[1].getY(), 
                    Q[2].getX(),Q[2].getY());
            newSegment=new Segment2D(QUAD, newCurve);
        }
        if(debug>0) System.out.println("++Segment2D.resizeSegment curve2D="+this.toString());
        return newSegment;
    }
//**** trimSegment
    public Segment2D trimSegment(double t1, double t2){
        Segment2D subSegment=null;
        if(t1<0||t1>1.0||t2<0||t2>1.0) {
            System.out.println("Segment2D.subSegment t1 or t2 out of bound: t1="+t1+", t2="+t2);
        }
        if(this.type==MOVETO) {
            subSegment=new Segment2D(this.type, null, null);
        }
        
        if(this.type==LINE) {
            Point2D P1=this.getP(t1);
            Point2D P2=this.getP(t2);
            Line2D line=new Line2D.Double(P1, P2);
            subSegment=new Segment2D(LINE, line, null);
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double x=arc.getX();
            double y=arc.getY();
            double w=arc.getWidth();
            double h=arc.getHeight();
            double start=arc.getAngleStart();
            double extent=arc.getAngleExtent();
            Arc2D subArc=new Arc2D.Double(x, y, w, h, start+extent*t1, extent*(t2-t1), Arc2D.OPEN);
            if(this.affineTransform==null) subSegment=new Segment2D(ARC, subArc, null);
            else subSegment=new Segment2D(ARC, subArc, (AffineTransform)this.affineTransform.clone());
        }
        if(this.type==CUBIC) {
            Vector2D[] Q=new Vector2D[4];
            Q[0]=new Vector2D(this.getP(t1));
            Q[3]=new Vector2D(this.getP(t2));
            double interval=t2-t1;
            Vector2D DP0=Vector2D.multiply(interval,this.getTangent(t1));
            Vector2D DP1=Vector2D.multiply(interval,this.getTangent(t2));
            DP0=Vector2D.multiply(1d/3d, DP0);
            Q[1]=Vector2D.add(Q[0],DP0);
            DP1=Vector2D.multiply(1d/3d, DP1);
            Q[2]=Vector2D.sub(Q[3], DP1);
            CubicCurve2D curve=new CubicCurve2D.Double(Q[0].getX(), Q[0].getY(),
                    Q[1].getX(), Q[1].getY(), Q[2].getX(), Q[2].getY(),
                    Q[3].getX(), Q[3].getY());
            subSegment=new Segment2D(CUBIC, curve, null);
        }
        if(this.type==QUAD) {
            Vector2D[] Q=new Vector2D[3];
            Q[0]=new Vector2D(this.getP(t1));
            Q[2]=new Vector2D(this.getP(t2));
            double interval=t2-t1;
            Vector2D DP0=Vector2D.multiply(interval,this.getTangent(t1));
            Vector2D DP1=Vector2D.multiply(interval,this.getTangent(t2));
            DP0=Vector2D.multiply(0.5, DP0);
            Q[1]=Vector2D.add(Q[0],DP0);
            DP1=Vector2D.multiply(0.5, DP1);
            Vector2D Q1=Vector2D.sub(Q[2], DP1);
            QuadCurve2D curve=new QuadCurve2D.Double(Q[0].getX(), Q[0].getY(),
                    Q[1].getX(), Q[1].getY(), Q[2].getX(), Q[2].getY());
            subSegment=new Segment2D(QUAD, curve, null);
            double error=Vector2D.dist(Q[1], Q1);
            if(error>eps4){
                System.err.println("*** ERROR Segment2D.trimSegment QuadCurve2D error="+error);
            }
        }
        return subSegment;
    }
//**** getFergusonCurve
    public FergusonCurve2D getFergusonCurve(){
        FergusonCurve2D fergusonCurve=null;
        if(this.type==MOVETO) {
            System.out.println("*** ERROR Segment2D.trimSegment, MOVETO segment, return null");
            return null;
        }
        if(this.getType()==Segment2D.LINE||
                this.getType()==Segment2D.CUBIC) {
            Point2D[] P=new Point2D[2];
            Vector2D[] Tin=new Vector2D[2];
            Vector2D[] Tout=new Vector2D[2];
            double t=0;
            for(int i=0;i<=1;i++){
                P[i]=this.getP(i);
                Tin[i]=this.getTangent(i);
                Tout[i]=this.getTangent(i);
            }
            fergusonCurve=new FergusonCurve2D(P, Tin, Tout);
        }
        if(this.getType()==Segment2D.ARC){
            Arc2D arc=(Arc2D)this.shape;
            double segmentLength=this.getSegmentLength(0, 1);
            double w=arc.getWidth();
            double h=arc.getHeight();
            double minR=0.5*Math.min(w, h);
            double anglePitch=20.0/180.0*Math.PI;
            double segmentPitch=anglePitch*minR;
            int div=(int)(segmentLength/segmentPitch);
            if(div==0) div=1;
            System.out.println("** Segment2D.convertArcToCubic segmentPich="+Util.Num(segmentPitch)+
                    ", division="+div);
            double dt=1.0/(double)div;
            Point2D[] P=new Point2D[div+1];
            Vector2D[] Tin=new Vector2D[div+1];
            Vector2D[] Tout=new Vector2D[div+1];
            double t=0;
            for(int i=0;i<=div;i++){
                P[i]=this.getP(t);
                Tin[i]=Vector2D.multiply(dt, this.getTangent(t));
                Tout[i]=(Vector2D)Tin[i].clone();
                t+=dt;
            }
            fergusonCurve=new FergusonCurve2D();
            for(int i=0;i<div;i++){
                Vector2D[] optimizedTangents
                    =fergusonCurve.getOptimizedTangents(P[i], P[i+1], Tout[i], Tin[i+1]);
                Tout[i]=optimizedTangents[0];
                Tin[i+1]=optimizedTangents[1];
            }
            fergusonCurve=new FergusonCurve2D(P, Tin, Tout);
        }
        return fergusonCurve;
    }
//**** transformSegment
    public Segment2D transformSegment(Matrix2D M){
        Segment2D newSegment=null;
        if(this.type==MOVETO) {
            newSegment=new Segment2D(this.type, null, null);
            return newSegment;
        }
        if(this.type==LINE) {
            Point2D P1=this.getP(0);
            Point2D P2=this.getP(1);
            Point2D newP1=Matrix2D.transForm(M, P1);
            Point2D newP2=Matrix2D.transForm(M, P2);
            Line2D newLine=new Line2D.Double(newP1, newP2);
            newSegment=new Segment2D(LINE, newLine);
        }
        if(this.type==ARC) {
            if(this.affineTransform==null) this.affineTransform=new AffineTransform();
            Matrix2D oldMatrix=new Matrix2D();
            oldMatrix.setAffineTransform(this.affineTransform);
            Matrix2D newMatrix=Matrix2D.multMatrix(M, oldMatrix);
            AffineTransform newAffine=newMatrix.getAffineTransform();
            newSegment=new Segment2D(ARC, this.shape, newAffine);
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D[] Q=new Point2D[4];
            Q[0]=curve.getP1();
            Q[1]=curve.getCtrlP1();
            Q[2]=curve.getCtrlP2();
            Q[3]=curve.getP2();
            Point2D[] newQ=new Point2D[4];
            for(int i=0;i<4;i++) newQ[i]=Matrix2D.transForm(M, Q[i]);
            CubicCurve2D newCurve=new CubicCurve2D.Double(newQ[0].getX(), newQ[0].getY(),
                    newQ[1].getX(), newQ[1].getY(), newQ[2].getX(), newQ[2].getY(),
                    newQ[3].getX(), newQ[3].getY());
            newSegment=new Segment2D(CUBIC, newCurve);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D[] Q=new Point2D[3];
            Q[0]=curve.getP1();
            Q[1]=curve.getCtrlPt();
            Q[2]=curve.getP2();
            Point2D[] newQ=new Point2D[3];
            for(int i=0;i<3;i++) newQ[i]=Matrix2D.transForm(M, Q[i]);
            QuadCurve2D newCurve=new QuadCurve2D.Double(newQ[0].getX(), newQ[0].getY(),
                    newQ[1].getX(), newQ[1].getY(), newQ[2].getX(), newQ[2].getY());
            newSegment=new Segment2D(QUAD, newCurve);
        }
        return newSegment;
    }
//**** reverseSegment
    public Segment2D reverseSegment(){
        Segment2D newSegment=null;
        if(this.type==MOVETO) {
            newSegment=new Segment2D(this.type, null, null);
            return newSegment;
        }
        if(this.type==LINE) {
            Line2D line=(Line2D)this.shape;
            Line2D newLine=new Line2D.Double(line.getP2(), line.getP1());
            newSegment=new Segment2D(LINE, newLine);
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double angleStart=arc.getAngleStart();
            double angleExtent=arc.getAngleExtent();
            Arc2D newArc=new Arc2D.Double(arc.getX(), arc.getY(), arc.getWidth(), 
                    arc.getHeight(), angleStart+angleExtent, -angleExtent, Arc2D.OPEN);
            newSegment=new Segment2D(ARC, newArc, this.affineTransform);
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlP1();
            Point2D Q2=curve.getCtrlP2();
            Point2D Q3=curve.getP2();
            CubicCurve2D newCubic=new CubicCurve2D.Double(Q3.getX(),Q3.getY(), 
                    Q2.getX(),Q2.getY(), Q1.getX(),Q1.getY(), Q0.getX(),Q0.getY());
            newSegment=new Segment2D(CUBIC, newCubic);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            QuadCurve2D newCurve=new QuadCurve2D.Double(Q2.getX(), Q2.getY(), Q1.getX(), Q1.getY(), Q0.getX(), Q0.getY());
            newSegment=new Segment2D(QUAD, newCurve);
        }
        return newSegment;
    }
//**** moveSegmentEndPT
    public Segment2D moveSegmentEndPT(int index, Point2D newPT){
        Segment2D newSegment=null;
        if(this.type==MOVETO) {
            newSegment=new Segment2D(this.type, null, null);
            return newSegment;
        }
        if(this.type==LINE) {
            Line2D newLine=null;
            Line2D line=(Line2D)this.getShape();
            if(index==0){
                newLine=new Line2D.Double(newPT, line.getP2());
            } else{
                newLine=new Line2D.Double(line.getP1(), newPT);
            }
            newSegment=new Segment2D(LINE, newLine);
        }
        if(this.type==ARC) {
            Point2D anchorP=null;
            Point2D movingP=null;
            if(index==0){
                movingP=this.getP(0);
                anchorP=this.getP(1);
            } else{
                movingP=this.getP(1);
                anchorP=this.getP(0);
            }
            if(Vector2D.dist(anchorP, movingP)<this.eps4) {
                return (Segment2D)this.clone();
            }
            Vector2D r0=Vector2D.sub(movingP,anchorP);
            Vector2D r=Vector2D.sub(newPT,anchorP);
            Matrix2D rotationMatrix=Matrix2D.getRotationMatrix(anchorP, r0, r);
            newSegment=this.transformSegment(rotationMatrix);
        }
        if(this.type==CUBIC) {
            FergusonCurve2D fergusonCurve=this.getFergusonCurve();
            fergusonCurve.movePoint(index, newPT);
            CubicCurve2D newCurve=fergusonCurve.getCubicCurve2D(0);
            newSegment=new Segment2D(CUBIC, newCurve);
        }
        if(this.type==QUAD) {
            QuadCurve2D newCurve=null;
            QuadCurve2D curve=(QuadCurve2D)this.getShape();
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            if(index==0){
                newCurve=new QuadCurve2D.Double(newPT.getX(), newPT.getY(),
                        Q1.getX(), Q1.getY(), Q2.getX(), Q2.getY());
            } else{
                newCurve=new QuadCurve2D.Double(Q0.getX(), Q0.getY(),
                        Q1.getX(), Q1.getY(), newPT.getX(), newPT.getY());
            }
            newSegment=new Segment2D(QUAD, newCurve);
        }
        return newSegment;
    }
//**** moveSegmentTangent
    public Segment2D moveSegmentTangent(int index, Vector2D newTangent){
        Segment2D newSegment=null;
        if(this.type==CUBIC) {
            FergusonCurve2D fergusonCurve=this.getFergusonCurve();
            int inout=FergusonCurve2D.TOUT;
            if(index==1) inout=FergusonCurve2D.TIN;
            fergusonCurve.moveTangent(index, inout, newTangent);
            CubicCurve2D newCubic=fergusonCurve.getCubicCurve2D(0);
            newSegment=new Segment2D(CUBIC, newCubic);
        } else{
            newSegment=(Segment2D)this.clone();
        }
        return newSegment;
    }

    public Object clone(){
        Segment2D newSegment=null;
        if(this.type==MOVETO) {
            newSegment=new Segment2D(this.type, null, null);
            return newSegment;
        }
        if(this.type==LINE){
            Line2D line=(Line2D)this.shape;
            Line2D newLine=(Line2D)line.clone();
            newSegment=new Segment2D(this.type, newLine, null);
        }
        if(this.type==ARC){
            Arc2D arc=(Arc2D)this.shape;
            Arc2D newArc=(Arc2D)arc.clone();
            if(this.affineTransform==null) newSegment=new Segment2D(ARC, newArc, null);
            else newSegment=new Segment2D(ARC, newArc, (AffineTransform)this.affineTransform.clone());
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            CubicCurve2D newCurve=(CubicCurve2D)curve.clone();
            newSegment=new Segment2D(this.type, newCurve, null);
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            QuadCurve2D newCurve=(QuadCurve2D)curve.clone();
            newSegment=new Segment2D(this.type, newCurve, null);
        }
        return newSegment;
    }

    public String toString(){
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumIntegerDigits(4);
        nf.setMinimumFractionDigits(1);
        String str="";
        if(this.type==MOVETO) {
            str="MoveTo";
        }
        if(this.type==LINE) {
            Line2D line=(Line2D)this.shape;
            str="LINE, x1,y1="+Util.Num(line.getX1())+", "+Util.Num(line.getY1())
                    +", x2,y2="+Util.Num(line.getX2())+", "+Util.Num(line.getY2());
            if(Math.abs(line.getY1()-line.getY2())<Curve2DUtil.eps3) str+=",  x-line";
            if(Math.abs(line.getX1()-line.getX2())<Curve2DUtil.eps3) str+=",  y-line";
        }
        if(this.type==ARC) {
            Arc2D arc=(Arc2D)this.shape;
            double x=arc.getX(), y=arc.getY(), w=arc.getWidth(), h=arc.getHeight();
            double angleStart=arc.getAngleStart();
            double angleEnd=angleStart+arc.getAngleExtent();
            double angleExtent=arc.getAngleExtent();
            double pai=Math.PI;
            double x1=0.5*w*Math.cos(angleStart*pai/180)+x+w/2;
            double y1=0.5*h*Math.sin(-angleStart*pai/180)+y+h/2;
            double x2=0.5*w*Math.cos(angleEnd*pai/180)+x+w/2;
            double y2=0.5*h*Math.sin(-angleEnd*pai/180)+y+h/2;
            Point2D p0=this.getP(0);
            Point2D p1=this.getP(1);
            String affineStr=", AffineTransform";
            AffineTransform affine=this.affineTransform;
            if(affine==null) affineStr+="=null";
            else{
                double[] elm=new double[6];
                affine.getMatrix(elm);
                affineStr+="; m00="+Util.Num(elm[0])+", m01="+Util.Num(elm[2])+
                        ", m02="+Util.Num(elm[4])+", m10="+Util.Num(elm[1])+
                        ", m11="+Util.Num(elm[3])+", m12="+Util.Num(elm[5]);
            }
            str+="ARC, startP="+Util.Pt(p0)+", endP="+Util.Pt(p1);
            //str+="ARC, x1,y1="+Util.Num(x1)+", "+Util.Num(y1)+", x2,y2="+Util.Num(x2)+", "+Util.Num(y2);
            str+=", x,y,w,h="+Util.Num(x)+", "+Util.Num(y)+", "+Util.Num(w)+", "+Util.Num(h);
            str+=", angleStart="+Util.Num(angleStart)+", angleEnd="+Util.Num(angleEnd)
                    +", angleExtent="+Util.Num(angleExtent);
            str+=affineStr;
        }
        if(this.type==CUBIC) {
            CubicCurve2D curve=(CubicCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlP1();
            Point2D Q2=curve.getCtrlP2();
            Point2D Q3=curve.getP2();
            str+="CUBIC, P1="+Util.Num(Q0.getX())+","+Util.Num(Q0.getY())+
                    ", P2="+Util.Num(Q3.getX())+","+Util.Num(Q3.getY())+
                    ", CtrlP1="+Util.Num(Q1.getX())+","+Util.Num(Q1.getY())+
                    ", CtrlP2="+Util.Num(Q2.getX())+","+Util.Num(Q2.getY());
        }
        if(this.type==QUAD) {
            QuadCurve2D curve=(QuadCurve2D)this.shape;
            Point2D Q0=curve.getP1();
            Point2D Q1=curve.getCtrlPt();
            Point2D Q2=curve.getP2();
            str+="CUBIC, P1="+Util.Num(Q0.getX())+","+Util.Num(Q0.getY())+
                    ", P2="+Util.Num(Q2.getX())+","+Util.Num(Q2.getY())+
                    ", CtrlPt="+Util.Num(Q1.getX())+","+Util.Num(Q1.getY());
        }
        return str;
    }

    public SerializableSegment2D getSerializableSegment2D(){
        SerializableSegment2D data=new SerializableSegment2D(this.type, this.shape, 
                this.affineTransform);
        return data;
    }

}
