package shapeUtil;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import DrawTop.*;
import shape.*;
import geomExtension.*;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import util.*;
import DrawTop.*;

public class ConnectionUtil {
    int mode=0;
    MousePositionInfo mousePositionInfo=null;
    ShapeContainer[] selectedContainers=null;
    int auto_tracking_option=DrawParameters.AUTO_TRACKING_OPTION;
    ShapeContainer[] targets=null;
    Vector TargetList=new Vector();
    Vector ConnectionList=new Vector();
    MouseHitShape mouseHitShape=null;
    int movingPtIndex=0;
    public boolean connected=false;
    public boolean guided=false;
    Point2D newPoint=null;
    Point2D oldPoint=null;
    Vector vector=new Vector();
    public static int debug=0;
    
    public ConnectionUtil(){}

    public void start(int mode, MousePositionInfo mousePositionInfo,
            ShapeContainer[] selectedContainers){
        if(!DrawParameters.ENABLE_CONNECTOR) return;
        this.mode=mode;
        if(mode==Command.MOVE) this.mode=Command.MOVING_MODE;
        if(mode==Command.RESIZE) this.mode=Command.RESIZING_MODE;
        if(mode==Command.MOVE_ENDPT) this.mode=Command.MOVING_ENDPT_MODE;
        this.mousePositionInfo=mousePositionInfo;
        this.selectedContainers=selectedContainers;       
      //-----------------------------------------//
        this.setTargets(this.selectedContainers);   
      //-----------------------------------------//   
    }
    public void end(){
        if(!DrawParameters.ENABLE_CONNECTOR) return;
        this.mode=Command.NORMAL_MODE;
        this.clearMark("Connection.pt");
        this.clearMark("Guide.pt");
        this.clearMark("ConnectionMark");
    }
    
     public void mouseDragged(MouseEvent e) {
        if(!DrawParameters.ENABLE_CONNECTOR) return;
        double scale=DrawParameters.getScale();
        double X = e.getX()/scale;
        double Y = e.getY()/scale;
        int key=e.getModifiersEx();
        int ctrl=0;
        if((key&InputEvent.SHIFT_DOWN_MASK)!=0) ctrl=1;
        if((key&InputEvent.CTRL_DOWN_MASK)!=0) ctrl=2;
        if((key&InputEvent.ALT_DOWN_MASK)!=0) ctrl=3;
        this.newPoint=new Point2D.Double(X,Y);

        if(this.mode==Command.MOVING_ENDPT_MODE){
            ConnectionLS connectionLS=ObjectTable.getConnectionLS();
            boolean lineDir=this.selectedContainers[0].getBooleanProperty("Keep_Line_Direction",
                    "ConnectionUtil");
            Curve2D curve=this.selectedContainers[0].getElement().getCurve2D();
            int endIndex=curve.getCloseEndpointIndex(this.newPoint);
            Point2D endPT=curve.getP(endIndex);
            Vector2D vec=curve.getTangent(endIndex);
            CurvePT connectionPT=null;
            if(ctrl>0||lineDir) {
                connectionPT = connectionLS.drawMouseHitPT(1, endPT, vec,
                    this.selectedContainers[0]);
            } else {
                connectionPT = connectionLS.drawMouseHitPT(0, endPT, null,
                    this.selectedContainers[0]);
            }
            if(connectionPT!=null){
                ShapeElement shapeElement=this.selectedContainers[0].getElement();
                Point2D replacedPT=connectionPT.getP();
                shapeElement.moveEndPoint(ctrl, this.movingPtIndex, replacedPT);
                this.connected=true;
            }
        }
        this.resizeConnectors(DrawParameters.AUTO_TRACKING_OPTION);
        this.oldPoint=this.newPoint;
    }

    public void mouseReleased(MouseEvent e) {
        if(!DrawParameters.ENABLE_CONNECTOR) return;
        //this.resizeConnectors(DrawParameters.AUTO_TRACKING_OPTION);

        if(DrawParameters.AUTO_ALIGN){
            if(this.mode==Command.MOVING_ENDPT_MODE){
                if(!this.connected){
                    this.resizeConnectors(DrawParameters.AUTO_TRACKING_OPTION);
                }
            } else {
                this.resizeConnectors(DrawParameters.AUTO_TRACKING_OPTION);
            }
        }

        this.newPoint=null;
        this.end();
    }
    public void setTargets(ShapeContainer[] targets){
        this.TargetList.clear();
        this.ConnectionList.clear();
        this.vector.clear();
        this.targets=targets;
        this.mouseHitShape=null;
        MousePositionInfo info=ObjectTable.getMousePositionLS().getMousePositionInfoForMoveResize();
        if(info!=null){
            if(debug>0) System.out.println("ConnectionUtil.setTargets MousePositionInfo="+info.toString());
            if(info.getPosition()==MousePositionInfo.END_POINT){
                MouseHitShape hitShape=new MouseHitShape(info.getContainer(), info.getPoint());
                this.mouseHitShape=hitShape;
                ShapeContainer targetContainer=info.getContainer();
                this.movingPtIndex=targetContainer.getElement().getCurve2D().getCloseEndpointIndex(info.getPoint());
            }
        }
        int size=0;
        if(targets!=null) size=targets.length;
        for(int i=0;i<size;i++) {
            ShapeContainer[] containers=targets[i].getGroupedSingleShapeContainers();
            for(int j=0;j<containers.length;j++) {
                containers[j].setChangeCode(UndoConstants.SHAPE);
                this.vector.add(containers[j]);
            }
        }
        size=this.vector.size();
        ShapeContainer[] targetContainers=new ShapeContainer[size];
        for(int i=0;i<size;i++) {
            targetContainers[i]=(ShapeContainer)this.vector.get(i);
        }

        this.ConnectionList.clear();
        this.TargetList.clear();

        int depth=0;
        ShapeContainer[] connectors=new ShapeContainer[0];
        while(depth<100) {
          //-----------------------------------------------------//
            connectors=this.setConnectors(depth, targetContainers);
          //-----------------------------------------------------//
            if(connectors.length==0) break;
            targetContainers=connectors;
            depth++;
        }
      //---------------------//
        this.setGuideLines();
      //---------------------//
        this.drawMarks("Connection.pt","");
        if(debug>0){
            printTargerList(" end of the setTarget");
            printConnectionList(" end of the setTarget");
        }
    }


