// Defines the anchors for a node; anchors can be not symmetrical in general
//
//           |TopAnchor
//Left anchor|
// ======Origin==================RightAnchor
//           |
//           |
//           |BottomAnchor
import { Point, TriangleOrientation } from './../../math/geometry/point';
import { Polyline } from './../../math/geometry/polyline';
import { Curve } from './../../math/geometry/curve';
import { GeomConstants } from './../../math/geometry/geomConstants';
// import {Assert} from './../../utils/assert'
export class Anchor {
    constructor(labelCornersPreserveCoefficient) {
        this.padding = 0;
        this.alreadySitsOnASpline = false;
        // An anchor for an edge label with the label to the left of the spline has its height equal to the one of the label
        // Its rightAnchor is a reserved space for the spline and the leftAnchor is equal to the label width.
        this.labelIsToTheLeftOfTheSpline = false;
        // An anchor for an edge label with the label to the right of the spline has its height equal to the one of the label
        // Its leftAnchor is a reserved space for the spline and the rightAnchor is equal to the label width.
        this.labelIsToTheRightOfTheSpline = false;
        /*Assert.assert(
          0 <= labelCornersPreserveCoefficient &&
            labelCornersPreserveCoefficient <= 1,
        )*/
        this.labelCornersPreserveCoefficient = labelCornersPreserveCoefficient;
    }
    // ToString
    toString() {
        return 'la:ra ' + this.la + ' ' + this.ra + ' ta:ba ' + this.ta + ' ' + this.ba + ' x:y ' + this.x_ + ' ' + this.y_;
    }
    // distance for the center of the node to its left boundary
    get leftAnchor() {
        return this.la;
    }
    set leftAnchor(value) {
        //the absence of this check allows a situation when an edge crosses its label or
        // a label which does not belong to the edge
        //       if(value<-Curve.DistEps)
        //       throw new Exception("assigning negative value to a anchor");
        this.la = Math.max(value, 0);
    }
    // distance from the center of the node to its right boundary
    get rightAnchor() {
        return this.ra;
    }
    set rightAnchor(value) {
        //   if(value<-Curve.DistEps)
        //   throw new Exception("assigning negative value to a anchor: "+value );
        this.ra = Math.max(value, 0);
    }
    // distance from the center of the node to its top boundary
    get topAnchor() {
        return this.ta;
    }
    set topAnchor(value) {
        //if(value<-Curve.DistEps)
        //throw new Exception("assigning negative value to a anchor");
        this.ta = Math.max(value, 0);
    }
    get bottomAnchor() {
        return this.ba;
    }
    set bottomAnchor(value) {
        //if(value<-Curve.DistEps)
        //throw new Error();//"assigning negative value to a anchor");
        this.ba = Math.max(value, 0);
    }
    // Left boundary of the node
    get left() {
        return this.x_ - this.la;
    }
    // right boundary of the node
    get right() {
        return this.x_ + this.ra;
    }
    // top boundary of the node
    get top() {
        return this.y_ + this.ta;
    }
    set top(value) {
        this.y_ += value - this.ta;
    }
    // bottom of the node
    get bottom() {
        return this.y_ - this.ba;
    }
    set bottom(value) {
        this.y_ += value - this.ba;
    }
    get leftTop() {
        return new Point(this.left, this.top);
    }
    get leftBottom() {
        return new Point(this.left, this.bottom);
    }
    // this.right bottom of the node
    get rightBottom() {
        return new Point(this.right, this.bottom);
    }
    get node() {
        return this.node_;
    }
    set node(value) {
        this.node_ = value;
        this.polygonalBoundary_ = null;
    }
    // Right top of the node
    get rightTop() {
        return new Point(this.right, this.top);
    }
    // constructor
    static mkAnchor(leftAnchor, rightAnchor, topAnchor, bottomAnchor, node, labelCornersPreserveCoefficient) {
        const a = new Anchor(labelCornersPreserveCoefficient);
        a.la = leftAnchor;
        a.ra = rightAnchor;
        a.ta = topAnchor;
        a.ba = bottomAnchor;
        a.node = node;
        return a;
    }
    // the x position
    get x() {
        return this.x_;
    }
    set x(value) {
        this.polygonalBoundary_ = null;
        this.x_ = value;
    }
    get y() {
        return this.y_;
    }
    set y(value) {
        this.polygonalBoundary_ = null;
        this.y_ = value;
    }
    // Center of the node
    get origin() {
        return new Point(this.x, this.y);
    }
    get width() {
        return this.la + this.ra;
    }
    get height() {
        return this.ta + this.ba;
    }
    // set to true if the anchor has been introduced for a label
    get hasLabel() {
        return this.labelIsToTheLeftOfTheSpline || this.labelIsToTheLeftOfTheSpline;
    }
    get LabelWidth() {
        if (this.labelIsToTheLeftOfTheSpline)
            return this.leftAnchor;
        if (this.labelIsToTheRightOfTheSpline)
            return this.rightAnchor;
        throw new Error();
    }
    // the polygon representing the boundary of a node
    get polygonalBoundary() {
        if (this.polygonalBoundary_ != null)
            return this.polygonalBoundary_;
        return (this.polygonalBoundary_ = Anchor.pad(this.creatPolygonalBoundaryWithoutPadding(), this.padding));
    }
    static pad(curve, padding) {
        if (padding == 0)
            return curve;
        if (Anchor.curveIsConvex(curve)) {
            return Anchor.padConvexCurve(curve, padding);
        }
        else
            return Anchor.padConvexCurve(curve.boundingBox.perimeter(), padding);
    }
    static padCorner(poly, p0, p1, p2, padding) {
        const cornerInfo = Anchor.getPaddedCorner(p0, p1, p2, padding);
        poly.addPoint(cornerInfo.a);
        if (cornerInfo.numberOfPoints == 2)
            poly.addPoint(cornerInfo.b);
    }
    static padConvexCurve(poly, padding) {
        const ret = new Polyline();
        Anchor.padCorner(ret, poly.endPoint.prev, poly.endPoint, poly.startPoint, padding);
        Anchor.padCorner(ret, poly.endPoint, poly.startPoint, poly.startPoint.next, padding);
        for (let pp = poly.startPoint; pp.next.next != null; pp = pp.next)
            Anchor.padCorner(ret, pp, pp.next, pp.next.next, padding);
        ret.closed = true;
        return ret;
    }
    static getPaddedCorner(first, second, third, padding) {
        const u = first.point;
        const v = second.point;
        const w = third.point;
        const ccw = Point.getTriangleOrientation(u, v, w) == TriangleOrientation.Counterclockwise;
        const uv = v.sub(u);
        //uvPerp has to look outside of the curve
        const uvPerp = uv.rotate((ccw ? -Math.PI : Math.PI) / 2).normalize();
        //l is bisector of the corner (u,v,w) pointing out of the corner - outside of the polyline
        const l = uv.normalize().add(v.sub(w).normalize());
        if (l.length < GeomConstants.intersectionEpsilon) {
            return {
                a: v.add(uvPerp.mul(padding)),
                b: null,
                numberOfPoints: 1,
            };
        }
        const d = l.normalize().mul(padding);
        const dp = d.rotate(Math.PI / 2);
        //look for a in the form d+x*dp + v
        //we need to have:  padding = (d+x*dp)*uvPerp
        const xp = (padding - d.dot(uvPerp)) / dp.dot(uvPerp);
        return {
            a: d.add(dp.mul(xp)).add(v),
            b: d.sub(dp.mul(xp)).add(v),
            numberOfPoints: 2, //number of points to add
        };
    }
    static *orientations(poly) {
        yield Point.getTriangleOrientation(poly.endPoint.point, poly.startPoint.point, poly.startPoint.next.point);
        yield Point.getTriangleOrientation(poly.endPoint.prev.point, poly.endPoint.point, poly.startPoint.point);
        let pp = poly.startPoint;
        while (pp.next.next != null) {
            yield Point.getTriangleOrientation(pp.point, pp.next.point, pp.next.next.point);
            pp = pp.next;
        }
    }
    static curveIsConvex(poly) {
        let orientation = TriangleOrientation.Collinear;
        for (const or of Anchor.orientations(poly)) {
            if (or == TriangleOrientation.Collinear)
                continue;
            if (orientation == TriangleOrientation.Collinear)
                orientation = or;
            else if (or != orientation)
                return false;
        }
        return true;
    }
    //private static number TurnAfterSeg(Curve curve, int i) {
    //    return Point.SignedDoubledTriangleArea(curve.segs[i].start, curve.segs[i].End, curve.segs[(i + 1) / curve.segs.Count].End);
    //}
    creatPolygonalBoundaryWithoutPadding() {
        if (this.hasLabel)
            return this.labelIsToTheLeftOfTheSpline ? this.polygonOnLeftLabel() : this.polygonOnRightLabel();
        else if (this.nodeBoundary == null)
            return this.standardRectBoundary();
        else
            return Curve.polylineAroundClosedCurve(this.nodeBoundary);
    }
    get nodeBoundary() {
        return this.node == null ? null : this.node.boundaryCurve;
    }
    standardRectBoundary() {
        const poly = new Polyline();
        poly.addPoint(this.leftTop);
        poly.addPoint(this.rightTop);
        poly.addPoint(this.rightBottom);
        poly.addPoint(this.leftBottom);
        poly.closed = true;
        return poly;
    }
    polygonOnLeftLabel() {
        const t = this.left + (1 - this.labelCornersPreserveCoefficient) * this.LabelWidth;
        const poly = Polyline.mkClosedFromPoints([
            new Point(t, this.top),
            this.rightTop,
            this.rightBottom,
            new Point(t, this.bottom),
            new Point(this.left, this.y),
        ]);
        return poly;
    }
    polygonOnRightLabel() {
        const t = this.right - (1 - this.labelCornersPreserveCoefficient) * this.LabelWidth;
        const poly = Polyline.mkClosedFromPoints([
            new Point(t, this.top),
            new Point(this.right, this.y),
            new Point(t, this.bottom),
            this.leftBottom,
            this.leftTop,
        ]);
        return poly;
    }
    move(p) {
        this.x += p.x;
        this.y += p.y;
    }
}
//# sourceMappingURL=anchor.js.map