import { Curve, GeomConstants, LineSegment, Point } from '../../../math/geometry';
import { Assert } from '../../../utils/assert';
import { Intersections } from './Intersections';
export class BundleInfo {
    constructor(sourceBase, targetBase, obstaclesToIgnore, edgeSeparation, halfWidthArray) {
        this.SourceBase = sourceBase;
        this.TargetBase = targetBase;
        this.obstaclesToIgnore = obstaclesToIgnore;
        this.EdgeSeparation = edgeSeparation;
        this.HalfWidthArray = halfWidthArray;
        this.TotalRequiredWidth = this.EdgeSeparation * this.HalfWidthArray.length + this.HalfWidthArray.reduce((a, b) => a + b, 0) * 2;
        this.longEnoughSideLength = sourceBase.Curve.boundingBox.addRec(targetBase.Curve.boundingBox).diagonal;
        // sometimes TotalRequiredWidth is too large to fit into the circle, so we evenly scale everything
        const mn = Math.max(sourceBase.Curve.boundingBox.diagonal, targetBase.Curve.boundingBox.diagonal);
        if (this.TotalRequiredWidth > mn) {
            const scale = this.TotalRequiredWidth / mn;
            for (let i = 0; i < this.HalfWidthArray.length; i++)
                this.HalfWidthArray[i] /= scale;
            this.TotalRequiredWidth /= scale;
            this.EdgeSeparation /= scale;
        }
    }
    SetParamsFeasiblySymmetrically(tightTree) {
        this.CalculateTightObstaclesForBundle(tightTree, this.obstaclesToIgnore);
        this.SetEndParamsSymmetrically();
    }
    CalculateTightObstaclesForBundle(tightTree, obstaclesToIgnore) {
        const sRadius = this.SourceBase.Curve.boundingBox.diagonal / 2;
        const tRadius = this.TargetBase.Curve.boundingBox.diagonal / 2;
        const bundle = Intersections.Create4gon(this.SourceBase.Position, this.TargetBase.Position, sRadius * 2, tRadius * 2);
        this.tightObstaclesInTheBoundingBox = Array.from(tightTree.AllHitItems(bundle.boundingBox, (p) => !obstaclesToIgnore.has(p) && Curve.ClosedCurveInteriorsIntersect(bundle, p)));
    }
    SetEndParamsSymmetrically() {
        const targetPos = this.TargetBase.Position;
        const sourcePos = this.SourceBase.Position;
        const dir = targetPos.sub(sourcePos).normalize();
        const perp = dir.rotate90Ccw();
        const middle = Point.middle(targetPos, sourcePos);
        const mdir = dir.mul(this.longEnoughSideLength);
        const a = middle.add(mdir);
        const b = middle.sub(mdir);
        //  [a,b] is a long enough segment
        // we are already fine
        if (this.SetRLParamsIfWidthIsFeasible(perp.mul(this.TotalRequiredWidth / 2), a, b)) {
            this.SetInitialMidParams();
            return;
        }
        // find the segment using binary search
        let uw = this.TotalRequiredWidth;
        let lw = 0;
        let mw = uw / 2;
        while (uw - lw > BundleInfo.FeasibleWidthEpsilon) {
            if (this.SetRLParamsIfWidthIsFeasible(perp.mul(mw / 2), a, b)) {
                lw = mw;
            }
            else {
                uw = mw;
            }
            mw = 0.5 * (uw + lw);
        }
        if (mw <= BundleInfo.FeasibleWidthEpsilon) {
            // try one side
            if (this.SetRLParamsIfWidthIsFeasible_(perp.mul(BundleInfo.FeasibleWidthEpsilon), new Point(0, 0), a, b) ||
                this.SetRLParamsIfWidthIsFeasible_(new Point(0, 0), perp.mul(-BundleInfo.FeasibleWidthEpsilon), a, b)) {
                mw = 2 * BundleInfo.FeasibleWidthEpsilon;
            }
        }
        Assert.assert(mw > BundleInfo.FeasibleWidthEpsilon);
        this.SetInitialMidParams();
    }
    SetRLParamsIfWidthIsFeasible(perp, a, b) {
        return this.SetRLParamsIfWidthIsFeasible_(perp, perp.neg(), a, b);
    }
    SetRLParamsIfWidthIsFeasible_(perpL, perpR, a, b) {
        const targetLParam = { targetPar: 0 };
        const sourceLParam = { sourcePar: 0 };
        const targetRParam = { targetPar: 0 };
        const sourceRParam = { sourcePar: 0 };
        let ls = this.TrimSegWithBoundaryCurves(LineSegment.mkPP(a.add(perpL), b.add(perpL)), sourceLParam, targetRParam);
        if (ls == null) {
            return false;
        }
        if (this.tightObstaclesInTheBoundingBox.find((t) => Intersections.LineSegmentIntersectPolyline(ls.start, ls.end, t))) {
            return false;
        }
        ls = this.TrimSegWithBoundaryCurves(LineSegment.mkPP(a.add(perpR), b.add(perpR)), sourceRParam, targetLParam);
        if (ls == null) {
            return false;
        }
        if (this.tightObstaclesInTheBoundingBox.find((t) => Intersections.LineSegmentIntersectPolyline(ls.start, ls.end, t))) {
            return false;
        }
        if (this.SourceBase.IsParent) {
            this.SourceBase.ParRight = sourceLParam.sourcePar;
            this.SourceBase.ParLeft = sourceRParam.sourcePar;
        }
        else {
            this.SourceBase.ParRight = sourceRParam.sourcePar;
            this.SourceBase.ParLeft = sourceLParam.sourcePar;
        }
        // SourceBase.InitialMidParameter = SourceBase.AdjustParam(SourceBase.ParRight + SourceBase.Span / 2);
        if (this.TargetBase.IsParent) {
            this.TargetBase.ParRight = targetLParam.targetPar;
            this.TargetBase.ParLeft = targetRParam.targetPar;
        }
        else {
            this.TargetBase.ParRight = targetRParam.targetPar;
            this.TargetBase.ParLeft = targetLParam.targetPar;
        }
        // TargetBase.InitialMidParameter = TargetBase.AdjustParam(TargetBase.ParRight + TargetBase.Span / 2);
        return true;
    }
    SetInitialMidParams() {
        const t = { targetPar: 0 };
        const s = { sourcePar: 0 };
        const ls = this.TrimSegWithBoundaryCurves(LineSegment.mkPP(this.TargetBase.CurveCenter, this.SourceBase.CurveCenter), s, t);
        if (ls != null) {
            this.SourceBase.InitialMidParameter = s.sourcePar;
            this.TargetBase.InitialMidParameter = t.targetPar;
        }
        else {
            this.SourceBase.InitialMidParameter = this.SourceBase.AdjustParam(this.SourceBase.ParRight + this.SourceBase.Span / 2);
            this.TargetBase.InitialMidParameter = this.TargetBase.AdjustParam(this.TargetBase.ParRight + this.TargetBase.Span / 2);
        }
    }
    TrimSegWithBoundaryCurves(ls, s, t) {
        // ls goes from target to source
        let inters = Curve.getAllIntersections(ls, this.SourceBase.Curve, true);
        if (inters.length == 0) {
            t.targetPar = 0;
            s.sourcePar = 0;
            return null;
        }
        let i0;
        if (inters.length == 1)
            i0 = inters[0];
        else {
            if (!this.SourceBase.IsParent)
                i0 = inters[0].par0 < inters[1].par0 ? inters[0] : inters[1];
            else
                i0 = inters[0].par0 < inters[1].par0 ? inters[1] : inters[0];
        }
        inters = Curve.getAllIntersections(ls, this.TargetBase.Curve, true);
        if (inters.length == 0) {
            t.targetPar = 0;
            s.sourcePar = 0;
            return null;
        }
        let i1;
        if (inters.length == 1)
            i1 = inters[0];
        else {
            if (!this.TargetBase.IsParent)
                i1 = inters[0].par0 > inters[1].par0 ? inters[0] : inters[1];
            else
                i1 = inters[0].par0 > inters[1].par0 ? inters[1] : inters[0];
        }
        s.sourcePar = i0.par1;
        t.targetPar = i1.par1;
        return LineSegment.mkPP(i0.x, i1.x);
    }
    RotateBy(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange) {
        const needToUpdateSource = rotationOfSourceRightPoint != 0 || rotationOfSourceLeftPoint != 0;
        const needToUpdateTarget = rotationOfTargetRightPoint != 0 || rotationOfTargetLeftPoint != 0;
        if (needToUpdateSource) {
            this.SourceBase.RotateBy(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, parameterChange);
        }
        if (needToUpdateTarget) {
            this.TargetBase.RotateBy(rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange);
        }
        this.UpdateSourceAndTargetBases(needToUpdateSource, needToUpdateTarget);
    }
    UpdateSourceAndTargetBases(sourceChanged, targetChanged) {
        if (sourceChanged) {
            this.UpdatePointsOnSourceBase();
        }
        if (targetChanged) {
            this.UpdatePointsOnTargetBase();
        }
        this.UpdateTangentsOnBases();
    }
    UpdateTangentsOnBases() {
        const count = this.TargetBase.length;
        // updating tangents
        for (let i = 0; i < count; i++) {
            let d = this.TargetBase.Points[i].sub(this.SourceBase.Points[count - 1 - i]);
            const len = d.length;
            if (len >= GeomConstants.tolerance) {
                d = d.div(len);
                this.TargetBase.Tangents[i] = d;
                this.SourceBase.Tangents[count - 1 - i] = d.neg();
            }
        }
    }
    UpdatePointsOnSourceBase() {
        const count = this.SourceBase.length;
        const pns = this.SourceBase.Points;
        const dir = this.SourceBase.RightPoint.sub(this.SourceBase.LeftPoint).div(this.TotalRequiredWidth);
        pns[count - 1] = this.SourceBase.LeftPoint.add(dir.mul(this.EdgeSeparation / 2 + this.HalfWidthArray[0]));
        let j = 1;
        for (let i = count - 2; i >= 0; i--, j++) {
            pns[i] = pns[i + 1].add(dir.mul(this.HalfWidthArray[j - 1] + (this.EdgeSeparation + this.HalfWidthArray[j])));
        }
    }
    UpdatePointsOnTargetBase() {
        const count = this.TargetBase.length;
        const pns = this.TargetBase.Points;
        const dir = this.TargetBase.LeftPoint.sub(this.TargetBase.RightPoint).div(this.TotalRequiredWidth);
        pns[0] = this.TargetBase.RightPoint.add(dir.mul(this.EdgeSeparation / 2 + this.HalfWidthArray[0]));
        for (let i = 1; i < count; i++) {
            pns[i] = pns[i - 1].add(dir.mul(this.HalfWidthArray[i - 1] + (this.EdgeSeparation + this.HalfWidthArray[i])));
        }
    }
    RotationIsLegal(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange) {
        // 1. we can't have intersections with obstacles
        // (we check borderlines of the bundle only)
        if (!this.SourceBase.IsParent && !this.TargetBase.IsParent) {
            if (rotationOfSourceLeftPoint != 0 || rotationOfTargetRightPoint != 0) {
                const rSoP = this.SourceBase.RotateLeftPoint(rotationOfSourceLeftPoint, parameterChange);
                const lTarP = this.TargetBase.RotateRigthPoint(rotationOfTargetRightPoint, parameterChange);
                if (!this.LineIsLegal(rSoP, lTarP)) {
                    return false;
                }
            }
            if (rotationOfSourceRightPoint != 0 || rotationOfTargetLeftPoint != 0) {
                const lSoP = this.SourceBase.RotateRigthPoint(rotationOfSourceRightPoint, parameterChange);
                const rTarP = this.TargetBase.RotateLeftPoint(rotationOfTargetLeftPoint, parameterChange);
                if (!this.LineIsLegal(lSoP, rTarP)) {
                    return false;
                }
            }
        }
        else {
            if (rotationOfSourceLeftPoint != 0 || rotationOfTargetLeftPoint != 0) {
                const lSoP = this.SourceBase.RotateLeftPoint(rotationOfSourceLeftPoint, parameterChange);
                const lTarP = this.TargetBase.RotateLeftPoint(rotationOfTargetLeftPoint, parameterChange);
                if (!this.LineIsLegal(lSoP, lTarP)) {
                    return false;
                }
            }
            if (rotationOfSourceRightPoint != 0 || rotationOfTargetRightPoint != 0) {
                const rSoP = this.SourceBase.RotateRigthPoint(rotationOfSourceRightPoint, parameterChange);
                const rTarP = this.TargetBase.RotateRigthPoint(rotationOfTargetRightPoint, parameterChange);
                if (!this.LineIsLegal(rSoP, rTarP)) {
                    return false;
                }
            }
        }
        // 2. we are also not allowed to change the order of bundles around a hub
        if (rotationOfSourceRightPoint != 0 || rotationOfSourceLeftPoint != 0) {
            if (!this.SourceBase.RelativeOrderOfBasesIsPreserved(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, parameterChange)) {
                return false;
            }
        }
        if (rotationOfTargetRightPoint != 0 || rotationOfTargetLeftPoint != 0) {
            if (!this.TargetBase.RelativeOrderOfBasesIsPreserved(rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange)) {
                return false;
            }
        }
        return true;
    }
    LineIsLegal(a, b) {
        return this.tightObstaclesInTheBoundingBox.find((t) => Intersections.LineSegmentIntersectPolyline(a, b, t)) == undefined;
    }
}
BundleInfo.FeasibleWidthEpsilon = 0.1;
//# sourceMappingURL=BundleInfo.js.map