    private ShapeContainer[] setConnectors(int depth, ShapeContainer[] targetContainers){
        ShapeContainer[] connectors=new ShapeContainer[0];
        if(targetContainers.length==0) {
            //System.err.println("*** Error ConnectionUtil.storeConnections:" +
            //        " No target");
            return connectors;
        }
        for(int i=0;i<targetContainers.length;i++){
            int index0=this.getTargetIndex(targetContainers[i], 0);
            int index=this.getTargetIndex(targetContainers[i], depth);
            if(index0>=0) continue;
            if(index>=0) continue;
            TargetContainer targetContainer=new TargetContainer(depth, targetContainers[i]);
            this.TargetList.add(targetContainer);
        }
        this.vector.clear();
        for(int i=0;i<targetContainers.length;i++){
            ShapeContainer target=targetContainers[i];
          //-----------------------------------------------------//  
            Connection[] connections=this.findConnectors(target);
          //-----------------------------------------------------//    
            int size=0;
            if(connections!=null) size=connections.length;
            for(int j=0;j<size;j++) {
                boolean resizable=connections[j].getConnector().getBooleanProperty("Enable_Resizing",
                        "ConnectionUtil");
                boolean compared=connections[j].closeToMouseHitShape(this.mouseHitShape);
                ShapeContainer connector=connections[j].getConnector();
                ConnectionContainer connectionContainer
                                =new ConnectionContainer("connector", depth, connections[j]);
                int connectionIndex=this.getSameConnectionContainer(connectionContainer);
                if(!compared&&connectionIndex<0&&resizable){
                    this.vector.add(connector);
                    this.ConnectionList.add(connectionContainer);
                }
            }
        }
        int size=this.vector.size();
        connectors=new ShapeContainer[size];
        for(int i=0;i<size;i++) connectors[i]=(ShapeContainer)this.vector.get(i);
        return connectors;
    }
    
    private Connection[] findConnectors(ShapeContainer target){
        Vector vector=new Vector();
        if(debug>0) System.out.println("*** ConnectionUtil.findConnectors " +
                        ", target="+target.toShortString());
        if(!target.isConnectorTarget()){
            return null;
        }
        ContainerManager containerManager=ObjectTable.getContainerManager();
        ShapeContainer[] connectors=containerManager.getContainers();
        int size=0;
        if(connectors!=null) size=connectors.length;
        for(int i=0;i<size;i++){
            if(!connectors[i].isConnector()||connectors[i].equals(target)) continue;
            if(!ShapeElementUtil.boxIntersectionCheck(connectors[i].getBoundingBox(),
                    target.getBoundingBox())) continue;

            Point2D[] connectorEndPTs=new Point2D[2];
            Curve2D cCurve=connectors[i].getElement().getCurve2D();
            int numSeg=cCurve.getNumOfSegments();
            connectorEndPTs[0]=cCurve.getP(0);
            connectorEndPTs[1]=cCurve.getP(numSeg);

            Curve2D targetCurve=target.getElement().getCurve2D();
          //-------------------------------------------------------------------------//  
            CurvePT targetPT0=Curve2DUtil.getShortestLine(connectorEndPTs[0], targetCurve);
            CurvePT targetPT1=Curve2DUtil.getShortestLine(connectorEndPTs[1], targetCurve);
          //-------------------------------------------------------------------------// 
            double dist0=Vector2D.dist(connectorEndPTs[0], targetPT0.getP());
            double dist1=Vector2D.dist(connectorEndPTs[1], targetPT1.getP());

            if(dist0<1.0e-2){
                Connection connection=new Connection(target, connectors[i], 
                        targetPT0.getParameter(), 0d);
                vector.add(connection);
                if(debug>0) System.out.println("*** ConnectionUtil.findConnectors, " +
                        " dist0="+dist0+"\n  connection="+connection.toString());
            }
            if(dist1<1.0e-2){
                int numOfSeg=connectors[i].getElement().getCurve2D().getNumOfSegments();
                Connection connection=new Connection(target, connectors[i], 
                        targetPT1.getParameter(), (double)numOfSeg);
                vector.add(connection);
                if(debug>0) System.out.println("*** Connection.getConnectors, " +
                        " dist1="+dist1+"\n  connection="+connection.toString());
            }
        } //for i
        size=vector.size();
        if(size==0) return null;
        Connection[] connections=new Connection[size];
        for(int i=0;i<size;i++) connections[i]=(Connection)vector.get(i);
        return connections;
    }

