var connector_defaultConnectorTextSize = 12;
var connector_defaultConnectorTextStr = "";
var connector_defaultConnectorTextFont = "Arial";
var connector_defaultConnectorTextStrokeStyle = "#000000";
var connector_defaultConnectorTextFillStyle = "#000000";
var connector_defaultConnectorTextBgStyle = "#ffffff";

/**
*It's a connector between 2 figureConnectionPoints.
*
*@constructor
*@this {Connector}
*@param {Point} startPoint - the start of the line, where a ConnectionPoint will be added
*@param {Point} endPoint - the end of the line, where a ConnectionPoint will be added
*@param {String} type - the type of the Connector. It can be 'straight' or 'jagged'
*@param {Number} id - the unique (at least among other Connectors) id this connector will have
*@author Zack Newsham <zack_newsham@yahoo.co.uk>
*@author Alex Gheorghiu <alex@scriptoid.com>
*/
function Connector(startPoint, endPoint, type, id) {
    /**Connector's id*/
    this.id = id;

    /**An {Array} of {Point}s. They will be used to draw the connector*/
    this.turningPoints = [startPoint, endPoint];

    /**Type of connector. Ex. TYPE_STRAIGHT*/
    this.type = type;

    /**An {Array} of {Object}s. Stores set of user manual changes to connector's shape.
    * Structure of instance:
    * - align: 'v' for vertical and 'h' for horizontal
    * - delta: user defined offset from default position
    * - index: index of turning point which is changed
    * Note: the changes are not store in the order of the turning points, but
    * the correspondence is made by 'index' field
    * */
    this.userChanges = [];

    /**Solution of connector's shape calculated with ConnectionManager.connector2Points.
    * It can be one of: 's0', 's1_1', 's2_2', etc. */
    this.solution = '';

    /**The {Style} this connector will have*/
    this.style = new Style();
    this.style.strokeStyle = "#b0b0b4";
    this.style.lineWidth = 2;   // fixes connector's display in Chrome for now
    this.style.lineStyle = Style.LINE_STYLE_CONTINOUS;

    /**The text that will appear in the middle of the connector*/
    this.middleText = new Text(connector_defaultConnectorTextStr, (startPoint.x + endPoint.x) / 2 + 10, (startPoint.y + endPoint.y) / 2 - 13, connector_defaultConnectorTextFont, connector_defaultConnectorTextSize);
    this.middleText.style.strokeStyle = connector_defaultConnectorTextStrokeStyle;
    this.middleText.style.fillStyle = connector_defaultConnectorTextFillStyle;
    this.middleText.bgStyle = connector_defaultConnectorTextBgStyle;

    /**An {Array} of {BuilderProperties} to store exposed properties of the connector*/
    this.properties = [];

    this.properties.push(new BuilderProperty('线属性设置', 'group', BuilderProperty.TYPE_GROUP_LABEL));
    this.properties.push(new BuilderProperty(BuilderProperty.SEPARATOR));
    //this.properties.push(new BuilderProperty("Start Style", "startStyle", BuilderProperty.TYPE_CONNECTOR_END));
    //this.properties.push(new BuilderProperty("End Style", "endStyle",  BuilderProperty.TYPE_CONNECTOR_END));
    this.properties.push(new BuilderProperty('粗细', 'style.lineWidth', BuilderProperty.TYPE_LINE_WIDTH));
    //this.properties.push(new BuilderProperty('Line Style','style.lineStyle', BuilderProperty.TYPE_LINE_STYLE));
    this.properties.push(new BuilderProperty('颜色', 'style.strokeStyle', BuilderProperty.TYPE_COLOR));
    //this.properties.push(new BuilderProperty('Text','middleText.str', BuilderProperty.TYPE_TEXT));
    //this.properties.push(new BuilderProperty('Text Size', 'middleText.size', BuilderProperty.TYPE_TEXT_FONT_SIZE));
    //this.properties.push(new BuilderProperty('Font', 'middleText.font', BuilderProperty.TYPE_TEXT_FONT_FAMILY));
    //this.properties.push(new BuilderProperty('Alignment', 'middleText.align', BuilderProperty.TYPE_TEXT_FONT_ALIGNMENT));
    //this.properties.push(new BuilderProperty('Text Underlined', 'middleText.underlined', BuilderProperty.TYPE_TEXT_UNDERLINED));
    //this.properties.push(new BuilderProperty('Text Color', 'middleText.style.fillStyle', BuilderProperty.TYPE_COLOR));

    /**Start style for connector. Ex: Connector.STYLE_NORMAL*/
    this.startStyle = Connector.STYLE_NORMAL;

    /**End style for connector. Ex: Connector.STYLE_FILLED_TRIANGLE*/
    this.endStyle = Connector.STYLE_NORMAL;

    /**The {ConnectionPoint}'s id that is currently being dragged*/
    this.activeConnectionPointId = -1;

    /**Serialization type*/
    this.oType = 'Connector'; //object type used for JSON deserialization
    /**Object CCForm_Shape used for ccflow */
    this.CCForm_Shape = "Connector";
    /**Object CCForm_MyPK used for ccflow */
    this.CCForm_MyPK = Util.NewGUID();
}

/**Straight connector type*/
Connector.TYPE_STRAIGHT = 'straight';

/**Jagged connector type*/
Connector.TYPE_JAGGED = 'jagged';

/**Round connector type. Orthogonal angles are smoothed. 
*TODO: Not implemented*/
Connector.TYPE_ROUND = 'round';

/**Round connector type. The connector is drawn as a curve*/
Connector.TYPE_ORGANIC = 'organic';



/**Normal end connector style*/
Connector.STYLE_NORMAL = "Normal";

/**Arrow like end connector style*/
Connector.STYLE_ARROW = "Arrow";

/**Empty triangle end connector style*/
Connector.STYLE_EMPTY_TRIANGLE = "Empty";

/**Filled triangle end connector style*/
Connector.STYLE_FILLED_TRIANGLE = "Filled";

/**End connector arrow size*/
Connector.ARROW_SIZE = 12;

/**End connector arrow angle*/
Connector.ARROW_ANGLE = 15;


/**User change horizontal align*/
Connector.USER_CHANGE_HORIZONTAL_ALIGN = 'h';

/**User change vertical align*/
Connector.USER_CHANGE_VERTICAL_ALIGN = 'v';


