package geomExtension;

import java.awt.*;
import java.awt.geom.*;
import java.text.*;
import java.util.*;
import DrawTop.*;
import shape.*;
import util.*;

public class GeneralCurve2DE extends Curve2D{
    int debug=0;
    
    public GeneralCurve2DE(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=null;
        
        for(int i=0;i<segments.length;i++) {
            if(segments[i].getType()==Segment2D.MOVETO){
                if(i==0||i==(segments.length-1)){
                    System.err.println("*** ERROR GeneralCurve2DE");
                    continue;
                }
                endP=segments[i-1].getP(1);
                if(Vector2D.dist(startP, endP)<Curve2D.eps) this.generalPath.closePath();
                startP=segments[i+1].getP(0);
                this.generalPath.moveTo(startP.getX(), startP.getY());
            } else {
                Shape shape=segments[i].getShape();
                this.generalPath.append(shape,true);
            }
            if(i==(segments.length-1)){
                endP=segments[i].getP(1);
                if(Vector2D.dist(startP, endP)<Curve2D.eps) this.generalPath.closePath();
            }
        }
        this.closed=this.isClosed();
        String str="";
        str+="*** GeneralCurve2DE pathtoString\n";
        str+=this.toPathString();
        if(debug>0) System.out.println(str);
    }
    
    public int getType2DE(){
        return Command.GENERAL_CURVE;
    }

    public int getNumOfSubPaths(){
        int pathCount=0;
        int localSegCount=0;
        int error=0;
        for(int i=0;i<segments.length;i++) {
            if(segments[i].getType()==Segment2D.MOVETO) {
                if(localSegCount==0) error=1;
                if(i==segments.length-1) error=2;
                pathCount++;
                localSegCount=0;
                
            } else{
                localSegCount++;
            }
        }
        if(error==1) System.err.println("*** ERROR in GeneralCurve2DE.getNumOfSubPaths: "
                + "No segments before MOVETO");
        if(error==2) System.err.println("*** ERROR in GeneralCurve2DE.getNumOfSubPaths: "
                + "The last segmentis before MOVETO");
        return (pathCount+1);
    }
    
    public int[] getNumOfLocalSegments(){
        int num=this.getNumOfSubPaths();
        int[] numseg=new int[num];
        int pathCount=0;
        int localSegCount=0;
        for(int i=0;i<segments.length;i++){
            if(segments[i].getType()==Segment2D.MOVETO) {
                numseg[pathCount]=localSegCount;
                pathCount++;
                localSegCount=0;
            } else {
                localSegCount++;
            }
            if(i==segments.length-1) numseg[pathCount]=localSegCount;
        }
        
        return numseg;
    }
    
    public int getSubPathIndex(int segmentPtIndex){
        if(segmentPtIndex<0||segmentPtIndex>this.segments.length){
            System.err.println("*** ERROR in GeneralCurve2DE.getSubPathIndex: "
                + "segmentPtIndex is out-of-bound, segmentPtIndex="+segmentPtIndex);
            return -1;
        }
        int pathCount=0;
        int localSegCount=-1;
        int max=Math.min(segmentPtIndex, this.segments.length);
        for(int i=0;i<max;i++){
            if(this.segments[i].getType()==Segment2D.MOVETO) {
                pathCount++;
                localSegCount=-1;
            } else {
                localSegCount++;
            }
        }
        return pathCount;
    }

    private int getLocalSegmentIndex(int segmentIndex){
        if(segmentIndex<0||segmentIndex>=this.segments.length) {
            System.err.println("*** ERROR in GeneralCurve2DE.getLocalSegmentIndex: "
                + "segmentPtIndex is out-of-bound, segmentIndex="+segmentIndex);
            return -1;
        }
        int pathCount=0;
        int localSegIndex=-1;
        for(int i=0;i<=segmentIndex;i++){
            if(this.segments[i].getType()==Segment2D.MOVETO) {
                pathCount++;
                localSegIndex=-1;
            } else {
                localSegIndex++;
            }
        }
        return localSegIndex;
    }

    public int getLocalSegmentPtIndex(int segmentPtIndex){
        if(segmentPtIndex<0||segmentPtIndex>this.segments.length) {
            System.err.println("*** ERROR in GeneralCurve2DE.getLocalSegmentPtIndex: "
                + "segmentPtIndex is out-of-bound, segmentPtIndex="+segmentPtIndex);
            return -1;
        }
        int pathCount=0;
        int localSegIndex=-1;
        for(int i=0;i<=Math.min(segmentPtIndex, this.segments.length-1);i++){
            if(this.segments[i].getType()==Segment2D.MOVETO) {
                if(i==segmentPtIndex) return ++localSegIndex;
                pathCount++;
                localSegIndex=-1;
            } else {
                localSegIndex++;
            }
        }
        if(segmentPtIndex==this.segments.length) return ++localSegIndex;
        return localSegIndex;
    }