    private void setGuideLines(){
        int depth=0;
        ShapeContainer[] targetContainers=this.getTargets(depth);
        ContainerManager containerManager=ObjectTable.getContainerManager();
        ShapeContainer[] guideContainers=containerManager.getAllSigleShapeContainers(false);
        for(int i=0;i<targetContainers.length;i++){
            ShapeContainer target=targetContainers[i];
            if(target.getParent()!=null) continue;
            if(!target.isConnector()) continue;
            Point2D[] targetEndPTs=new Point2D[2];
            Curve2D targetCurve=target.getElement().getCurve2D();
            int numSeg=targetCurve.getNumOfSegments();
            targetEndPTs[0]=targetCurve.getP(0);
            targetEndPTs[1]=targetCurve.getP(numSeg);
            int size=0;
            if(guideContainers!=null) size=guideContainers.length;
            for(int j=0;j<size;j++){
                if(guideContainers[j].getContainerType()==ShapeContainer.GROUP) continue;
                if((guideContainers[j].compare(targetContainers))) continue;
                if(!ShapeElementUtil.boxIntersectionCheck(guideContainers[j].getBoundingBox(),
                        target.getBoundingBox())) continue;
                Curve2D guideCurve=guideContainers[j].getElement().getCurve2D();
            //-------------------------------------------------------------------------//  
                CurvePT guideCurvePT0=Curve2DUtil.getShortestLine(targetEndPTs[0], guideCurve);
                CurvePT guideCurvePT1=Curve2DUtil.getShortestLine(targetEndPTs[1], guideCurve);
            //-------------------------------------------------------------------------// 
                double dist0=Vector2D.dist(targetEndPTs[0], guideCurvePT0.getP());
                double dist1=Vector2D.dist(targetEndPTs[1], guideCurvePT1.getP());

                if(dist0<1.0e-2){
                    double distE0=guideCurvePT0.getDistanceToCurveEndPT(0);
                    double distE1=guideCurvePT0.getDistanceToCurveEndPT(1);
                    if(distE0>1.0e-2&&distE1>1.0e-2){
                        Connection connection=new Connection(target, guideContainers[j], 0d,
                            guideCurvePT0.getParameter());
                        ConnectionContainer connectionContainer
                                =new ConnectionContainer("guide_line", depth, connection);
                        this.ConnectionList.add(connectionContainer);
                    }
                }
                if(dist1<1.0e-2){
                    double distE0=guideCurvePT1.getDistanceToCurveEndPT(0);
                    double distE1=guideCurvePT1.getDistanceToCurveEndPT(1);
                    if(distE0>1.0e-2&&distE1>1.0e-2){
                        int numOfSeg=target.getElement().getCurve2D().getNumOfSegments();
                        //int numOfSeg=guideContainers[j].getElement().getCurve2D().getNumOfSegments();
                        Connection connection=new Connection(target, guideContainers[j], 
                            (double)numOfSeg, guideCurvePT1.getParameter());
                        ConnectionContainer connectionContainer
                                =new ConnectionContainer("guide_line", depth, connection);
                        this.ConnectionList.add(connectionContainer);
                    }
                }
            } //for j
        }
    }

    private void printTargerList(String message){
        System.out.println("* TargetList  size="+this.TargetList.size()+", message="+message);
        for(int i=0;i<this.TargetList.size();i++){
            TargetContainer target=(TargetContainer)this.TargetList.get(i);
            System.out.println(" -- target="+target.getContainer().getShapeId()+
                    ", depth="+target.getDepth());
        }
    }

    private void printConnectionList(String message){
        System.out.println("* ConnectiontList  size="+this.ConnectionList.size()+
                ", message="+message);
        for(int i=0;i<this.ConnectionList.size();i++){
            ConnectionContainer connectionContainer
                    =(ConnectionContainer)this.ConnectionList.get(i);
            String infoStr="not set";
            if(connectionContainer!=null) 
                infoStr="id="+connectionContainer.getId()+
                        ", depth="+connectionContainer.getDepth();
            System.out.println(" -- connection["+i+"]: info: "+infoStr+",  "+
                    connectionContainer.getConnection().toString());
        }
    }

    private ShapeContainer[] getTargets(int depth){
        Vector vector=new Vector();
        int size=this.TargetList.size();
        for(int i=0;i<size;i++){
            TargetContainer TargetContainer=(TargetContainer)this.TargetList.get(i);
            if(TargetContainer.getDepth()==depth) vector.add(TargetContainer.getContainer());
        }
        ShapeContainer[] targets=new ShapeContainer[vector.size()];
        for(int i=0;i<vector.size();i++) targets[i]=(ShapeContainer)vector.get(i);
        return targets;
    }

    private int getTargetIndex(ShapeContainer target, int depth){
        int index=-1;
        String targetId=target.getShapeId();
        for(int i=0;i<this.TargetList.size();i++){
            TargetContainer TargetContainer=(TargetContainer)this.TargetList.get(i);
            int dep=TargetContainer.getDepth();
            if(depth>=0&&dep!=depth) continue;
            if(TargetContainer.getContainer().getShapeId().equals(targetId)){
                index=i;
                break;
            }
        }
        return index;
    }

    private ConnectionContainer[] getConnectionContainers(String id, int depth, 
            ShapeContainer target, ShapeContainer connector){
        this.vector.clear();
        for(int i=0;i<this.ConnectionList.size();i++){
            ConnectionContainer connectionContainer
                    =(ConnectionContainer)this.ConnectionList.get(i);
            Connection connection=connectionContainer.getConnection();
            if(!connectionContainer.getId().equals(id)) continue;
            if(depth>=0&&depth!=connectionContainer.getDepth()) continue;
            
            if(target!=null&&connector!=null){
                if(target.compare(connection.getTarget())&&
                   connector.compare(connection.getConnector()))
                    this.vector.add(connectionContainer);
            }
            if(target!=null&&connector==null){
                if(target.compare(connection.getTarget()))
                    this.vector.add(connectionContainer);
            }
            if(target==null&&connector!=null){
                if(connector.compare(connection.getConnector()))
                    this.vector.add(connectionContainer);
            }
        }
        ConnectionContainer[] connectionContainers=new ConnectionContainer[this.vector.size()];
        for(int i=0;i<this.vector.size();i++) connectionContainers[i]=(ConnectionContainer)this.vector.get(i);
        return connectionContainers;
    }