/**Creates a {Connector} out of JSON parsed object
*@param {JSONObject} o - the JSON parsed object
*@return {Connector} a newly constructed Connector
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
Connector.load = function (o) {
    var newConnector = new Connector(new Point(0, 0), new Point(0, 0), Connector.TYPE_STRAIGHT, 0); //fake constructor

    newConnector.id = o.id;
    newConnector.turningPoints = Point.loadArray(o.turningPoints);
    newConnector.type = o.type;
    newConnector.userChanges = o.userChanges;
    newConnector.solution = o.solution;
    newConnector.style = Style.load(o.style);

    newConnector.middleText = Text.load(o.middleText);

    newConnector.properties = BuilderProperty.loadArray(o.properties);

    newConnector.endStyle = o.endStyle;
    newConnector.startStyle = o.startStyle;

    newConnector.activeConnectionPointId = o.activeConnectionPointId;

    newConnector.CCForm_Shape = o.CCForm_Shape;
    newConnector.CCForm_MyPK = o.CCForm_MyPK;
    
    return newConnector;
}

/**Creates a an {Array} of {Connector} out of JSON parsed array
*@param {JSONObject} v - the JSON parsed {Array}
*@return {Array} of newly loaded {Connector}s
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
Connector.loadArray = function (v) {
    var newConnectors = [];

    for (var i = 0; i < v.length; i++) {
        newConnectors.push(Connector.load(v[i]));
    }

    return newConnectors;
}

Connector.prototype = {

    constructor: Connector,


    /**
    *Compares to another Connector
    *
    *@param {Connector} anotherConnector - the other connector
    **/
    equals: function (anotherConnector) {
        if (!anotherConnector instanceof Connector) {
            return false;
        }

        //test turning points
        for (var i = 0; i < this.turningPoints.length; i++) {
            if (!this.turningPoints[i].equals(anotherConnector.turningPoints[i])) {
                return false;
            }
        }

        //test properties
        for (var i = 0; i < this.properties.length; i++) {
            if (!this.properties[i].equals(anotherConnector.properties[i])) {
                return false;
            }
        }

        //test user changes
        for (var i = 0; i < this.userChanges.length; i++) {
            if (this.userChanges[i].align != anotherConnector.userChanges[i].align
                || this.userChanges[i].index != anotherConnector.userChanges[i].index
                || this.userChanges[i].delta != anotherConnector.userChanges[i].delta) {
                return false;
            }
        }

        if (this.id != anotherConnector.id
            || this.type != anotherConnector.type
            || this.solution != anotherConnector.solution
            || !this.middleText.equals(anotherConnector.middleText)
            || this.startStyle != anotherConnector.startStyle
            || this.endStyle != anotherConnector.endStyle
            || this.activeConnectionPointId != anotherConnector.activeConnectionPointId) {
            return false;
        }

        return true;
    },

    /**
    *Creates an arrow like figure, pointed down \/, at a certain position
    * @param {Number} x - the X coordinates of the point
    * @param {Number} y - the X coordinates of the point
    * @return {Path} the arrow as a {Path} object
    * @author Zack
    **/
    getArrow: function (x, y) {
        var startPoint = new Point(x, y);
        var line = new Line(startPoint.clone(), Util.getEndPoint(startPoint, Connector.ARROW_SIZE, Math.PI / 180 * Connector.ARROW_ANGLE));
        var line1 = new Line(startPoint.clone(), Util.getEndPoint(startPoint, Connector.ARROW_SIZE, Math.PI / 180 * -Connector.ARROW_ANGLE));

        var path = new Path();

        path.style = this.style;
        line.style = this.style;
        line1.style = this.style;

        path.addPrimitive(line);
        path.addPrimitive(line1);

        return path;
    },

    /**Creates a triangle like figure, pointed down \/, at a certain position
    * @param {Number} x - the X coordinates of the point
    * @param {Number} y - the X coordinates of the point
    * @param {Boolean} fill - if true fill the triangle
    * @return {Path} the arrow as a {Path} object
    * @author Zack, Alex
    * */
    getTriangle: function (x, y, fill) {

        var startPoint = new Point(x, y);
        var point2 = Util.getEndPoint(startPoint, Connector.ARROW_SIZE, Math.PI / 180 * Connector.ARROW_ANGLE);
        var point3 = Util.getEndPoint(startPoint, Connector.ARROW_SIZE, -Math.PI / 180 * Connector.ARROW_ANGLE);

        var tri = new Polygon();
        tri.addPoint(startPoint);
        tri.addPoint(point2);
        tri.addPoint(point3);

        tri.style = this.style.clone();
        if (fill) {
            tri.style.fillStyle = this.style.strokeStyle;
        }
        else {
            tri.style.fillStyle = '#FFFFFF';
        }

        return tri;
    },



    /**Paints the connector
    *@param {CanvasRenderingContext2D} context - the 2D context of the canvas
    *@author Alex, Zack, Artyom
    **/
    paint: function (context) {
        //Do the start and end point match?
        if (this.areStartEndPointsMatch()) {
            // then not paint Connector at all
            return;
        }

        context.save();

        this.style.setupContext(context);

        switch (this.type) {
            case Connector.TYPE_ORGANIC:
                this.paintOrganic(context);
                break;

            case Connector.TYPE_STRAIGHT:
            case Connector.TYPE_JAGGED:
                context.beginPath();

                //paint connector's line
                context.moveTo(this.turningPoints[0].x, this.turningPoints[0].y);

                for (var i = 1; i < this.turningPoints.length; i++) {
                    //start style
                    if (this.startStyle == Connector.STYLE_EMPTY_TRIANGLE && i == 1) { //special case
                        //get the angle of the start line
                        var angle = Util.getAngle(this.turningPoints[0], this.turningPoints[1]);
                        //by alex: var newPoint = Util.getEndPoint(this.turningPoints[0], Connector.ARROW_SIZE * Math.sin(Math.PI/180 * Connector.ARROW_ANGLE * 2), angle);
                        var newPoint = Util.getEndPoint(this.turningPoints[0], Connector.ARROW_SIZE * Math.cos(Math.PI / 180 * Connector.ARROW_ANGLE), angle);
                        //move to new start
                        context.moveTo(newPoint.x, newPoint.y);
                    }

                    //end style
                    if (this.endStyle == Connector.STYLE_EMPTY_TRIANGLE && i == this.turningPoints.length - 1) { //special case 
                        //get the angle of the final line
                        var angle = Util.getAngle(this.turningPoints[i - 1], this.turningPoints[i]);
                        //by alex: var newPoint = Util.getEndPoint(this.turningPoints[i], -Connector.ARROW_SIZE*Math.sin(Math.PI/180*Connector.ARROW_ANGLE*2), angle)
                        var newPoint = Util.getEndPoint(this.turningPoints[i], -Connector.ARROW_SIZE * Math.cos(Math.PI / 180 * Connector.ARROW_ANGLE), angle)
                        //line to new end
                        context.lineTo(newPoint.x, newPoint.y);
                    }
                    else {
                        context.lineTo(this.turningPoints[i].x, this.turningPoints[i].y);
                    }
                }
                context.stroke();
                break;
        }

        this.paintVisualDebug(context);

        this.paintStart(context);
        this.paintEnd(context);

        this.paintText(context);


        context.restore();
    },

    /**
    * @param {CanvasRenderingContext2D} context the context to draw 
    * */
    paintOrganic: function (context) {
        //poly.style.strokeStyle = '#000000';
        //poly.style.lineWidth = 1;
        //poly.paint(context);

        //paint NURBS
        //        var rPoints  = Util.collinearReduction(this.turningPoints);
        var rPoints = this.turningPoints;
        Log.info("Connector:paint() - Number of reduced points: " + rPoints.length + " " + rPoints);

        //1 - Draw NURB based only on turning points
        var n = new NURBS(rPoints);
        n.style.strokeStyle = 'rgba(0,100,0,0.5)'; //green

        //paint glow
        if (DIAGRAMO.debug) {
            //Log.info("Nr of cubic curves " +  this.fragments.length);
            for (var f = 0; f < n.fragments.length; f++) {
                var fragment = n.fragments[f].clone();
                fragment.style.lineWidth = 6;
                fragment.style.strokeStyle = "rgb(" + f * 100 % 255 + "," + f * 50 % 255 + "," + f * 20 % 255 + ")";
                fragment.paint(context);
            }
        }

        n.paint(context);
        //end 1
    },

    paintStart: function (context) {
        //paint start style
        var path = null;
        if (this.startStyle == Connector.STYLE_ARROW) {
            path = this.getArrow(this.turningPoints[0].x, this.turningPoints[0].y);
        }
        if (this.startStyle == Connector.STYLE_EMPTY_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, false);
        }
        if (this.startStyle == Connector.STYLE_FILLED_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, true);
        }


        //move start path(arrow, triangle, etc) into position
        if (path) {
            var transX = this.turningPoints[0].x;
            var transY = this.turningPoints[0].y;

            var lineAngle = Util.getAngle(this.turningPoints[0], this.turningPoints[1], 0);
            path.transform(Matrix.translationMatrix(-transX, -transY));
            path.transform(Matrix.rotationMatrix(lineAngle));
            path.transform(Matrix.translationMatrix(transX, transY));

            context.save();

            //context.lineJoin = "miter";
            context.lineJoin = "round";
            context.lineCap = "round";
            path.paint(context);

            context.restore();
        }
    },

    paintEnd: function (context) {
        //paint end style
        var path = null;
        if (this.endStyle == Connector.STYLE_ARROW) {
            path = this.getArrow(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y);
        }
        if (this.endStyle == Connector.STYLE_EMPTY_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y, false);
        }
        if (this.endStyle == Connector.STYLE_FILLED_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y, true);
        }

        //move end path (arrow, triangle, etc) into position
        if (path) {
            var transX = this.turningPoints[this.turningPoints.length - 1].x;
            var transY = this.turningPoints[this.turningPoints.length - 1].y;
            var lineAngle = Util.getAngle(this.turningPoints[this.turningPoints.length - 1], this.turningPoints[this.turningPoints.length - 2], 0);

            path.transform(Matrix.translationMatrix(-transX, -transY));
            path.transform(Matrix.rotationMatrix(lineAngle));
            path.transform(Matrix.translationMatrix(transX, transY));

            context.save();

            context.lineJoin = "round";
            context.lineCap = "round";

            path.paint(context);

            context.restore();
        }
    },


    paintVisualDebug: function (context) {
        //paint debug points
        if (DIAGRAMO.debug) {
            context.beginPath();
            for (var i = 0; i < this.turningPoints.length; i++) {
                context.moveTo(this.turningPoints[i].x, this.turningPoints[i].y);
                context.arc(this.turningPoints[i].x, this.turningPoints[i].y, 3, 0, Math.PI * 2, false);

                //context.strokeText('' + Util.round(this.turningPoints[i].x,2) + ',' + Util.round(this.turningPoints[i].y,2), this.turningPoints[i].x + 5, this.turningPoints[i].y - 5);
            }
            context.stroke();

            //paint coordinates
            context.save();
            for (var i = 0; i < this.turningPoints.length; i++) {
                context.fillText('(' + Util.round(this.turningPoints[i].x, 3) + ', ' + Util.round(this.turningPoints[i].y, 3) + ')', this.turningPoints[i].x + 5, this.turningPoints[i].y - 5);
            }
            context.restore();
        }
    },


    /**Paints the text of the connector
    *@param {Context} context - the 2D context of the canvas
    *@private 
    *@author Alex
    **/
    paintText: function (context) {
        if (this.middleText.str != '') {

            //TODO: not so smart to paint the background of the text
            var oldFill = context.fillStyle;

            context.beginPath();
            var textBounds = this.middleText.getBounds();
            context.moveTo(textBounds[0], textBounds[1]);
            context.lineTo(textBounds[0], textBounds[3]);
            context.lineTo(textBounds[2], textBounds[3]);
            context.lineTo(textBounds[2], textBounds[1]);
            context.fillStyle = "white";
            context.closePath();
            context.fill();

            context.fillStyle = oldFill;
            this.middleText.paint(context);
        }
    },

    /*
    *apply/clone another Connector style onto this connector
    *@param{Connector} anotherConnector - another Connector
    *@author dgq 
    *@deprecated
    */
    applyAnotherConnectorStyle: function (anotherConnector) {
        this.style = anotherConnector.style;
        this.style.strokeStyle = anotherConnector.style.strokeStyle;
        this.style.lineWidth = anotherConnector.style.lineWidth;
    },

    /**
    *Apply a transformation to this Connector
    *@param {Matrix} matrix - a matrix of numbers
    **/
    transform: function (matrix) {
        this.activeConnectionPointId = -1;
        if (SHIFT_PRESSED) {
            var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id);
            var rStartPoint = cps[0];
            var rEndPoint = cps[cps.length - 1];
            var xSpan = rStartPoint.point.x - rEndPoint.point.x;
            var ySpan = rStartPoint.point.y - rEndPoint.point.y;
            //如果为横线,上下键只能是移动，左右键增长或缩短线长度
            if (Math.abs(xSpan) > Math.abs(ySpan)) {
                //计算左右点
                var leftPoint = rStartPoint;
                var rightPoint = rEndPoint;
                if (rStartPoint.point.x > rEndPoint.point.x) {
                    rightPoint = rStartPoint;
                    leftPoint = rEndPoint;
                }
                //判断移动方向
                if (Matrix.equals(matrix,Matrix.RIGHT) == true) {
                    this.activeConnectionPointId = rightPoint.id;
                }
                if (Matrix.equals(matrix,Matrix.LEFT) == true) {
                    this.activeConnectionPointId = leftPoint.id;
                }

            } else { //竖线
                
            }
        }
        //are we moving the whole Connector, or just one point?
        if (this.activeConnectionPointId != -1) {
            var point = CONNECTOR_MANAGER.connectionPointGetById(this.activeConnectionPointId);
            point.transform(matrix);
        }
        else {
            for (var i = 0; i < this.turningPoints.length; i++) {
                this.turningPoints[i].transform(matrix);
            }
            var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id);
            for (var j = 0; j < cps.length; j++) {
                cps[j].transform(matrix);
            }
            //this.startText.transform(matrix);
            //this.endText.transform(matrix);
        }

    },

    /**
    *Creates as jagged(zig-zag) line between 2 ConnectionPoints
    *@author Zack Newsham <zack_newsham@yahoo.co.uk>
    *@deprecated
    **/
    jagged: function () {
        this.jaggedReloaded();
        return;


        //reference to the start and end
        var endPoint = this.turningPoints.pop();
        var startPoint = this.turningPoints[0];

        //the figure at the start
        var startConnectionPoint = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0];
        var glue = CONNECTOR_MANAGER.glueGetByConnectionPointId(startConnectionPoint.id)[0]; //there will only be one for this

        var startConnectionFigureId = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == startConnectionPoint.id ? glue.id2 : glue.id1).parentId;
        var startConnectionFigure = STACK.figureGetById(startConnectionFigureId);

        var startCenterPoint;
        if (startConnectionFigure) {
            startCenterPoint = startConnectionFigure.rotationCoords[0];
        }
        else {
            startCenterPoint = startPoint;
        }

        //the figure at the end
        var endCon = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1];
        glue = CONNECTOR_MANAGER.glueGetByConnectionPointId(endCon.id)[0]; //there will only be one for this
        var endConnectionFigure = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == endCon.id ? glue.id2 : glue.id1).parentId;
        endConnectionFigure = STACK.figureGetById(endConnectionFigure);
        var endCenterPoint;
        if (endConnectionFigure) {
            endCenterPoint = endConnectionFigure.rotationCoords[0];
        }
        else {
            endCenterPoint = endPoint;
        }

        //regardless of whih figure was clicked first, the left figure is start.
        var swapped = false;
        if (endCenterPoint.x < startCenterPoint.x) {
            var t = endCenterPoint;
            endCenterPoint = startCenterPoint;
            startCenterPoint = t;

            t = endConnectionFigure;
            endConnectionFigure = startConnectionFigure;
            startConnectionFigure = t;

            t = endPoint;
            endPoint = startPoint;
            startPoint = t;
            swapped = true;
        }


        //we use this later
        var endPoints = [endPoint];

        //clear the array of all intermediate turningPoints, we use this when we have a connector that is moved from one connectionPoint to another
        this.turningPoints = [startPoint];

        //start+end+4 turning points
        var nextPoint;
        var startAngle = Util.getAngle(startCenterPoint, startPoint, Math.PI / 2);
        var endAngle = Util.getAngle(endCenterPoint, endPoint, Math.PI / 2);

        //move away from figure in angle(90) direction

        //END FIGURE ESCAPE
        if (endAngle == 0) {
            nextPoint = new Point(endPoint.x, endConnectionFigure.getBounds()[1] - 20);
        }
        else if (endAngle == Math.PI / 2) {
            nextPoint = new Point(endConnectionFigure.getBounds()[2] + 20, endPoint.y);
        }
        else if (endAngle == Math.PI) {
            nextPoint = new Point(endPoint.x, endConnectionFigure.getBounds()[3] + 20);
        }
        else {
            nextPoint = new Point(endConnectionFigure.getBounds()[0] - 20, endPoint.y);
        }
        endPoints.push(nextPoint);
        endPoint = nextPoint;

        var previousPoint = startPoint;

        //START FIGURE ESCAPE
        //clear bounds
        //clear bounds by 20px in direction of angle
        if (startAngle == 0) {
            nextPoint = new Point(startPoint.x, startConnectionFigure.getBounds()[1] - 20);
        }
        else if (startAngle == Math.PI / 2) {
            nextPoint = new Point(startConnectionFigure.getBounds()[2] + 20, startPoint.y);
        }
        else if (startAngle == Math.PI) {
            nextPoint = new Point(startPoint.x, startConnectionFigure.getBounds()[3] + 20);
        }
        else {
            nextPoint = new Point(startConnectionFigure.getBounds()[0] - 20, startPoint.y);
        }
        this.turningPoints.push(nextPoint);

        startPoint = nextPoint;

        var currentPoint = startPoint;
        nextPoint = null;
        var angles = [0, Math.PI / 2, Math.PI, Math.PI / 2 * 3, Math.PI * 2];
        var startCounter = 0; //keeps track of new turning points added
        var intEnd = Util.lineIntersectsRectangle(startPoint, endPoint, endConnectionFigure.getBounds());
        var intStart = Util.lineIntersectsRectangle(startPoint, endPoint, startConnectionFigure.getBounds());
        while (intEnd || intStart) {//while we have an intersection, keep trying

            //get the angle of the last turn made, we know we need to do something 90degrees off this
            startAngle = Util.getAngle(startPoint, this.turningPoints[this.turningPoints.length - 2], Math.PI / 2);
            endAngle = Util.getAngle(endPoint, endPoints[endPoints.length - 2], Math.PI / 2);
            switch (startCounter) {
                case 0:
                    if (startAngle == 0 || startAngle == Math.PI) { //we were going N/S, now we want to go E/W
                        if (startPoint.x < endPoint.x) {
                            startPoint = new Point(startConnectionFigure.getBounds()[2] + 20, startPoint.y);
                        }
                        else {
                            startPoint = new Point(startConnectionFigure.getBounds()[0] - 20, startPoint.y);
                        }
                    }
                    else {//going E/W now want N/S
                        if (startPoint.y < endPoint.y || endPoint.y > startConnectionFigure.getBounds()[1]) {
                            startPoint = new Point(startPoint.x, startConnectionFigure.getBounds()[3] + 20);
                        }
                        else {
                            startPoint = new Point(startPoint.x, startConnectionFigure.getBounds()[1] - 20);
                        }
                    }
                    this.turningPoints.push(startPoint);
                    break;
                case 1: //we have already done something to the startPoint, changing the end point should resolve the issue
                    endPoints.push(endPoint);
                    if (endAngle == 0 || endAngle == Math.PI) { //we were going N/S, now we want to go E/W
                        if (startPoint.x > endPoint.x) {
                            endPoint = new Point(endConnectionFigure.getBounds()[2] + 20, endPoint.y);
                        }
                        else {
                            endPoint = new Point(endConnectionFigure.getBounds()[0] - 20, endPoint.y);
                        }
                    }
                    else {//going E/W now want N/S
                        if (startPoint.y > endPoint.y) {
                            endPoint = new Point(endPoint.x, endConnectionFigure.getBounds()[3] + 20);
                        }
                        else {
                            endPoint = new Point(endPoint.x, endConnectionFigure.getBounds()[1] - 20);
                        }
                    }
                    break;
            }

            startCounter++;
            intEnd = Util.lineIntersectsRectangle(startPoint, endPoint, endConnectionFigure.getBounds());
            intStart = Util.lineIntersectsRectangle(startPoint, endPoint, startConnectionFigure.getBounds());
            if (startCounter == 3) {//we have done all we can, if we still can't make a good jagged, make a bad one
                break;
            }
        }

        //there are no intersections of the straight line between start and end
        //now lets see if making a jagged line will create one
        //this should only occur when we need to make an opposite turn, that could lead to us running along the edge of one figures bounds
        if (!Util.lineIntersectsRectangle(new Point(startPoint.x, endPoint.y), new Point(endPoint.x, endPoint.y), endConnectionFigure.getBounds())
            && !Util.lineIntersectsRectangle(new Point(startPoint.x, endPoint.y), new Point(endPoint.x, endPoint.y), startConnectionFigure.getBounds())
            && !Util.lineIntersectsRectangle(new Point(startPoint.x, startPoint.y), new Point(startPoint.x, endPoint.y), endConnectionFigure.getBounds())
            && !Util.lineIntersectsRectangle(new Point(startPoint.x, startPoint.y), new Point(startPoint.x, endPoint.y), startConnectionFigure.getBounds())) {
            this.turningPoints.push(new Point(startPoint.x, endPoint.y));
        }
        else {//if(!Util.lineIntersectsRectangle(new Point(endPoint.x,startPoint.y), new Point(endPoint.x,endPoint.y), endConnectionFigure.getBounds()) && !Util.lineIntersectsRectangle(new Point(endPoint.x,startPoint.y), new Point(endPoint.x,endPoint.y), startConnectionFigure.getBounds())){
            this.turningPoints.push(new Point(endPoint.x, startPoint.y));
        }
        /*else{//worst case scenario, we cant go up then across, cant go across then up, lets clear the end figure, and go up then across
        var yMove=(startPoint.y>endPoint.y?endConnectionFigure.getBounds()[3]+20:endConnectionFigure.getBounds()[3]-20);
        this.turningPoints.push(new Point(startPoint.x,yMove));
        this.turningPoints.push(new Point(endPoint.x,yMove));
        }*/

        //add the endPoints we have changed to the line
        this.turningPoints.push(new Point(endPoint.x, endPoint.y));
        for (var i = 0; i < endPoints.length; i++) {
            this.turningPoints.push(endPoints.pop());
            i--; //lower the counter or we will only get half the points
        }

        //if our line was supposed to go backwards, lets reverse it
        if (swapped) {
            this.turningPoints = this.turningPoints.reverse();
        }
    },


    /**A rework of jagged method
    *Just creates all turning points for Connector that has a StartPoint and an EndPoint
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    jaggedReloaded: function () {

        //reference to the start and end
        var startPoint = this.turningPoints[0];
        var startExitPoint = null; //next turning point after the startPoint (if start figure present)
        var endExitPoint = null; //the last turning point before endPoint (if end figure present)
        var endPoint = this.turningPoints[this.turningPoints.length - 1];



        //START FIGURE
        var startConnectionPointOnConnector = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0]; //fist ConnectionPoint on the Connector
        var glue = CONNECTOR_MANAGER.glueGetByConnectionPointId(startConnectionPointOnConnector.id)[0]; //the (only) Glue tied to ConnectionPoint

        if (glue != null) { //only if there is a Figure glued
            //get ConnectionPoint on Figure
            var startFigureConnectionPoint = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == startConnectionPointOnConnector.id ? glue.id2 : glue.id1);
            var startFigure = STACK.figureGetById(startFigureConnectionPoint.parentId);

            var startAngle = Util.getAngle(startFigure.rotationCoords[0], startPoint, Math.PI / 2);
            switch (startAngle) {
                case 0: //north exit
                    startExitPoint = new Point(startPoint.x, startFigure.getBounds()[1] - 20);
                    break;
                case Math.PI / 2: //east exit
                    startExitPoint = new Point(startFigure.getBounds()[2] + 20, startPoint.y);
                    break;
                case Math.PI: //south exit
                    startExitPoint = new Point(startPoint.x, startFigure.getBounds()[3] + 20);
                    break;
                case 3 * Math.PI / 2: //west exit
                    startExitPoint = new Point(startFigure.getBounds()[0] - 20, startPoint.y);
                    break;
            }
        }


        //END FIGURE
        var endConnectionPointOnConnector = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1]; //last ConnectionPoint on Connector
        glue = CONNECTOR_MANAGER.glueGetByConnectionPointId(endConnectionPointOnConnector.id)[0]; //there will only be one for this

        if (glue != null) { //only if there is a Figure glued
            //get ConnectionPoint on Figure
            var endFigureConnectionPoint = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == endConnectionPointOnConnector.id ? glue.id2 : glue.id1);
            var endFigure = STACK.figureGetById(endFigureConnectionPoint.parentId);

            var endAngle = Util.getAngle(endFigure.rotationCoords[0], endPoint, Math.PI / 2);
            switch (startAngle) {
                case 0: //north exit
                    endExitPoint = new Point(endPoint.x, endFigure.getBounds()[1] - 20);
                    break;
                case Math.PI / 2: //east exit
                    endExitPoint = new Point(endFigure.getBounds()[2] + 20, endPoint.y);
                    break;
                case Math.PI: //south exit
                    endExitPoint = new Point(endPoint.x, endFigure.getBounds()[3] + 20);
                    break;
                case 3 * Math.PI / 2: //west exit
                    endExitPoint = new Point(endFigure.getBounds()[0] - 20, endPoint.y);
                    break;
            }
        }

        alert('jaggedReloaded:Connector has ' + this.turningPoints.length + " points");
        this.turningPoints.splice(1, 0, startExitPoint, endExitPoint);
        alert('jaggedReloaded:Connector has ' + this.turningPoints.length + " points");
    },

    /**This function simply tries to create all possible intermediate points that can be placed
    *between 2 points to create a jagged connector
    *@param {Point} p1 - a point
    *@param {Point} p2 - the other point*/
    connect2Points: function (p1, p2) {
        var solutions = [];

        //1. is p1 == p2?
        if (p1.equals(p2)) {

        }

        //2. is p1 on a vertical or horizontal line with p2? S0
        //3. can we have a single intermediate point? S1
        //4. can we have 2 intermediate points? S2
        return solutions;
    },

    /**
    *Remove redundant points (we have just ajusted one of the handles of this figure, so)
    **/
    redraw: function () {
        if (this.type == 'jagged') {
            var changed = true;
            while (changed == true) {
                changed = false;
                for (var i = 1; i < this.turningPoints.length - 2; i++) {
                    if (this.turningPoints[i].x == this.turningPoints[i - 1].x && this.turningPoints[i - 1].x == this.turningPoints[i + 1].x) {
                        this.turningPoints.splice(i, 1);
                        changed = true;
                    }
                    if (this.turningPoints[i].y == this.turningPoints[i - 1].y && this.turningPoints[i - 1].y == this.turningPoints[i + 1].y) {
                        this.turningPoints.splice(i, 1);
                        changed = true;
                    }
                }
            }

        }
    },

    /**
    * Transform a ConnectionPoint by a matrix. Usually called only by ConnectionManager.connectionPointTransform(),
    * when a figure is being moved, so it's more or less start point or end point of a connector.
    * Important to remember is that by moving and edge turning point all ther might be cases when more than one
    * points need to change
    * Once a figure is changed its ConnectionPoints got tranformed...so the glued Connector must
    * change...it's like a cascade change
    * @param {Matrix} matrix - the transformation to be used
    * @param {Point} point - the point to start from (could be end or start). It is the point that
    * triggered the adjustement
    */
    adjust: function (matrix, point) {

        //Log.info('Adjusting...');
        if (this.type == Connector.TYPE_STRAIGHT) {
            //Log.info("straight ");

            var tempConPoint = CONNECTOR_MANAGER.connectionPointGetByParentAndCoordinates(this.id, point.x, point.y);

            //find index of the turning point
            var index = -1;
            if (this.turningPoints[0].equals(point)) {
                index = 0;
            }
            else if (this.turningPoints[1].equals(point)) {
                index = 1;
            }
            else {
                Log.error("Connector:adjust() - This should not happend" + this.toString() + ' point is ' + point);
            }


            //Log.info('\tinitial' +  tempConPoint.toString());
            tempConPoint.transform(matrix);
            //Log.info('\tafter' +  tempConPoint.toString());


            this.turningPoints[index].x = tempConPoint.point.x;
            this.turningPoints[index].y = tempConPoint.point.y;

            /*TODO: it seems that the code bellow is not working fine...clone is wrong?
            this.turningPoints[index] = new Point(tempConPoint.point.x,tempConPoint.point.y);
            */

        }

        if (this.type == Connector.TYPE_JAGGED) {
            //Log.info("jagged ");
            var oldX = point.x;
            var oldY = point.y;

            var tempConPoint = CONNECTOR_MANAGER.connectionPointGetByParentAndCoordinates(this.id, point.x, point.y);
            tempConPoint.transform(matrix);

            //are we starting from beginning or end, so we will detect the interval and direction
            var start, end, direction;
            if (point.equals(this.turningPoints[0])) {//if the point is the starting Point
                //Log.info("It is the starting point");

                //adjust first turning point
                this.turningPoints[0].x = tempConPoint.point.x;
                this.turningPoints[0].y = tempConPoint.point.y;

                start = 1;
                end = this.turningPoints.length;
                direction = 1;
            }
            else if (point.equals(this.turningPoints[this.turningPoints.length - 1])) { //if the point is the ending Point
                //Log.info("It is the ending point");

                //adjust last turning point
                this.turningPoints[this.turningPoints.length - 1].x = tempConPoint.point.x;
                this.turningPoints[this.turningPoints.length - 1].y = tempConPoint.point.y;

                start = this.turningPoints.length - 2;
                end = -1;
                direction = -1;
            }
            else {
                Log.error("Connector:adjust() - this should never happen for point " + point + ' and connector ' + this.toString());
            }

            //TODO: the only affected turning point should be ONLY the next one (if start point) or previous one (if end point)
            for (var i = start; i != end; i += direction) {
                //If this turningPoints X==last turningPoints X (or Y), prior to transformation, then they used to be the same, so make them the same now
                //dont do this if they are our start/end point
                //we don't want to use them if they are on he exact spot
                if (this.turningPoints[i].y != oldY
                    && this.turningPoints[i].x == oldX //same x
                    && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0].point
                    && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1].point) {
                    oldX = this.turningPoints[i].x;
                    oldY = this.turningPoints[i].y;
                    this.turningPoints[i].x = this.turningPoints[i - direction].x;
                }
                else if (this.turningPoints[i].x != oldX
                    && this.turningPoints[i].y == oldY
                    && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0].point
                    && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1].point) {
                    oldX = this.turningPoints[i].x;
                    oldY = this.turningPoints[i].y;
                    this.turningPoints[i].y = this.turningPoints[i - direction].y;
                }
            }
        }
    },


    /**Applies solution from ConnectionManager.connector2Points() method.
    *@param {Array} solutions - value returned from ConnectionManager.connector2Points()
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    *@author Alex
    **/
    applySolution: function (solutions) {
        // solution category: 's0', 's1_1', 's2_2', etc.
        var solutionCategory = solutions[0][1];

        /*We should check if solution changed from previous.
        * Solution determined by it's category (s1_2, s2_1) and number of turning points.*/
        if (!this.solution //No solution?
                || this.solution != solutionCategory   // Did category changed?
                || this.turningPoints.length != solutions[0][2].length) {   // Did number of turning points changed?
            this.solution = solutionCategory;   // update solution
            this.clearUserChanges();  // clear user changes
            this.turningPoints = Point.cloneArray(solutions[0][2]);  // update turning points
        } else { //same solution (category and turning points no)
            this.turningPoints = Point.cloneArray(solutions[0][2]);    // get turning points from solution
            this.applyUserChanges();    // apply user changes to turning points
        }

        this.updateMiddleText();    // update position of middle text
    },


    /**Applies user changes to turning points.
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    applyUserChanges: function () {
        var changesLength = this.userChanges.length;
        var currentChange;
        var translationMatrix;

        // go through and apply all changes
        for (var i = 0; i < changesLength; i++) {
            currentChange = this.userChanges[i];

            // generate translation matrix
            if (currentChange.align == Connector.USER_CHANGE_HORIZONTAL_ALIGN) {    // Do we have horizontal change?
                translationMatrix = Matrix.translationMatrix(currentChange.delta, 0);   // apply horizontal delta
            } else if (currentChange.align == Connector.USER_CHANGE_VERTICAL_ALIGN) {       // Do we have vertical change?
                translationMatrix = Matrix.translationMatrix(0, currentChange.delta);   // apply vertical delta
            }
            // apply change
            this.turningPoints[currentChange.index].transform(translationMatrix);
        }
    },


    /**Adds user change.
    *@param {Object} userChange - user change to add. It's form is
    * {align : '', delta: '', index: ''} where 
    *  align:  Connector.USER_CHANGE_VERTICAL_ALIGN | Connector.USER_CHANGE_HORIZONTAL_ALIGN
    *  delta: Numeric
    *  index : the index of turning point     
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    addUserChange: function (userChange) {
        var changesLength = this.userChanges.length;
        var currentChange;

        /*First seach if we need to merge current change with existing one,
        * if no existing one present we will simply add it.*/

        // Go through all changes (Merge option)
        for (var i = 0; i < changesLength; i++) {
            currentChange = this.userChanges[i];

            // Do we have change with such align and index?
            if (currentChange.align == userChange.align && currentChange.index == userChange.index) {
                /* update delta of previous change
                * we should accumulate delta value, not replace
                * because current step here based on previous*/
                currentChange.delta += userChange.delta;
                return; // work is done - exit function
            }
        }

        // we have new change and add it to array (new change)
        this.userChanges.push(userChange);
    },


    /**Clears user changes.
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    clearUserChanges: function () {
        this.userChanges = [];
    },


    /**Clones array of user changes.
    *@return {Array} - array containing current user changes
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    cloneUserChanges: function () {
        var clonedArray = [];
        var changesLength = this.userChanges.length;

        // go through and clone all user changes
        for (var i = 0; i < changesLength; i++) {
            // create new instance of user changes
            // set values equal to current user change
            // push new instance in result array with cloned user changes
            clonedArray.push({
                align: this.userChanges[i].align,
                index: this.userChanges[i].index,
                delta: this.userChanges[i].delta
            });
        }

        return clonedArray;
    },


    /**Check if start and end members of turningPoints match/are the same.
    *@return {Boolean} - match or not
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    areStartEndPointsMatch: function () {
        return this.turningPoints[0].equals(this.turningPoints[this.turningPoints.length - 1]);
    },


    /**
    * See if a file is on a connector
    * @param {Number} x - coordinate
    * @param {Number} y - coordinate
    * @author alex
    */
    contains: function (x, y) {
        var r = false;
        switch (this.type) {

            case Connector.TYPE_STRAIGHT:
                //just fall :)

            case Connector.TYPE_JAGGED:
                for (var i = 0; i < this.turningPoints.length - 1; i++) {
                    var l = new Line(this.turningPoints[i], this.turningPoints[i + 1]);
                    if (l.contains(x, y)) {
                        r = true;
                        break;
                    }
                }
                break;

            case Connector.TYPE_ORGANIC:
                var n = new NURBS(this.turningPoints);

                r = n.contains(x, y);

                break;
        }

        return r;
    },

    /**Tests if a point defined by (x,y) is within a radius
    *@param {Number} x - x coordinates of the point
    *@param {Number} y - y coordinates of the point
    *@param {Number} radius - the radius to seach within
    *@author alex
    **/
    near: function (x, y, radius) {
        var r = false;
        switch (this.type) {
            case Connector.TYPE_STRAIGHT:
                //just fall :)    
            case Connector.TYPE_JAGGED:
            case Connector.STYLE_NORMAL:
                for (var i = 0; i < this.turningPoints.length - 1; i++) {
                    var l = new Line(this.turningPoints[i], this.turningPoints[i + 1]);
                    if (l.near(x, y, radius)) {
                        r = true;
                        break;
                    }
                }
                break;
            case Connector.TYPE_ORGANIC:
                var n = new NURBS(this.turningPoints);
                r = n.near(x, y, radius);
                break;
        }

        return r;
    },
    clone: function () {
        var startPoint = this.turningPoints[0].clone();
        var endPoint = this.turningPoints[1].clone();

        var cId = CONNECTOR_MANAGER.connectorCreate(startPoint, endPoint, Connector.TYPE_STRAIGHT);
        var conn = CONNECTOR_MANAGER.connectorGetById(cId)
        //solutions
        var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection(false, false, -1, startPoint, -1, endPoint);
        var rStartBounds = null;
        var rEndBounds = null;
        DIAGRAMO.debugSolutions = CONNECTOR_MANAGER.connector2Points(Connector.TYPE_STRAIGHT, candidate[0], candidate[1], rStartBounds, rEndBounds);

        conn.style = this.style;
        conn.style.strokeStyle = this.style.strokeStyle;
        conn.style.lineWidth = this.style.lineWidth;
        return conn;
    },

    /**Returns the middle of a connector
    *Usefull for setting up the middle text
    *@return {Array} of 2 {Number}s - the x and y of the point
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    middle: function () {
        if (this.type == Connector.TYPE_STRAIGHT) {
            var middleX = (this.turningPoints[0].x + this.turningPoints[1].x) / 2;
            var middleY = (this.turningPoints[0].y + this.turningPoints[1].y) / 2;
            return [middleX, middleY];
        }
        else if (this.type == Connector.TYPE_JAGGED) {
            /** Algorithm:
            * Find the lenght of the connector. Then go on each segment until we will reach half of the
            * connector's lenght.
            **/

            //find total distance
            var distance = 0;
            for (var i = 0; i < this.turningPoints.length - 1; i++) {
                distance += Util.getLength(this.turningPoints[i], this.turningPoints[i + 1]);
            }

            //find between what turning points the half distance is
            var index = -1;
            var ellapsedDistance = 0;
            for (var i = 0; i < this.turningPoints.length - 1; i++) {
                index = i;
                var segment = Util.getLength(this.turningPoints[i], this.turningPoints[i + 1]);
                if (ellapsedDistance + segment < distance / 2) {
                    ellapsedDistance += segment;
                }
                else {
                    break;
                }
            }

            //we have the middle distance somewhere between i(ndex) and i(ndex)+1
            if (index != -1) {
                var missingDistance = distance / 2 - ellapsedDistance;
                if (Util.round(this.turningPoints[index].x, 3) == Util.round(this.turningPoints[index + 1].x, 3)) { //vertical segment (same x)
                    return [this.turningPoints[index].x, Math.min(this.turningPoints[index].y, this.turningPoints[index + 1].y) + missingDistance];
                } else if (Util.round(this.turningPoints[index].y, 3) == Util.round(this.turningPoints[index + 1].y, 3)) { //horizontal segment (same y)
                    return [Math.min(this.turningPoints[index].x, this.turningPoints[index + 1].x) + missingDistance, this.turningPoints[index].y];
                } else {
                    Log.error("Connector:middle() - this should never happen " + this.turningPoints[index] + " " + this.turningPoints[index + 1]
                        + " nr of points " + this.turningPoints.length
                        );
                }

            }
        } else if (this.type === Connector.TYPE_ORGANIC) {
            //TODO: Either compute the middle using pure NURB algorithm (and use t=0.5) or 
            //base it on the curves already computed (but they might no be evenly distributes
            //(or might not have the same length) to pick the middle of middle curve 
            //(if no. of curves is odd) or joining
            //point (if number of curves is even)
            var n = new NURBS(this.turningPoints);

            var middle = n.getMiddle();
            Log.info("Middle is " + middle);

            return [middle.x, middle.y];
        }

        return null;
    },


    /**Updates the middle text of the connector
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    updateMiddleText: function () {
        //        Log.info("updateMiddleText called");
        var middlePoint = this.middle();

        if (middlePoint != null) {
            var m = Matrix.translationMatrix(middlePoint[0] - this.middleText.vector[0].x, middlePoint[1] - this.middleText.vector[0].y)
            this.middleText.transform(m);
        }
    },

    /**Founds the bounds of the connector
    *@return {Array} - the [minX, minY, maxX, maxY]
    **/
    getBounds: function () {
        var minX = null;
        var minY = null;
        var maxX = null;
        var maxY = null;
        for (var i = 0; i < this.turningPoints.length; i++) {
            if (this.turningPoints[i].x < minX || minX == null)
                minX = this.turningPoints[i].x;
            if (this.turningPoints[i].x > maxX || maxX == null)
                maxX = this.turningPoints[i].x;
            if (this.turningPoints[i].y < minY || minY == null)
                minY = this.turningPoints[i].y;
            if (this.turningPoints[i].y > maxY || maxY == null)
                maxY = this.turningPoints[i].y;
        }
        return [minX, minY, maxX, maxY];
    },

    /**String representation*/
    toString: function () {
        return 'Connector : (id = ' + this.id
            + ', type = ' + this.type
            + ', turningPoints = [' + this.turningPoints + ']'
            + ', userChanges = [' + this.userChanges + ']'
            + ', solution = ' + this.solution
            + ', startStyle = ' + this.startStyle
            + ', endStyle = ' + this.endStyle
            + ', activeConnectionPointId = ' + this.activeConnectionPointId
            + ')';
    },


    /**SVG representation of the connector
    *@return {String} - the SVG part
    **/
    toSVG: function () {

        //1. paint line
        var r = '<polyline points="';
        for (var i = 0; i < this.turningPoints.length; i++) {
            r += this.turningPoints[i].x + ',' + this.turningPoints[i].y + ' ';
        }
        r += '"';
        r += this.style.toSVG();
        //        r += ' style="fill: #none; stroke:#ADADAD;" ';
        r += '/>';

        //2. paint the start/end
        //paint start style
        var path = null;
        if (this.startStyle == Connector.STYLE_ARROW) {
            path = this.getArrow(this.turningPoints[0].x, this.turningPoints[0].y);
        }
        if (this.startStyle == Connector.STYLE_EMPTY_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, false);
        }
        if (this.startStyle == Connector.STYLE_FILLED_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, true);
        }

        if (path) {
            var transX = this.turningPoints[0].x;
            var transY = this.turningPoints[0].y;

            var lineAngle = Util.getAngle(this.turningPoints[0], this.turningPoints[1], 0);
            path.transform(Matrix.translationMatrix(-transX, -transY));
            path.transform(Matrix.rotationMatrix(lineAngle));
            path.transform(Matrix.translationMatrix(transX, transY));

            r += path.toSVG();
        }


        //paint end style
        if (this.endStyle == Connector.STYLE_ARROW) {
            path = this.getArrow(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y);
        }
        if (this.endStyle == Connector.STYLE_EMPTY_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y, false);
        }
        if (this.endStyle == Connector.STYLE_FILLED_TRIANGLE) {
            path = this.getTriangle(this.turningPoints[this.turningPoints.length - 1].x, this.turningPoints[this.turningPoints.length - 1].y, true);
        }
        //move end path (arrow, triangle, etc) into position
        if (path) {
            var transX = this.turningPoints[this.turningPoints.length - 1].x;
            var transY = this.turningPoints[this.turningPoints.length - 1].y;
            var lineAngle = Util.getAngle(this.turningPoints[this.turningPoints.length - 1], this.turningPoints[this.turningPoints.length - 2], 0);

            path.transform(Matrix.translationMatrix(-transX, -transY));
            path.transform(Matrix.rotationMatrix(lineAngle));
            path.transform(Matrix.translationMatrix(transX, transY));

            r += path.toSVG();
        }



        //3. pain text (if any)
        if (this.middleText.str.length != 1) {
            //paint white background
            var txtBounds = this.middleText.getBounds(); //this is actually an array of numbers [minX, minY, maxX, maxY]

            var poly = new Polygon();

            poly.addPoint(new Point(txtBounds[0], txtBounds[1]));
            poly.addPoint(new Point(txtBounds[2], txtBounds[1]));
            poly.addPoint(new Point(txtBounds[2], txtBounds[3]));
            poly.addPoint(new Point(txtBounds[0], txtBounds[3]));
            poly.style.fillStyle = "#FFFFFF";

            r += poly.toSVG();


            //paint actuall text
            r += this.middleText.toSVG();
        }

        return r;
    }
}

