/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package geomExtension;

import DrawTop.Command;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import util.*;

public abstract class Curve2D {
    Segment2D[] segments=null;
    GeneralPath generalPath=null;
    boolean closed=false;
    Vector workVector=new Vector();
    public final static double eps=1e-5;
    public final static double largeNumber=1e+5;
    public final static double closedTolerance=3d;
    public final static int Path2D_WIND=Path2D.WIND_EVEN_ODD;
    int debug=0;

    public Curve2D(){}

    public Curve2D(Segment2D[] segments){
        this.setData(segments);
    }

    public void setData(Segment2D[] segments){
        this.segments=segments;
        this.generalPath=new GeneralPath(Curve2D.Path2D_WIND);
        Point2D startP=segments[0].getP(0);
        Point2D endP=segments[segments.length-1].getP(1);
        double dist=Vector2D.dist(startP, endP);
        this.closed=false;
        if(dist<Curve2D.closedTolerance) {
            closed=true;
        }
        for(int i=0;i<segments.length;i++) {
            Shape shape=segments[i].getShape();
            this.generalPath.append(shape,true);
        }
        if(this.closed) this.generalPath.closePath();
    }
    
    public abstract int getType2DE();
    
    public Shape getShape(){
        return this.generalPath;
    }

    public boolean isClosed(){
        return this.closed;
    }

    public int getNumOfSegments(){
        int size=0;
        if(segments!=null) size=this.segments.length;
        return size;
    }

    public Segment2D[] getSegment2Ds(){
        return this.segments;
    }

    public Segment2D getSegment2D(int index){
        if(index<0||index>segments.length-1){
            System.err.println("*** Error Curve2D.getSegment2D ; index out of range; index="+index);
            return null;
        }
        return this.segments[index];
    }
    
    public int getNumOfSubPaths(){
        if(this.getType2DE()==Command.GENERAL_CURVE){
            GeneralCurve2DE curve= (GeneralCurve2DE)this;
            return curve.getNumOfSubPaths();
        }else{
            return 1;
        }
    }
    
    public Curve2D[] getSubPaths(){
        if(this.getType2DE()==Command.GENERAL_CURVE){
            GeneralCurve2DE curve= (GeneralCurve2DE)this;
            return curve.getSubPaths();
        }else{
            Curve2D[] subPaths=new Curve2D[1];
            subPaths[0]=(Curve2D)this;
            return subPaths;
        }
    }
/*
    public void setSubPaths(Curve2D[] curves){
        if(this.getType2DE()==Command.GENERAL_CURVE){
            GeneralCurve2DE curve= (GeneralCurve2DE)this;
            curve.setSubPaths(Curve2D[] curves);
        }else{
            Curve2D[] subPaths=new Curve2D[1];
            subPaths[0]=(Curve2D)this;
            subPaths;
        }
    }
*/
    public Point2D getP(double t){
        int it=(int)t;
        if(it<0) it=0;
        if(it>=this.segments.length) it=this.segments.length-1;
        if(this.segments[it].getType()==Segment2D.MOVETO&&it>0)it--;
        Segment2D segment=(Segment2D)this.segments[it];
        return segment.getP(t-it);
    }

    public Vector2D getTangent(double t){
        int it=(int)t;
        if(it<0) it=0;
        if(it>=this.segments.length) it=this.segments.length-1;
        if(this.segments[it].getType()==Segment2D.MOVETO&&it>0)it--;
        return segments[it].getTangent(t-it);
    }
    
    public Vector2D gettangentDerivative(double t){
        int it=(int)t;
        if(it<0) it=0;
        if(it>this.segments.length-1) it=this.segments.length-1;
        if(this.segments[it].getType()==Segment2D.MOVETO&&it>0)it--;
        return segments[it].getTangentDerivative(t-it);
    }
    