    private ConnectionContainer[] getConnectionContainers(String id, ShapeContainer target,
            ShapeContainer connector){
        return getConnectionContainers(id, -1, target, connector);
    }

    private int getSameConnectionContainer(ConnectionContainer connectionContainer){
        String id=connectionContainer.getId();
        Connection connection=connectionContainer.getConnection();
        int index=-1;
        int size=this.ConnectionList.size();
        for(int i=0;i<size;i++){
            ConnectionContainer testCoContainer=(ConnectionContainer)this.ConnectionList.get(i);
            Connection testConnection=testCoContainer.getConnection();
            if(!testCoContainer.getId().equals(id)) continue;
            if(connection.equalsInNormalData(testConnection)||
               connection.equalsInReversedData(testConnection)){
                index=i;
                break;
            }
        }
        return index;
    }

    private ShapeContainer[] getConnectors(String id){
        this.vector.clear();
        int size=this.ConnectionList.size();
        for(int i=0;i<size;i++){
            ConnectionContainer connectionContainer=(ConnectionContainer)this.ConnectionList.get(i);
            if(!id.equals(connectionContainer.getId())) continue;
            ShapeContainer connector=connectionContainer.getConnection().getConnector();
            int index=this.vector.indexOf(connector);
            if(index<0) this.vector.add(connector);
        }
        size=this.vector.size();
        ShapeContainer[] connectors=new ShapeContainer[size];
        for(int i=0;i<size;i++) connectors[i]=(ShapeContainer)this.vector.get(i);
        return connectors;
    }
    
    public ShapeContainer[] getTargetsAndConnectors(){
        int nTargets=this.targets.length;
        ShapeContainer[] connectors=this.getConnectors("connector");
        ShapeContainer[] containers=new ShapeContainer[nTargets+connectors.length];
        for(int i=0;i<nTargets;i++) containers[i]=this.targets[i];
        for(int i=0;i<connectors.length;i++) containers[i+nTargets]=connectors[i];
        if(debug>0){
            System.out.println("getTargetsAndConnectors");
            for(int i=0;i<containers.length;i++) {
                System.out.println(" - container="+containers[i].getShapeId());
            }
        }
        return containers;
    }

    public void resizeConnectors(int option){
        if(this.ConnectionList.size()==0) return;
        this.clearMark("Connection.pt");
        int depth=1;
        while(depth<100){
            ShapeContainer[] connectors=this.getTargets(depth);
            if(connectors.length==0) break;
            this.resizeConnectors(option, depth, connectors);
            depth++;
        }
    }
    
    private void resizeConnectors(int option, int depth, ShapeContainer[] connectors){
        if(debug>0) System.out.println("resizeConnectors  depth="+depth+", option="+option);
        for(int i=0;i<connectors.length;i++){
            ConnectionContainer[] connectionContainers
                    =this.getConnectionContainers("connector", depth-1, null, connectors[i]);
            int size=0;
            if(connectionContainers!=null) size=connectionContainers.length;
            for(int j=0;j<size;j++){
                Connection connection=connectionContainers[j].getConnection();
                Vector2D unitDir=connectionContainers[j].getInitialConnection().getConnectorTangent();
                unitDir=Vector2D.unitVector(unitDir);
                ShapeContainer connector=connection.getConnector();
                boolean lineDir=connector.getBooleanProperty("Keep_Line_Direction","ConnectionUtil");
              //--------------------------------------------// 
                boolean connected=true;
                if(option==Command.FREE_DIRECTION&&!lineDir){
                    this.resizeConnectorByCurveParameter(connection);
                }

                if(option==Command.KEEP_XY_DIRECTION&&!lineDir) {
                    if(this.isXOrYvector(unitDir)>0){
                        this.resizeConnectorKeepingDirection(connectionContainers[j]);
                        if(debug>0) System.out.println("resizeConnectorKeepingDirection depth="
                                +depth+", option="+option);
                    } else {
                        this.resizeConnectorByCurveParameter(connection);
                    }
                }
                if(option==Command.KEEP_DIRECTION||lineDir) {
                    connected=this.resizeConnectorKeepingDirAndPos(connectionContainers[j]);
                    if(debug>0) System.out.println("resizeConnectorKeepingDirection depth="
                            +depth+", option="+option);
                }
                if(option==Command.TO_NEAREST_PT&&!lineDir) {
                    this.resizeConnectorToNearestPT(connection);
                }
              //---------------------------------------------//    
                if(connected) this.drawMark("Connection.pt", "", depth-1, connection);
            }
        }
    }

    public void resizeConnectorByCurveParameter(Connection connection){
        Curve2D curve=connection.getTarget().getElement().getCurve2D();
        Point2D connectionPT=curve.getP(connection.targetParameter);
        Point2D connectorPT=connection.getConnectorPT();
        connection.resizeConnector(connectorPT, connectionPT);
    }
   