/**
*A connection point that is attached to a figure and can accept connectors
*
*@constructor
*@this {ConnectionPoint}
*@param {Number} parentId - the parent to which this ConnectionPoint is attached. It can be either a {Figure} or a {Connector}
*@param {Point} point - coordinate of this connection point, better than using x and y, because when we move "snap to" this
* connectionPoint the line will follow
*@param {Number} id - unique id to the parent figure
*@param {String} type - the type of the parent. It can be either 'figure' or 'connector'
*
*@author Zack Newsham <zack_newsham@yahoo.co.uk>
*@author Alex Gheorghiu <alex@scriptoid.com>
*/
function ConnectionPoint(parentId, point, id, type) {
    /**Connection point id*/
    this.id = id;

    /**The {Point} that is behind this ConnectionPoint*/
    this.point = point.clone(); //we will create a clone so that no side effect will appear

    /**Parent id (id of the Figure or Connector)*/
    this.parentId = parentId;

    /**Type of ConnectionPoint. Ex: ConnectionPoint.TYPE_FIGURE*/
    this.type = type;

    /**Current connection point color*/
    this.color = ConnectionPoint.NORMAL_COLOR;

    /**Radius of the connection point*/
    this.radius = 3;

    /**Serialization type*/
    this.oType = 'ConnectionPoint'; //object type used for JSON deserialization

}