    public Point2D[] getEndPoints(){
        Point2D[] endPoints=new Point2D[2];
        int numseg=this.getNumOfSegments();
        endPoints[0]=this.getP(0);
        endPoints[1]=this.getP(numseg);
        return endPoints;
    }

    public int getCloseEndpointIndex(Point2D point){
        Point2D[] endPoints=new Point2D[2];
        int numseg=this.getNumOfSegments();
        endPoints[0]=this.getP(0);
        endPoints[1]=this.getP(numseg);
        double d0=Vector2D.dist(point, endPoints[0]);
        double d1=Vector2D.dist(point, endPoints[1]);
        if(this.isClosed()) return -1;
        int index=0;
        if(d0>d1) index=numseg;
        return index;
    }

    public Point2D[] getNodePoints(){
        int numseg=this.getNumOfSegments();
        Point2D[] points=new Point2D[numseg+1];
        for(int i=0;i<=numseg;i++) points[i]=this.getP(i);
        return points;
    }
    
    public CurvePT[] getCharacteristicPoints(){
        this.workVector.clear();
        int numseg=this.getNumOfSegments();
        boolean closed=this.isClosed();
        double delta=0.5d;
        if(closed&&numseg==1) delta=0.25d;
        for(double tc=0;tc<=numseg;tc+=delta) {
            CurvePT curvePT=new CurvePT(tc, this.getP(tc), this);
            this.workVector.add(curvePT);
        }
        int size=this.workVector.size();
        CurvePT[] curvePTs=new CurvePT[size];
        for(int i=0;i<size;i++) curvePTs[i]=(CurvePT)this.workVector.get(i);
        return curvePTs;
    }

    public Point2D[] getSamplingPTs(double pitch){
        Point2D[] points=new Point2D[0];
        CurvePT[] curvePTs=this.getSamplingCurvePTs(pitch);
        int size=0;
        if(curvePTs!=null) size=curvePTs.length;
        points=new Point2D[size];
        for(int i=0;i<size;i++) points[i]=curvePTs[i].getP();
        return points;
    }
    
    public CurvePT[] getSamplingCurvePTs(double pitch){
        CurvePT[] curvePTs=new CurvePT[0];
        int numSeg=this.getNumOfSegments();
        this.workVector.clear();
        for(int it=0;it<numSeg;it++){
            Segment2D segment=this.getSegment2D(it);
            if(segment.getType()==Segment2D.MOVETO) continue;
            double length=segment.getSegmentLength(0,1);
            int div=(int)(length/pitch);
            if(div-div/2*2>0) div++;
            double delta=1d/div;
            double t=0;
            int iEnd=div;
            if(it==numSeg-1) iEnd=div+1;
            for(int i=0;i<iEnd;i++){
                Point2D point=segment.getP(t);
                CurvePT curvePT=new CurvePT(t+it, point);
                this.workVector.add(curvePT);
                t=t+delta; 
            }
        }
        if(this.workVector.size()==0) return null;
        curvePTs=new CurvePT[this.workVector.size()];
        for(int i=0;i<this.workVector.size();i++) {
            curvePTs[i]=(CurvePT)this.workVector.get(i);
        }
        if(debug>0){
            String str="Curve2D getSamplingCurvePTs";
            for(int i=0;i<this.workVector.size();i++) {
                curvePTs[i]=(CurvePT)this.workVector.get(i);
                str+="\n  curvePTs["+i+"]: t="+Util.Num(curvePTs[i].getParameter())+
                    ", PT="+Util.Pt(curvePTs[i].getP());
            }
            System.out.println(str);
        }
        return curvePTs;
    }
    public double getCurveLength(double t1, double t2){
        int numSeg=this.getNumOfSegments();
        if(t1<0||t1>numSeg||t2<0||t2>numSeg){
            System.out.println("*** Warning SegmentedCurve2D ; parameter out og range;" +
                    " t1, t2="+t1+","+t2);
            if(t1<0) t1=0;
            if(t1>numSeg) t1=numSeg;
            if(t2<0) t2=0;
            if(t2>numSeg) t2=numSeg;
        }
        int it1=(int)t1;
        int it2=(int)t2;
        double length=0;
        for(int i=it1;i<=it2;i++) {
            double ts=0, te=1;
            if(i==it1) ts=t1-it1;
            if(i==it2) te=t2-it2;
            if(te-ts>Curve2D.eps){
                Segment2D segment2D=this.getSegment2D(i);
                length+=segment2D.getSegmentLength(ts, te);
            }
        }
        return length;
    }