    public void resizeConnectorToNearestPT(Connection connection){
        CurvePT[] targetPTs=
           connection.getTarget().getElement().getCurve2D().getSamplingCurvePTs
                (DrawParameters.Sampling_Pitch);
        Point2D connectorPT=connection.getConnectorPT();
        Vector2D p0=new Vector2D(connectorPT);
        int index=-1;
        double distMin=1e+5;
        for(int i=0;i<targetPTs.length;i++){
            double dist=Vector2D.dist(p0, new Vector2D(targetPTs[i].getP()));
            if(dist<distMin){
                distMin=dist;
                index=i;
            }
        }
        if(index<0) return;
        connection.resizeConnector(connectorPT, targetPTs[index].getP());
        connection.targetParameter=targetPTs[index].getParameter();
    }

    private void resizeConnectorKeepingDirection(ConnectionContainer connectionContainer){
        Connection initConnection=connectionContainer.getInitialConnection();
        int initType=initConnection.getConnector().getElement().getTypeE();
        Connection connection=connectionContainer.getConnection();
        int currentType=connection.getConnector().getElement().getTypeE();
        Curve2D curve=connection.getConnector().getElement().getCurve2D();
        int numseg=curve.getNumOfSegments();
        double T=connection.getConnectorParameter();
        double oppositeT=numseg-T;
      //Opposite endpoint to the connection point
        Point2D oppositeP=curve.getP(oppositeT);
        Vector2D unitDir=connectionContainer.getInitialConnection().getConnectorTangent();
        unitDir=Vector2D.unitVector(unitDir);
        if(T==0.0) unitDir=Vector2D.multiply(-1.0, unitDir);
        Curve2D targetCurve=connection.getTarget().getElement().getCurve2D();
      //---------------------------------------------------------------// 
        CurvePT[] curvePTs=this.projectionPT(oppositeP, unitDir, targetCurve);
        CurvePT curvePT=this.selectCurvePT(connectionContainer, curvePTs);
      //---------------------------------------------------------------//  
        int id=0;
        if(initType==Command.LINE&&currentType==Command.LINE) id=0;
        if(initType==Command.LINE&&currentType==Command.POLYLINE) id=1;
        if(initType==Command.POLYLINE&&currentType==Command.LINE) id=2;
        if(initType==Command.POLYLINE&&currentType==Command.POLYLINE) id=3;
             switch(id){
            //Line -> Line
            case 0: {
                if(curvePT==null) {
                    if(this.isChangable(connection.getConnector())){
                        this.changeToPolylineConnector(connectionContainer);
                    }
                } else{
                    Point2D connectorPT=connection.getConnectorPT();
                    connection.targetParameter=curvePT.getParameter();
                    connection.resizeConnector(connectorPT, curvePT.getP());
                }
                break;
            }
            //Line -> PolyLine
            case 1: {
                if(curvePT==null||!this.isChangable(connection.getConnector())) {
                    this.resizeConnectorByCurveParameter(connection);
                } else{
                    this.changeToLineConnector(connectionContainer, curvePT);
                }
                break;
            }
            //Polyline -> Line
            case 2: {
                if(curvePT==null) {
                    if(this.isChangable(connection.getConnector())&&
                      this.isChangable(initConnection.getConnector())){
                        this.changeToPolylineConnector(connectionContainer);
                    }
                } else{
                    Point2D connectorPT=connection.getConnectorPT();
                    connection.resizeConnector(connectorPT, curvePT.getP());
                    connection.targetParameter=curvePT.getParameter();
                }
                break;
            }
            //Polyline -> Polyline
            case 3: {
                this.resizeConnectorByCurveParameter(connection);
                if(curvePT!=null){
                    if(this.isChangable(connection.getConnector())&&
                       this.isChangable(initConnection.getConnector())){
                        this.changeToLineConnector(connectionContainer, curvePT);
                    }
                }
                break;
            }
        }
        if(debug>0&&connection.getErrorDistance()>1.0e-10) 
            System.out.println("resizeConnectorKeepingDirection" +
                        ", connection="+connection.toString());
        return;
    }

    private boolean resizeConnectorKeepingDirAndPos(ConnectionContainer connectionContainer){
        Connection initConnection=connectionContainer.getInitialConnection();
        int initType=initConnection.getConnector().getElement().getTypeE();
        Connection connection=connectionContainer.getConnection();
        int currentType=connection.getConnector().getElement().getTypeE();
        Curve2D curve=connection.getConnector().getElement().getCurve2D();
        int numseg=curve.getNumOfSegments();
        double T=connection.getConnectorParameter();
        Point2D oppositeP=curve.getP(T);
        Vector2D unitDir=connectionContainer.getInitialConnection().getConnectorTangent();
        unitDir=Vector2D.unitVector(unitDir);
        if(T==0.0) unitDir=Vector2D.multiply(-1.0, unitDir);
        Curve2D targetCurve=connection.getTarget().getElement().getCurve2D();
      //-------------------------------------------------------------------//
        CurvePT[] curvePTs=this.projectionPT(oppositeP, unitDir, targetCurve);
        CurvePT curvePT=this.selectCurvePT(connectionContainer, curvePTs);
      //-------------------------------------------------------------------//
        if(curvePT!=null){
            Point2D connectorPT = connection.getConnectorPT();
            connection.targetParameter = curvePT.getParameter();
            connection.resizeConnector(connectorPT, curvePT.getP());
        }
        if(debug>0&&connection.getErrorDistance()>1.0e-10)
            System.out.println("resizeConnectorKeepingDirection" +
                        ", connection="+connection.toString());
        boolean connected=true;
        if(curvePT==null) connected=false;
        return connected;
    }