/**Color used by default to draw the connection point*/
ConnectionPoint.NORMAL_COLOR = "#FFFF33"; //yellow.

/*Color used to signal that the 2 connection points are about to glue*/
ConnectionPoint.OVER_COLOR = "#FF9900"; //orange

/*Color used to draw connected (glued) connection points*/
ConnectionPoint.CONNECTED_COLOR = "#ff0000"; //red

/**Connection point default radius*/
ConnectionPoint.RADIUS = 4;

/**Connection point (liked to)/ type figure*/
ConnectionPoint.TYPE_FIGURE = 'figure';

/**Connection point (liked to)/ type connector*/
ConnectionPoint.TYPE_CONNECTOR = 'connector';


/**Creates a {ConnectionPoint} out of JSON parsed object
*@param {JSONObject} o - the JSON parsed object
*@return {ConnectionPoint} a newly constructed ConnectionPoint
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
ConnectionPoint.load = function (o) {
    var newConnectionPoint = new ConnectionPoint(0, new Point(0, 0), ConnectionPoint.TYPE_FIGURE); //fake constructor

    newConnectionPoint.id = o.id;
    newConnectionPoint.point = Point.load(o.point);
    newConnectionPoint.parentId = o.parentId;
    newConnectionPoint.type = o.type;

    newConnectionPoint.color = o.color;
    newConnectionPoint.radius = o.radius;

    return newConnectionPoint;
}

/**Creates a an {Array} of {ConnectionPoint} out of JSON parsed array
*@param {JSONObject} v - the JSON parsed {Array}
*@return {Array} of newly loaded {ConnectionPoint}s
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
ConnectionPoint.loadArray = function (v) {
    var newConnectionPoints = [];

    for (var i = 0; i < v.length; i++) {
        newConnectionPoints.push(ConnectionPoint.load(v[i]));
    }

    return newConnectionPoints;
}

/**Clones an array of {ConnectionPoint}s
*@param {Array} v - the array of {ConnectionPoint}s
*@return an {Array} of {ConnectionPoint}s
**/
ConnectionPoint.cloneArray = function (v) {
    var newConnectionPoints = [];
    for (var i = 0; i < v.length; i++) {
        newConnectionPoints.push(v[i].clone());
    }
    return newConnectionPoints;
}