    public Rectangle2D getBoundingBox(){
        double xmin=1.0e+4, ymin=xmin, xmax=-xmin, ymax=-xmin;
        for(int i=0;i<this.segments.length;i++){
            if(this.segments[i].getType()==Segment2D.MOVETO) continue;
            Rectangle2D box=this.segments[i].getBoundingBox();
            if(box==null) continue;
             double x=box.getX();
             double y=box.getY();
             double w=box.getWidth();
             double h=box.getHeight();
             if(x<xmin) xmin=x;
             if(y<ymin) ymin=y;
             if(x+w>xmax) xmax=x+w;
             if(y+h>ymax) ymax=y+h;
        }
         Rectangle2D maxBoundingBox=
            new Rectangle2D.Double(xmin, ymin, xmax-xmin, ymax-ymin);
        return maxBoundingBox;
    }

    public Rectangle2D getBoundingBox(double t1, double t2){
        if(this.segments==null) return null;
        double Xmin=Math.pow(10, 5);
        double Ymin=Xmin;
        double Xmax=-Xmin;
        double Ymax=-Ymin;
        int it1=(int)t1;
        int it2=(int)t2;
        for(int it=it1;it<=it2;it++){
            double ts=0;
            double te=1;
            if(it==it1) ts=t1-it;
            if(it==it2) te=t2-it;
            if(te-ts<eps) continue;
            if(this.getSegment2D(it).getType()==Segment2D.MOVETO) continue;
            Rectangle2D box=this.getSegment2D(it).getBoundingBox(ts, te);
            if(box==null) continue;
            if(box.getX()<Xmin) Xmin=box.getX();
            if(box.getY()<Ymin) Ymin=box.getY();
            if(box.getX()+box.getWidth()>Xmax) Xmax=box.getX()+box.getWidth();
            if(box.getY()+box.getHeight()>Ymax) Ymax=box.getY()+box.getHeight();
        }
        return new Rectangle2D.Double(Xmin, Ymin, Xmax-Xmin, Ymax-Ymin);
    }
    
//**** getSerializableCurve2D
    public SerializableCurve2D getSerializableCurve2D(){
        SerializableCurve2D sdata=new SerializableCurve2D();
        sdata.type=this.getType2DE();
        Segment2D[] segments=this.getSegment2Ds();
        int numSeg=this.getNumOfSegments();
        SerializableSegment2D[] serializableSegments=new SerializableSegment2D[numSeg];
        for(int i=0;i<numSeg;i++){
            serializableSegments[i]=segments[i].getSerializableSegment2D();
        }
        sdata.setSegments(serializableSegments);
        return sdata;
    }
	
//**** getSerializableCurve2D
    public void setSerializableCurve2D(SerializableCurve2D sdata){
        SerializableSegment2D[] data=sdata.serializableSegments;
        int size=data.length;
        Segment2D[] segments=new Segment2D[size];
        for(int i=0;i<size;i++){
            segments[i]=new Segment2D(data[i].type, data[i].shape, data[i].affineTransform);
        }
        this.setData(segments);
    }
	
//**** convertToGeneralCurve2DE
    public GeneralCurve2DE convertToGeneralCurve2DE(){
        int numseg=this.getNumOfSegments();
        Segment2D[] segments=new Segment2D[numseg];
        for(int i=0;i<numseg;i++) segments[i]=(Segment2D)this.getSegment2D(i).clone();
        GeneralCurve2DE generalcurve=new GeneralCurve2DE(segments); 
        return generalcurve;
    }
//**** getExtendedCurve2DE
    public GeneralCurve2DE getExtendedCurve2DE(double ext){
        if(this.isClosed()) return null;
        int numseg=this.getNumOfSegments();
        Segment2D[] newSegments=new Segment2D[numseg+2];
        for(int i=0;i<numseg;i++) newSegments[i+1]=(Segment2D)this.getSegment2D(i).clone();
        Vector2D p=new Vector2D(this.getP(0));
        Vector2D Tvec=this.getTangent(0);
        Tvec=Vector2D.multiply(-ext, Vector2D.unitVector(Tvec));
        Vector2D pExt=Vector2D.add(p, Tvec);
        Line2D line=new Line2D.Double(pExt.getX(), pExt.getY(), p.getX(), p.getY());
        Segment2D newSeg=new Segment2D(Segment2D.LINE, line);
        newSegments[0]=newSeg;
        p=new Vector2D(this.getP(numseg));
        Tvec=this.getTangent(numseg);
        Tvec=Vector2D.multiply(ext, Vector2D.unitVector(Tvec));
        pExt=Vector2D.add(p, Tvec);
        line=new Line2D.Double(p.getX(), p.getY(), pExt.getX(), pExt.getY());
        newSeg=new Segment2D(Segment2D.LINE, line);
        newSegments[numseg+1]=newSeg;
        GeneralCurve2DE generalcurve=new GeneralCurve2DE(newSegments); 
        return generalcurve;
    }