    private CurvePT[] projectionPT(Point2D p, Vector2D vec, Curve2D targetCurve){
        double ext=0.1;
        Curve2D target=targetCurve;
        Curve2D extCurve=targetCurve.getExtendedCurve2DE(ext);
        if(extCurve!=null) {
            target=extCurve;
            if(debug>0) System.out.println(" -- projectionPT extendedCurve="+target.toString());
        }
      //--------------------------------------------------------------//    
        CurvePT[] curvePTs=Curve2DUtil.getProjectionLines(p, vec, target);    
      //--------------------------------------------------------------//  
        if(curvePTs==null||curvePTs.length==0) {
            if(debug>0) System.out.println(" -- IntersectionPT not found by projection p="+Util.Pt(p)+
                    ", vec="+Util.Pt(vec)+"\n   target="+target.toString());
            curvePTs=new CurvePT[0];
            return curvePTs;
        }
        if(extCurve!=null){
            for(int i=0;i<curvePTs.length;i++){
                double t=curvePTs[i].getParameter();
                int numseg=targetCurve.getNumOfSegments();
                t=t-1d;
                if(t<0.0) t=0;
                if(t>numseg) t=numseg;
                curvePTs[i]=new CurvePT(t, targetCurve.getP(t));
            }
        }
        if(debug>0) {
            for(int i=0;i<curvePTs.length;i++) 
                System.out.println(" -- ProjectionLine found by projection curvePT="+
                    curvePTs[i].toString());
        }       
        return curvePTs;
    }
    
    private CurvePT selectCurvePT(ConnectionContainer connectionContainer, 
             CurvePT[] curvePTs){
        Connection connection=connectionContainer.getConnection();
        Curve2D connectorCurve=connection.getConnector().getElement().getCurve2D();
        int numseg=connectorCurve.getNumOfSegments();
        double T=connection.getConnectorParameter();
        double t=1.0;
        if(T!=0.0){
            t=(double)numseg-1.0;
        }
        //double t1=numseg-connection.getConnectorParameter();
        Point2D P=connectorCurve.getP(t);
        Vector2D unitDir=connectionContainer.getInitialConnection().getConnectorTangent();
        unitDir=Vector2D.unitVector(unitDir);
        if(T==0.0) unitDir=Vector2D.multiply(-1.0, unitDir);
        //System.out.println("selectCurvePT t="+t1+", ConnectorParameter="
        //        +connection.getConnectorParameter()+", initDir="+Util.Pt(unitDir));
        CurvePT curvePT=null;
        if(curvePTs.length==1) return curvePTs[0];
        double distMin=1.0e+4;
        int index=-1;
        Point2D p=connection.getConnectorPT();
        for(int i=0;i<curvePTs.length;i++){
            double scal=Vector2D.sproduct(unitDir, Vector2D.sub(curvePTs[i].getP(), P));
            //if(scal<0.0) continue;
            Vector2D dirVec=Vector2D.sub(curvePTs[i].getP(), p);
            double dist=Math.abs(Vector2D.sproduct(dirVec, unitDir));
            if(dist<distMin){
                distMin=dist;
                index=i;
            }
        }
        if(index>=0) curvePT=curvePTs[index];
        return curvePT;
    }

    private boolean isChangable(ShapeContainer connector){
        if(connector==null) return false;
        boolean changable=true;
        double eps=1.0e-2;
        int type=connector.getElement().getTypeE();
        Curve2D curve=connector.getElement().getCurve2D();
        if(type==Command.LINE){
            Vector2D tangent=curve.getTangent(0.0);
            tangent=Vector2D.unitVector(tangent);
            if(tangent.getX()>eps&&tangent.getY()>eps) changable=false;
        }
        if(type==Command.POLYLINE){
            int numseg=curve.getNumOfSegments();
            if(numseg!=3) changable=false;
            for(int i=0;i<numseg;i++){
                Vector2D tangent=curve.getTangent((double)i);
                tangent=Vector2D.unitVector(tangent);
                if(tangent.getX()>eps&&tangent.getY()>eps) changable=false;
            }
            String original_type=Command.getCommandString(Command.ORIGINAL_TYPE);
            String originalType=(String)connector.getProperty(original_type);
            if(originalType==null){
                System.err.println("isChangable key=originalType value=null");
            }
            else {
                if(originalType.equals("Polyline")) changable = false;
            }
        }
        return changable;
    }
    
    private void changeToPolylineConnector(ConnectionContainer connectionContainer){
        Connection connection=connectionContainer.getConnection();
        Connection initConnection=connectionContainer.getInitialConnection();
        Vector2D unitDir=connectionContainer.getInitialConnection().getConnectorTangent();
        unitDir=Vector2D.unitVector(unitDir);
        ShapeContainer connector=connection.getConnector();
        Point2D[] endPTs=connector.getElement().getEndPTs();
        connection.targetParameter=initConnection.targetParameter;
        Point2D targetPT=connection.getTargetPT();
        if(connection.getConnectorParameter()==0.0) endPTs[0]=targetPT;
        else endPTs[1]=targetPT;
        if(connection.getConnectorParameter()>0.0) connection.connectorParameter=3.0;
        ConnectionContainer[] containers
                    =this.getConnectionContainers("connector", null, connection.connector);
        for(int i=0;i<containers.length;i++){
            Connection connectionX=containers[i].getConnection();
            if(connectionX.getConnectorParameter()>0.0)
                connectionX.connectorParameter=3.0;
        }
        containers=this.getConnectionContainers("connector", connection.connector, null);
        for(int i=0;i<containers.length;i++){
            Connection connectionX=containers[i].getConnection();
            connectionX.targetParameter=connectionX.getTargetParameter()*3.0;
        }
        Vector2D p0=new Vector2D(endPTs[0]);
        Vector2D p3=new Vector2D(endPTs[1]);
        Vector2D vec=Vector2D.sub(endPTs[1], endPTs[0]);
        double len=Vector2D.sproduct(unitDir, vec);
        Vector2D vech=Vector2D.multiply(0.5*len, unitDir);
        Vector2D p1=Vector2D.add(p0, vech);
        Vector2D p2=Vector2D.sub(p3, vech);
        Point2D[] nodePTs=new Point2D[4];
        nodePTs[0]=new Point2D.Double(p0.getX(), p0.getY());
        nodePTs[1]=new Point2D.Double(p1.getX(), p1.getY());
        nodePTs[2]=new Point2D.Double(p2.getX(), p2.getY());
        nodePTs[3]=new Point2D.Double(p3.getX(), p3.getY());
        
        ShapeElement newElement=new PolylineElement();
        connector.setElement(newElement);
        newElement.setShapeContainer(connector);
        newElement.create(nodePTs);
        //this.updateConnectionList();
        if(debug>0) System.out.println("changeToPolylineConnector  unitDir="+Util.Pt(unitDir)+
                ", newEndPTs[0]="+Util.Pt(endPTs[0])+", newEndPTs[1]="+Util.Pt(endPTs[1])+
                "\n  connection="+connection.toString()+
                "\n  initConnection="+initConnection.toString());
        return;
    }