    private int getGlobalSegmentIndex(int subPathIndex, int localSegmentIndex){
        int numSubPath=this.getNumOfSubPaths();
        int[] numLocalSegs=this.getNumOfLocalSegments();
        if(subPathIndex<0||subPathIndex>=numSubPath) {
            System.err.println("*** ERROR in GeneralCurve2DE.getGlobalSegmentIndex: "
                + "segmentPtIndex is out-of-bound, subPathIndex="+subPathIndex);
            return -1;
        }
        if(localSegmentIndex<0||localSegmentIndex>=numLocalSegs[subPathIndex]) {
            System.err.println("*** ERROR in GeneralCurve2DE.getGlobalSegmentIndex: "
                + "segmentPtIndex is out-of-bound, localSegmentIndex="+localSegmentIndex);
            return -1;
        }
        
        int pathCount=0;
        int localSegIndex=-1;
        int isave=-1;
        for(int i=0;i<segments.length;i++){
            if(segments[i].getType()==Segment2D.MOVETO) {
                pathCount++;
                localSegIndex=-1;
            } else {
                localSegIndex++;
            }
            if(pathCount==subPathIndex&&localSegIndex==localSegmentIndex) return i;
        }
        return isave;
    }
    
    public int getGlobalSegmentPtIndex(int subPathIndex, int localSegmentPtIndex){
        int numSubPath=this.getNumOfSubPaths();
        int[] numLocalSegs=this.getNumOfLocalSegments();
        if(subPathIndex<0||subPathIndex>=numSubPath){
            System.err.println("*** ERROR in GeneralCurve2DE.getGlobalSegmentIndex: "
                + "segmentPtIndex is out-of-bound, subPathIndex="+subPathIndex);
            return -1;
        }
        if(localSegmentPtIndex<0||localSegmentPtIndex>numLocalSegs[subPathIndex]){
            System.err.println("*** ERROR in GeneralCurve2DE.getGlobalSegmentIndex: "
                + "segmentPtIndex is out-of-bound, subPathIndex="+subPathIndex
                +", localSegmentPtIndex="+localSegmentPtIndex);
            return -1;
        }
        
        int pathCount=0;
        int localSegIndex=-1;
        int isave=-1;
        for(int i=0;i<segments.length;i++){
            if(segments[i].getType()==Segment2D.MOVETO) {
                if(pathCount==subPathIndex&&localSegIndex+1==localSegmentPtIndex) return i;
                pathCount++;
                localSegIndex=-1;
            } else {
                localSegIndex++;
            }
            if(pathCount==subPathIndex&&localSegIndex==localSegmentPtIndex) return i;
        }
        if(pathCount==subPathIndex&&localSegIndex+1==localSegmentPtIndex) return segments.length;
        return isave;
    }

    private Segment2D[] getSubPathSegments(int subPathIndex){
        int pathCount=0;
        Vector vector=new Vector();
        for(int i=0;i<this.segments.length;i++){
            if(this.segments[i].getType()==Segment2D.MOVETO) {
                pathCount++;
            } else {
                if(subPathIndex==pathCount){
                    vector.add(this.segments[i]);
                } else{
                    continue;
                }
            }
        }
        Segment2D[] pathSegments=new Segment2D[vector.size()];
        for(int i=0; i<vector.size();i++){
            pathSegments[i]=(Segment2D)vector.get(i);
        }
        return pathSegments;
    }
    
    private GeneralCurve2DE getSubPath(int index){
        Segment2D[] path=this.getSubPathSegments(index);
        GeneralCurve2DE curve=new GeneralCurve2DE(path);
        return curve;
    }
    
    public GeneralCurve2DE[] getSubPaths(){
        int numPath=this.getNumOfSubPaths();
        GeneralCurve2DE[] curves=new GeneralCurve2DE[numPath];
        for(int i=0;i<numPath;i++){
            curves[i]=this.getSubPath(i);
            if(debug>0) System.out.println("GeneralCurve2DE.getPathCurves "
                    + "i="+i+", PathCurve:"+curves[i].toString());
        }
        return curves;
    }
    