ConnectionPoint.prototype = {
    constructor: ConnectionPoint,


    /**Clone current {ConnectionPoint}
    **/
    clone: function () {
        //parentId,point,id, type
        return new ConnectionPoint(this.parentId, this.point.clone(), this.id, this.type);
    },

    /**Compares to another ConnectionPoint
    *@param {ConnectionPoint} anotherConnectionPoint - the other connection point
    *@return {Boolean} - true if equals, false otherwise
    **/
    equals: function (anotherConnectionPoint) {

        return this.id == anotherConnectionPoint.id
        && this.point.equals(anotherConnectionPoint.point)
        && this.parentId == anotherConnectionPoint.parentId
        && this.type == anotherConnectionPoint.type
        && this.color == anotherConnectionPoint.color
        && this.radius == anotherConnectionPoint.radius;
    },

    /**
    *Paints the ConnectionPoint into a Context
    *@param {Context} context - the 2D context
    **/
    paint: function (context) {
        context.save();
        context.fillStyle = this.color;
        context.strokeStyle = '#000000';
        context.lineWidth = defaultThinLineWidth;
        context.beginPath();
        context.arc(this.point.x, this.point.y, ConnectionPoint.RADIUS, 0, (Math.PI / 180) * 360, false);
        context.stroke();
        context.fill();
        context.restore();
    },


    /**
    *Transform the ConnectionPoint through a Matrix
    *@param {Matrix} matrix - the transformation matrix
    **/
    transform: function (matrix) {
        this.point.transform(matrix);
    },


    /**Highlight the connection point*/
    highlight: function () {
        this.color = ConnectionPoint.OVER_COLOR;
    },

    /**Un-highlight the connection point*/
    unhighlight: function () {
        this.color = ConnectionPoint.NORMAL_COLOR;
    },


    /**Tests to see if a point (x, y) is within a range of current ConnectionPoint
    *@param {Numeric} x - the x coordinate of tested point
    *@param {Numeric} y - the x coordinate of tested point
    *@return {Boolean} - true if inside, false otherwise
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    contains: function (x, y) {
        return this.near(x, y, ConnectionPoint.RADIUS);
    },

    /**Tests to see if a point (x, y) is within a specified range of current ConnectionPoint
    *@param {Numeric} x - the x coordinate of tested point
    *@param {Numeric} y - the x coordinate of tested point
    *@param {Numeric} radius - the radius around this point
    *@return {Boolean} - true if inside, false otherwise
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    near: function (x, y, radius) {
        return new Point(this.point.x, this.point.y).near(x, y, radius);
    },


    /**A String representation of the point*/
    toString: function () {
        return "ConnectionPoint id = " + this.id + ' point = [' + this.point + '] ,type = ' + this.type + ", parentId = " + this.parentId + ")";
    }
}