    private void changeToLineConnector(ConnectionContainer connectionContainer, 
            CurvePT curvePT){
        Connection initConnection=connectionContainer.getInitialConnection();
        Connection connection=connectionContainer.getConnection();
        Vector2D unitDir=connectionContainer.getInitialConnection().getConnectorTangent();
        unitDir=Vector2D.unitVector(unitDir);
        ShapeContainer connector=connection.getConnector();
        ShapeElement element=connector.getElement();
        //int type=element.getTypeE();
        Point2D[] endPTs=element.getEndPTs();
        if(connection.getConnectorParameter()==0.0) endPTs[0]=curvePT.getP();
        else endPTs[1]=curvePT.getP();
        ShapeElement newElement=new LineElement();
        connector.setElement(newElement);
        newElement.setShapeContainer(connector);
        newElement.create(endPTs[0], endPTs[1]);
        if(connection.getConnectorParameter()>0.0) connection.connectorParameter=1.0;
        connection.targetParameter=curvePT.getParameter();
        
        ConnectionContainer[] containers
                    =this.getConnectionContainers("connector", null, connection.connector);
        for(int i=0;i<containers.length;i++){
            Connection connectionX=containers[i].getConnection();
            if(connectionX.getConnectorParameter()>0.0)
                connectionX.connectorParameter=1.0;
        }
        containers=this.getConnectionContainers("connector", connection.connector, null);
        for(int i=0;i<containers.length;i++){
            Connection connectionX=containers[i].getConnection();
            connectionX.targetParameter=connectionX.getTargetParameter()/3.0;
        }
        if(debug>0) System.out.println("changeToLineConnector  unitDir="+Util.Pt(unitDir)+
                "\n  connector:"+connector.toShortString()+
                "\n  connection="+connection.toString()+
                "\n  initConnection="+initConnection.toString());
        return;
    }

    public boolean resizeTargetsByGuideLines(){
        this.clearMark("Guide.pt");
        int depth=0;
        ShapeContainer[] targets=this.getTargets(depth);
        if(targets.length==0) System.out.println("resizeTargetsByGuideLines targets null");
        boolean guided=false;
        for(int i=0;i<targets.length;i++){
            if(!targets[i].isConnector()) continue;
            ConnectionContainer[] connectionContainers
                    =this.getConnectionContainers("guide_line", 0, targets[i], null);
            for(int j=0;j<connectionContainers.length;j++){
                Connection connection=connectionContainers[j].getConnection();
                boolean resized=this.resizeTargetKeepingDirection(connectionContainers[j]);
                if(resized) {
                    this.drawMark("Guide.pt", "",0, connection);
                    guided=true;
                }
                if(debug>0) System.out.println("resizeTargetsByGuideLines target["+i+"]="+
                        targets[i].getShapeId()+", connection["+j+"]="+connection.toString());
            }
        }
        return guided;
    }
    
    private boolean resizeTargetKeepingDirection(ConnectionContainer connectionContainer){
        Connection connection=connectionContainer.getConnection();
        Vector2D unitDir=connectionContainer.getInitialConnection().getTargetTangent();
        unitDir=Vector2D.unitVector(unitDir);
        Curve2D cCurve=connection.getConnector().getElement().getCurve2D();
      //----------------------------------------------------------------------------------// 
        CurvePT[] curvePTs=this.projectionPT(connection.getTargetPT(), unitDir, cCurve);
        CurvePT curvePT=this.selectCurvePT(connection.getTargetPT(), unitDir, curvePTs);
      //----------------------------------------------------------------------------------// 
        if(curvePT==null) return false;
        
        connection.resizeTarget(connection.getTargetPT(), curvePT.getP());
        connection.connectorParameter=curvePT.getParameter();
        return true;
    }
    
    private CurvePT selectCurvePT(Point2D p, Vector2D vec, CurvePT[] curvePTs){
        CurvePT curvePT=null;
        if(curvePTs.length==1) return curvePTs[0];
        double distMin=1.0e+4;
        int index=-1;
        for(int i=0;i<curvePTs.length;i++){
            Vector2D dirVec=Vector2D.sub(curvePTs[i].getP(), p);
            double dist=Math.abs(Vector2D.sproduct(dirVec, vec));
            if(dist<distMin){
                distMin=dist;
                index=i;
            }
        }
        if(index>=0) {
            curvePT=curvePTs[index];
        }
        return curvePT;
    }