    public void setSubPaths(Curve2D[] curves){
        int numPath=curves.length;
      //closed?
        boolean[] closed=new boolean[numPath];
        Point2D[][] endP=new Point2D[numPath][2];
        for(int i=0;i<numPath;i++){
            closed[i]=false;
            Curve2D curve=curves[i];
            int numseg=curve.getNumOfSegments();
            Point2D P0=curve.getP(0);
            Point2D P1=curve.getP(numseg);
            if(Vector2D.dist(P0,P1)<Curve2D.eps) closed[i]=true;
            endP[i][0]=P0;
            endP[i][1]=P1;
        }
      //connection check
        PathConnect con=new PathConnect();
        PathSegment[][] paths=con.findPaths(endP);
        int numPaths=paths.length;
        if(debug>0){
            String str="\nGeneralCurve2DE.setSubPaths paths list";
            for(int i=0;i<numPaths;i++){
                int len=paths[i].length;
                if(len==0) continue;
                str+="\npath no.="+i;
                for(int j=0;j<len;j++){
                    PathSegment pathSeg=paths[i][j];
                    str+="\n  -- pathSegment "+pathSeg.toString();
                }
            }
            System.out.println(str);
        }
        int totalNumSeg=0;
        for(int i=0;i<curves.length;i++){
            totalNumSeg+=curves[i].getNumOfSegments();
        }
        Segment2D[] segments=new Segment2D[totalNumSeg+numPaths-1];
        Segment2D moveSeg=new Segment2D(Segment2D.MOVETO, null);
        int index=0;
        for(int i=0;i<numPaths;i++){
            int numSubPaths=paths[i].length;
            for(int j=0;j<numSubPaths;j++){
                int curveIndex=paths[i][j].id;
                boolean reversed=paths[i][j].reversed;
                if(reversed) curves[curveIndex].reverse();
                Segment2D[] segs=curves[curveIndex].getSegment2Ds();
                for(int k=0;k<segs.length;k++){
                    segments[index]=(Segment2D)segs[k].clone();
                    index++;
                }
            }    
            if(i!=numPaths-1){
                segments[index]=(Segment2D)moveSeg.clone();
                index++;
            }
        }
        this.setData(segments);
        if(debug>0) System.out.println(this.toString());
    }