/**A Glue just glues together 2 ConnectionPoints.
*Glued ConnectionPoints usually belongs to a Connector and a Figure.
*
*@constructor
*@this {Glue}
*@param {Number} cp1Id - the id of the first {ConnectionPoint} (usually from a {Figure})
*@param {Number} cp2Id - the id of the second {ConnectionPoint} (usualy from a {Connector})
*@param {Boolean} automatic - type of connection connector to a figure:
* if true - {Connector} connects a {Figure} itself
* else - {Connector} connects specific {ConnectionPoint} of {Figure}
**/
function Glue(cp1Id, cp2Id, automatic) {
    /**First shape's id (usually from a {Figure})*/
    this.id1 = cp1Id;

    /**Second shape's id (usualy from a {Connector})*/
    this.id2 = cp2Id;

    /*By default all the Glues are created with the first number as Figure's id and second number as
    *Connector's id. In the future glues can be used to glue other types as well*/

    /**First id type (usually 'figure')*/
    this.type1 = 'figure';

    /**First id type (usually 'connector')*/
    this.type2 = 'connector';

    /**object type used for JSON deserialization*/
    this.oType = 'Glue';

    /**Type of connector's behaviour:
    * if it's true - connector connects the whole figure and touches it's optimum connection point
    * if it's false - connector connects one fixed connection point of the figure
    * */
    this.automatic = automatic;
}