    private int isXOrYvector(Vector2D vec){
        int dir=0;
        if(Math.abs(vec.getY())<1.0e-3) dir=1;
        if(Math.abs(vec.getX())<1.0e-3) dir=2;
        return dir;
    }

    private void drawMark(String id, String message, int depth, Connection connection){
        Color[] colors={Color.RED, Color.MAGENTA, Color.ORANGE, Color.GREEN, Color.BLUE};
        Point2D point=connection.getTargetPT();
        depth=depth-(int)(depth/colors.length)*depth;
        DrawShapeUtil.drawTempShape(id, point, DrawParameters.Mark_SmallSize, message, colors[depth]);
        if(debug>0) System.out.println(" - drawMark depth="+depth);
    }
    
    private void drawMarks(String id, String message){
        if(debug>0) System.out.println("drawMarks id="+id+", message="+message);
        for(int i=0;i<this.ConnectionList.size();i++){
            ConnectionContainer connectionContainer=(ConnectionContainer)this.ConnectionList.get(i);
            int depth=connectionContainer.getDepth();
            this.drawMark(id, message, depth, connectionContainer.getConnection());
        }
    }

    private void clearMark(String id){
        DrawPanel drawPanel=ObjectTable.getDrawPanel("");
      //----------------------------------------------// 
        DrawShapeUtil.clearTempShape(id);
        drawPanel.repaint("ConnectionUtil.clearMark");
      //-----------------------------------------------// 
        return;
    }
    
    public static void moveResize(ShapeContainer container, Rectangle2D oldBox,
            Rectangle2D newBox, boolean keepConnection, boolean undoSetting){
        ShapeContainer[] targetContainers=new ShapeContainer[1];
        targetContainers[0]=container;
        ConnectionUtil connectionUtil=new ConnectionUtil();
        connectionUtil.setTargets(targetContainers); 
        ShapeContainer[] containers=connectionUtil.getTargetsAndConnectors();
        for(int i=0;i<containers.length;i++) containers[i].setChangeCode(i);
        ContainerManager containerManager=ObjectTable.getContainerManager("");
        boolean result=ConnectionUtil.resizeCheck(container, oldBox, newBox);
      //----------------------------------------------------------//   
        if(undoSetting) containerManager.undoSetupStart(containers);
      //----------------------------------------------------- ----//  
        container.getElement().moveResize(oldBox, newBox, true);
        int connectorResizeOption=DrawParameters.AUTO_TRACKING_OPTION;
        if(keepConnection) connectionUtil.resizeConnectors(connectorResizeOption);  
        connectionUtil.end();
      //------------------------------------------------//   
        if(undoSetting) containerManager.undoSetupEnd();
      //------------------------------------------------//   
    }

    private static boolean resizeCheck(ShapeContainer container, Rectangle2D oldBox,
            Rectangle2D newBox) {
        boolean result=true;
        int type=container.getElement().getTypeE();
        boolean resizable=container.getBooleanProperty("Enable_Resizing","ConnectionUtil");
        boolean aspectRatio=container.getBooleanProperty("Keep_Aspect_Ratio", "ConnectionUtil");
        boolean lineDir=container.getBooleanProperty("Keep_Line_Direction","ConnectionUtil");
        double oldRatio=oldBox.getHeight()/oldBox.getWidth();
        double newRatio=newBox.getHeight()/newBox.getWidth();
        if(aspectRatio){
            if(Math.abs(oldRatio-newRatio)>1.0e-4){
                System.err.println("Warning check for ConnectionUtil.resizeContainer:"
                        + " illegal aspect ratio  old(h/w)="+oldRatio+", new(h/w)="+newRatio+
                        ", shapeId="+container.getShapeId());
                result=false;
            }
        }
        if(lineDir){
            if(Math.abs(oldRatio-newRatio)>1.0e-4){
                System.err.println("Warning check for ConnectionUtil.resizeContainer:"
                        + " illegal line direction  old(h/w)="+oldRatio+", new(h/w)="+newRatio+
                        ", shapeId="+container.getShapeId());
                result=false;
            }
        }
        return result;
    }

    public static void moveEndPoint(ShapeContainer container, int movePtIndex,
            Point2D oldPoint, Point2D newPoint, boolean keepConnection, boolean undoSetting){
        ShapeContainer[] targetContainers=new ShapeContainer[1];
        targetContainers[0]=container;
        ConnectionUtil connectionUtil=new ConnectionUtil();
        connectionUtil.setTargets(targetContainers);
        ShapeContainer[] containers=connectionUtil.getTargetsAndConnectors();
        for(int i=0;i<containers.length;i++) containers[i].setChangeCode(i);
        ContainerManager containerManager=ObjectTable.getContainerManager("");
      //----------------------------------------------------------//
        if(undoSetting) containerManager.undoSetupStart(containers);
      //----------------------------------------------------- ----//
        container.getElement().moveEndPoint(0, movePtIndex, oldPoint, newPoint);
        int connectorResizeOption=DrawParameters.AUTO_TRACKING_OPTION;
        if(keepConnection) connectionUtil.resizeConnectors(connectorResizeOption);
        connectionUtil.end();
      //------------------------------------------------//
        if(undoSetting) containerManager.undoSetupEnd();
      //------------------------------------------------//
    }
} // End of class

class TargetContainer{
    int depth=-1;
    ShapeContainer container=null;
    
    public TargetContainer(int depth, ShapeContainer container){
        this.depth=depth;
        this.container=container;
    }
    
    public int getDepth(){
        return this.depth;
    }
    
    public ShapeContainer getContainer(){
        return this.container;
    } 
}