    public void reverse(){
        int numseg=this.getNumOfSegments();
        Segment2D[] newSegments=new Segment2D[numseg];
        for(int i=0;i<numseg;i++){
            Segment2D segment=(Segment2D)this.getSegment2D(numseg-1-i).clone();
            newSegments[i]=segment.reverseSegment();
        }
        this.setData(newSegments);
    }
    
    public void transform(Matrix2D M){
        int numseg=this.getNumOfSegments();
        Segment2D[] newSegments=new Segment2D[numseg];
        for(int i=0;i<numseg;i++) {
            newSegments[i]=this.getSegment2D(i).transformSegment(M);
        }
        this.setData(newSegments);
    }
    
    public abstract Object clone();
    public abstract String toString();
    
    public String toPathString() {
        double[] coordinates = new double[6];
        Shape path=this.generalPath;
        String str="";
        PathIterator pi = path.getPathIterator(null);
        while (pi.isDone() == false) {
            int type = pi.currentSegment(coordinates);
            switch (type) {
                case PathIterator.SEG_MOVETO:
                    str+="   move to  "+Util.Num(coordinates[0])+ ", "+ Util.Num(coordinates[1])+"\n";
                    break;
                case PathIterator.SEG_LINETO:
                    str+="   line to  " + Util.Num(coordinates[0]) + ", "+ Util.Num(coordinates[1])+"\n";
                    break;
                case PathIterator.SEG_QUADTO:
                    str+="   quadratic to  " + Util.Num(coordinates[0]) + ", "
                            + Util.Num(coordinates[1])+ ", " + Util.Num(coordinates[2])+ ", "
                            + Util.Num(coordinates[3])+"\n";
                    break;
                case PathIterator.SEG_CUBICTO:
                    str+="   cubic to  " + Util.Num(coordinates[0])+ ", "
                            + Util.Num(coordinates[1])+ ", " +Util.Num(coordinates[2])+ ", "
                            + Util.Num(coordinates[3])+ ", " +Util.Num(coordinates[4])+ ", "
                            + Util.Num(coordinates[5])+"\n";
                    break;
                case PathIterator.SEG_CLOSE:
                    str+="   close"+"\n";
                    break;
                default:
                    break;
            }
            pi.next();
        }
        return str;
    }

} // End of class