/**Creates a {Glue} out of JSON parsed object
*@param {JSONObject} o - the JSON parsed object
*@return {Glue} a newly constructed Glue
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
Glue.load = function (o) {
    var newGlue = new Glue(-1, -1, false); //fake constructor

    newGlue.id1 = o.id1;
    newGlue.id2 = o.id2;
    newGlue.type1 = o.type1;
    newGlue.type2 = o.type2;
    newGlue.automatic = o.automatic ? o.automatic : false;

    return newGlue;
}


/**Creates a an {Array} of {Glue} out of JSON parsed array
*@param {JSONObject} v - the JSON parsed {Array}
*@return {Array} of newly loaded {Glue}s
*@author Alex Gheorghiu <alex@scriptoid.com>
**/
Glue.loadArray = function (v) {
    var newGlues = [];

    for (var i = 0; i < v.length; i++) {
        newGlues.push(Glue.load(v[i]));
    }

    return newGlues;
}

/**Clones an array of points
*@param {Array} v - the array of {Glue}s
*@return an {Array} of {Glue}s
**/
Glue.cloneArray = function (v) {
    var newGlues = [];
    for (var i = 0; i < v.length; i++) {
        newGlues.push(v[i].clone());
    }
    return newGlues;
}

Glue.prototype = {

    constructor: Glue,


    /**Clone current {Glue}
    **/
    clone: function () {
        return new Glue(this.id1, this.id2, this.automatic);
    },

    /**Compares to another Glue
    *@param {Glue} anotherGlue -  - the other glue
    *@return {Boolean} - true if equals, false otherwise
    **/
    equals: function (anotherGlue) {
        if (!anotherGlue instanceof Glue) {
            return false;
        }

        return this.id1 == anotherGlue.id1
        && this.id2 == anotherGlue.id2
        && this.automatic == anotherGlue.automatic
        && this.type1 == anotherGlue.type1
        && this.type2 == anotherGlue.type2;
    },

    /**String representation of the Glue
    *@return {String} - the representation
    **/
    toString: function () {
        return 'Glue : (id1 = ' + this.id1
            + ', id2 = ' + this.id2
            + ', type1 = ' + this.type1
            + ', type2 = ' + this.type2
            + ', automatic = ' + this.automatic
            + ')';
    }
}