    public boolean isClosed(){
        boolean closed=false;
        if(this.getNumOfSubPaths()!=1) return false;
        int numseg=this.getNumOfSegments();
        Point2D p0=this.segments[0].getP(0);
        Point2D p1=this.segments[numseg-1].getP(1);
        if(Vector2D.dist(p0, p1)<Curve2D.closedTolerance) closed=true;
        return closed;
    }
//**** getTrimmedCurve2D ****//
    public GeneralCurve2DE getTrimmedCurve2D(double t1, double t2){
        GeneralCurve2DE trimmedCurve=null;
        Vector vector=new Vector();
        int it1=(int)t1;
        int it2=(int)t2;
        if(this.segments[it1].getType()==Segment2D.MOVETO) {
            System.out.println("*** WARNING GeneralCurve2DE getTrimmedCurve2D  t1 is on MOVETO segment t1="+t1);
            it1++;
        }
        if(this.segments[it2].getType()==Segment2D.MOVETO) {
            System.out.println("*** WARNING GeneralCurve2DE getTrimmedCurve2D  t1 is on MOVETO segment t2="+t2);
            it2--;
        }
        for(int i=it1;i<=it2;i++){
            double t01=0;
            double t02=1;
            if(i==it1) t01=t1-it1;
            if(i==it2) t02=t2-it2;
            Segment2D segment=this.getSegment2D(i);
            if(Math.abs(t02-t01)>Curve2D.eps) {
                Segment2D subSegment=segment.trimSegment(t01, t02);
                vector.add(subSegment);
            }
        }
        int size=vector.size();
        if(size==0) return null;
        Segment2D[] segments=new Segment2D[size];
        for(int i=0;i<size;i++) segments[i]=(Segment2D)vector.get(i);
        trimmedCurve=new GeneralCurve2DE(segments);
        return trimmedCurve;
    }
//**** getSimpleCurve2Ds ****//
    public Curve2D[] getSimpleCurve2Ds(){
        GeneralCurve2DE[] gcurves=this.getSubPaths();
        Curve2D[] curves=new Curve2D[gcurves.length];
        for(int i=0;i<gcurves.length;i++){
            curves[i]=gcurves[i].getSimpleCurve2D();
        }
        if(debug>0){
            String str="** GeneralCurve2DE.getSimpleCurve2Ds num="+curves.length;
            for(int i=0;i<curves.length;i++){
                str+="\n SimpleCurve["+i+"]: "+curves[i].toString();
            }
            System.out.println(str);
        }
        return curves;
    }
//**** getSimpleCurve2D ****//
    public Curve2D getSimpleCurve2D(){
        int size=this.getNumOfSegments();
        boolean line=false;
        boolean arc=false;
        boolean cubic=false;
        for(int i=0;i<size;i++) {
            int type=this.getSegment2D(i).getType();
            if(type==Segment2D.LINE) line=true;
            if(type==Segment2D.ARC) arc=true;
            if(type==Segment2D.CUBIC) cubic=true;
        }
        Curve2D simpleCurve=(GeneralCurve2DE)this.clone();
        
        if(line&&!arc&&!cubic&&size==1){
            Segment2D lineSegment=getSegment2D(0);
            lineSegment.getP(0);
            simpleCurve=new Line2DE(lineSegment.getP(0), lineSegment.getP(1));
        }
        if(line&&!arc&&!cubic&&size>1){
            Segment2D[] newSegments=new Segment2D[size];
            for(int i=0;i<size;i++) {
                newSegments[i]=(Segment2D)this.getSegment2D(i).clone();
            }
            simpleCurve=new Polyline2DE(newSegments);
        }
        boolean smooth=true;
        if(cubic&&!line&&!arc){
            Segment2D[] newSegments=new Segment2D[size];
            for(int i=0;i<size;i++) {
                newSegments[i]=(Segment2D)this.getSegment2D(i).clone();
                if(i>0){
                    Vector2D tout=newSegments[i-1].getTangent(1.0);
                    Vector2D tin=newSegments[i].getTangent(0.0);
                    double sprod=Vector2D.sproduct(tin, tout);
                    sprod=sprod/(Vector2D.length(tin)*Vector2D.length(tout));
                    if(smooth&&Math.abs(sprod)<1-1e-5) smooth=false;
                }
            }
            if(smooth) simpleCurve=new CubicCurve2DE(newSegments);
        }
        return simpleCurve;
    }

//Curve2D method
    public Object clone(){
        int size=this.segments.length;
        Segment2D[] newSegments=new Segment2D[size];
        for(int i=0;i<size;i++){
            Segment2D segment=(Segment2D)this.segments[i];
            newSegments[i]=(Segment2D)segment.clone();
        }
        return (new GeneralCurve2DE(newSegments));
    }
//Curve2D method
    public String toString(){
        boolean detailed=true;
        String str="";
        int numPath=this.getNumOfSubPaths();
        if(numPath==1){
            int size=this.segments.length;
            str+="GeneralCurve, segments="+size+", close="+this.closed;
            for(int j=0;j<size;j++){
                Segment2D segment=(Segment2D)this.segments[j];
                str+="\n   segment["+j+"]="+segment.toString();
            }
        } else {
            str+="GeneralCurve, num of subPaths="+numPath;
            GeneralCurve2DE[] curves=this.getSubPaths();
            int[] numLocalSegs=this.getNumOfLocalSegments();
            for(int i=0;i<numPath;i++){
                str+="\n - sub path="+i+", close="+curves[i].isClosed();
                str+=", numseg="+numLocalSegs[i]
                    +", startSegment="+this.getGlobalSegmentPtIndex(i, 0)
                    +", endSegment="+this.getGlobalSegmentPtIndex(i, numLocalSegs[i]-1)
                    +" [ start t-param="+this.getGlobalSegmentPtIndex(i, 0)
                    +", end t-param="+this.getGlobalSegmentPtIndex(i, numLocalSegs[i])+" ]";
                Segment2D[] segments=this.getSubPathSegments(i);
                for(int j=0;j<segments.length;j++){
                    Segment2D segment=segments[j];
                    str+="\n   segment["+j+"]="+segment.toString();
                }
            }
            if(detailed){
                str+="\n - global path list";
                Segment2D[] segments=this.getSegment2Ds();
                for(int i=0;i<this.getNumOfSegments();i++){
                    str+="\n   segment["+i+"]="+segments[i].toString();
                }
/*
                str+="\n  ++ GeneralCurve Segment Mapping: segmentIndex=>(subPathIndex, segmentLocalIndex)...\n    ";
                for(int i=0;i<=this.segments.length;i++){
                    str+=" "+i+"=>("+this.getSubPathIndex(i)+", "+this.getLocalSegmentPtIndex(i)+"),";
                }
*/
            }
        }
        return str;
    }
    
    public void test(){
        String str="\n******** GeneralCurve2DE method test ********\n"+this.toString();
        System.out.println(str);
        int numPath=this.getNumOfSubPaths();
        int numGlobalSegs=this.getNumOfSegments();
        int[] numLocalSegs=this.getNumOfLocalSegments();
        
        str="\n ++ getSubPathIndex, getLocalSegmentPtIndex test\n  ";
        for(int i=0;i<=numGlobalSegs;i++){
            str+=" "+i+"=>("+this.getSubPathIndex(i)+", "+this.getLocalSegmentPtIndex(i)+"),";
        }
        System.out.println(str);
        
        str=" ++ getGlobalSegmentPtIndex test\n";
        for(int i=0;i<numPath;i++){
            str+="  -- subPath="+i+", numLocalseg="+numLocalSegs[i]+"\n    ";
            for(int j=0;j<=numLocalSegs[i];j++){
                str+=" ("+i+","+j+")=>"+this.getGlobalSegmentPtIndex(i, j)+" ";
            }
            str+="\n";
        }
        System.out.println(str);
    }
}
