using System;
using System.Collections.Generic;
using System.Linq;

namespace DeepNestLib
{
    public class GeometryUtil
    {
        // returns true if points are within the given distance
        public static bool _withinDistance(SvgPoint p1, SvgPoint p2, double distance)
        {
            var dx = p1.x - p2.x;
            var dy = p1.y - p2.y;
            return ((dx * dx + dy * dy) < distance * distance);
        }

        // returns an interior NFP for the special case where A is a rectangle
        public static NFP[] noFitPolygonRectangle(NFP A, NFP B)
        {
            var minAx = A[0].x;
            var minAy = A[0].y;
            var maxAx = A[0].x;
            var maxAy = A[0].y;

            for (var i = 1; i < A.Length; i++)
            {
                if (A[i].x < minAx)
                {
                    minAx = A[i].x;
                }
                if (A[i].y < minAy)
                {
                    minAy = A[i].y;
                }
                if (A[i].x > maxAx)
                {
                    maxAx = A[i].x;
                }
                if (A[i].y > maxAy)
                {
                    maxAy = A[i].y;
                }
            }

            var minBx = B[0].x;
            var minBy = B[0].y;
            var maxBx = B[0].x;
            var maxBy = B[0].y;
            for (int i = 1; i < B.Length; i++)
            {
                if (B[i].x < minBx)
                {
                    minBx = B[i].x;
                }
                if (B[i].y < minBy)
                {
                    minBy = B[i].y;
                }
                if (B[i].x > maxBx)
                {
                    maxBx = B[i].x;
                }
                if (B[i].y > maxBy)
                {
                    maxBy = B[i].y;
                }
            }

            if (maxBx - minBx > maxAx - minAx)
            {
                return null;
            }
            if (maxBy - minBy > maxAy - minAy)
            {
                return null;
            }


            var pnts = new NFP[] { new NFP() { Points=new SvgPoint[]{

                new SvgPoint(minAx - minBx + B[0].x, minAy - minBy + B[0].y),
            new SvgPoint(maxAx - maxBx + B[0].x, minAy - minBy + B[0].y),
            new SvgPoint( maxAx - maxBx + B[0].x, maxAy - maxBy + B[0].y),
            new SvgPoint( minAx - minBx + B[0].x, maxAy - maxBy + B[0].y)
            } } };
            return pnts;
        }

  
        // returns the rectangular bounding box of the given polygon
        public static PolygonBounds getPolygonBounds(NFP _polygon)
        {
            return getPolygonBounds(_polygon.Points);
        }
        public static PolygonBounds getPolygonBounds(List<SvgPoint
            > polygon)
        {
            return getPolygonBounds(polygon.ToArray());
        }
        public static PolygonBounds getPolygonBounds(SvgPoint[] polygon)
        {

            if (polygon == null || polygon.Count() < 3)
            {
                throw new ArgumentException("null");
            }

            var xmin = polygon[0].x;
            var xmax = polygon[0].x;
            var ymin = polygon[0].y;
            var ymax = polygon[0].y;

            for (var i = 1; i < polygon.Length; i++)
            {
                if (polygon[i].x > xmax)
                {
                    xmax = polygon[i].x;
                }
                else if (polygon[i].x < xmin)
                {
                    xmin = polygon[i].x;
                }

                if (polygon[i].y > ymax)
                {
                    ymax = polygon[i].y;
                }
                else if (polygon[i].y < ymin)
                {
                    ymin = polygon[i].y;
                }
            }

            var w = xmax - xmin;
            var h = ymax - ymin;
            //return new rectanglef(xmin, ymin, xmax - xmin, ymax - ymin);
            return new PolygonBounds(xmin, ymin, w, h);


        }

        public static bool isRectangle(NFP poly, double? tolerance = null)
        {
            var bb = getPolygonBounds(poly);
            if (tolerance == null)
            {
                tolerance = TOL;
            }


            for (var i = 0; i < poly.Points.Length; i++)
            {
                if (!_almostEqual(poly.Points[i].x, bb.x) && !_almostEqual(poly.Points[i].x, bb.x + bb.width))
                {
                    return false;
                }
                if (!_almostEqual(poly.Points[i].y, bb.y) && !_almostEqual(poly.Points[i].y, bb.y + bb.height))
                {
                    return false;
                }
            }

            return true;
        }

        public static PolygonWithBounds rotatePolygon(NFP polygon, float angle)
        {

            List<SvgPoint> rotated = new List<SvgPoint>();
            angle = (float)(angle * Math.PI / 180.0f);
            for (var i = 0; i < polygon.Points.Length; i++)
            {
                var x = polygon.Points[i].x;
                var y = polygon.Points[i].y;
                var x1 = (float)(x * Math.Cos(angle) - y * Math.Sin(angle));
                var y1 = (float)(x * Math.Sin(angle) + y * Math.Cos(angle));

                rotated.Add(new SvgPoint(x1, y1));
            }
            // reset bounding box
            //RectangleF rr = new RectangleF();

            var ret = new PolygonWithBounds()
            {
                Points = rotated.ToArray()
            };
            var bounds = GeometryUtil.getPolygonBounds(ret);
            ret.x = bounds.x;
            ret.y = bounds.y;
            ret.width = bounds.width;
            ret.height = bounds.height;
            return ret;

        }

        public class PolygonWithBounds : NFP
        {
            public double x;
            public double y;
            public double width;
            public double height;
        }
        public static bool _almostEqual(double a, double b, double? tolerance = null)
        {
            if (tolerance == null)
            {
                tolerance = TOL;
            }
            return Math.Abs(a - b) < tolerance;
        }
        public static bool _almostEqual(double? a, double? b, double? tolerance = null)
        {
            return _almostEqual(a.Value, b.Value, tolerance);
        }
        // returns true if point already exists in the given nfp
        public static bool inNfp(SvgPoint p, NFP[] nfp)
        {
            if (nfp == null || nfp.Length == 0)
            {
                return false;
            }

            for (var i = 0; i < nfp.Length; i++)
            {
                for (var j = 0; j < nfp[i].length; j++)
                {
                    if (_almostEqual(p.x, nfp[i][j].x) && _almostEqual(p.y, nfp[i][j].y))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
        // normalize vector into a unit vector
        public static SvgPoint _normalizeVector(SvgPoint v)
        {
            if (_almostEqual(v.x * v.x + v.y * v.y, 1))
            {
                return v; // given vector was already a unit vector
            }
            var len = Math.Sqrt(v.x * v.x + v.y * v.y);
            var inverse = (float)(1 / len);

            return new SvgPoint(v.x * inverse, v.y * inverse
        );
        }
        public static double? pointDistance(SvgPoint p, SvgPoint s1, SvgPoint s2, SvgPoint normal, bool infinite = false)
        {
            normal = _normalizeVector(normal);

            var dir = new SvgPoint(normal.y, -normal.x);

            var pdot = p.x * dir.x + p.y * dir.y;
            var s1dot = s1.x * dir.x + s1.y * dir.y;
            var s2dot = s2.x * dir.x + s2.y * dir.y;

            var pdotnorm = p.x * normal.x + p.y * normal.y;
            var s1dotnorm = s1.x * normal.x + s1.y * normal.y;
            var s2dotnorm = s2.x * normal.x + s2.y * normal.y;

            if (!infinite)
            {
                if (((pdot < s1dot || _almostEqual(pdot, s1dot)) && (pdot < s2dot || _almostEqual(pdot, s2dot))) || ((pdot > s1dot || _almostEqual(pdot, s1dot)) && (pdot > s2dot || _almostEqual(pdot, s2dot))))
                {
                    return null; // dot doesn't collide with segment, or lies directly on the vertex
                }
                if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm > s1dotnorm && pdotnorm > s2dotnorm))
                {
                    return Math.Min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm);
                }
                if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm < s1dotnorm && pdotnorm < s2dotnorm))
                {
                    return -Math.Min(s1dotnorm - pdotnorm, s2dotnorm - pdotnorm);
                }
            }

            return -(pdotnorm - s1dotnorm + (s1dotnorm - s2dotnorm) * (s1dot - pdot) / (s1dot - s2dot));
        }
        static double TOL = (float)Math.Pow(10, -9); // Floating point error is likely to be above 1 epsilon
                                                     // returns true if p lies on the line segment defined by AB, but not at any endpoints
                                                     // may need work!
        public static bool _onSegment(SvgPoint A, SvgPoint B, SvgPoint p)
        {

            // vertical line
            if (_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x))
            {
                if (!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && p.y < Math.Max(B.y, A.y) && p.y > Math.Min(B.y, A.y))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            // horizontal line
            if (_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y))
            {
                if (!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && p.x < Math.Max(B.x, A.x) && p.x > Math.Min(B.x, A.x))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            //range check
            if ((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y))
            {
                return false;
            }


            // exclude end points
            if ((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y)))
            {
                return false;
            }

            var cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y);

            if (Math.Abs(cross) > TOL)
            {
                return false;
            }

            var dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y);



            if (dot < 0 || _almostEqual(dot, 0))
            {
                return false;
            }

            var len2 = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y);



            if (dot > len2 || _almostEqual(dot, len2))
            {
                return false;
            }

            return true;
        }


        // project each point of B onto A in the given direction, and return the 
        public static double? polygonProjectionDistance(NFP A, NFP B, SvgPoint direction)
        {
            var Boffsetx = B.offsetx ?? 0;
            var Boffsety = B.offsety ?? 0;

            var Aoffsetx = A.offsetx ?? 0;
            var Aoffsety = A.offsety ?? 0;

            A = A.slice(0);
            B = B.slice(0);

            // close the loop for polygons
            if (A[0] != A[A.length - 1])
            {
                A.push(A[0]);
            }

            if (B[0] != B[B.length - 1])
            {
                B.push(B[0]);
            }

            var edgeA = A;
            var edgeB = B;

            double? distance = null;
            SvgPoint p, s1, s2;
            double? d;


            for (var i = 0; i < edgeB.length; i++)
            {
                // the shortest/most negative projection of B onto A
                double? minprojection = null;
                SvgPoint minp = null;
                for (var j = 0; j < edgeA.length - 1; j++)
                {
                    p = new SvgPoint(edgeB[i].x + Boffsetx, edgeB[i].y + Boffsety);
                    s1 = new SvgPoint(edgeA[j].x + Aoffsetx, edgeA[j].y + Aoffsety);
                    s2 = new SvgPoint(edgeA[j + 1].x + Aoffsetx, edgeA[j + 1].y + Aoffsety);

                    if (Math.Abs((s2.y - s1.y) * direction.x - (s2.x - s1.x) * direction.y) < TOL)
                    {
                        continue;
                    }

                    // project point, ignore edge boundaries
                    d = pointDistance(p, s1, s2, direction);

                    if (d != null && (minprojection == null || d < minprojection))
                    {
                        minprojection = d;
                        minp = p;
                    }
                }
                if (minprojection != null && (distance == null || minprojection > distance))
                {
                    distance = minprojection;
                }
            }

            return distance;
        }

        public static double polygonArea(NFP polygon)
        {
            double area = 0;
            int i, j;
            for (i = 0, j = polygon.Points.Length - 1; i < polygon.Points.Length; j = i++)
            {
                area += (polygon.Points[j].x + polygon.Points[i].x) * (polygon.Points[j].y
                    - polygon.Points[i].y);
            }
            return 0.5f * area;
        }

        // return true if point is in the polygon, false if outside, and null if exactly on a point or edge
        public static bool? pointInPolygon(SvgPoint point, NFP polygon)
        {
            if (polygon == null || polygon.Points.Length < 3)
            {
                throw new ArgumentException();
            }

            var inside = false;
            //var offsetx = polygon.offsetx || 0;
            //var offsety = polygon.offsety || 0;
            double offsetx = polygon.offsetx == null ? 0 : polygon.offsetx.Value;
            double offsety = polygon.offsety == null ? 0 : polygon.offsety.Value;

            int i, j;
            for (i = 0, j = polygon.Points.Count() - 1; i < polygon.Points.Length; j = i++)
            {
                var xi = polygon.Points[i].x + offsetx;
                var yi = polygon.Points[i].y + offsety;
                var xj = polygon.Points[j].x + offsetx;
                var yj = polygon.Points[j].y + offsety;

                if (_almostEqual(xi, point.x) && _almostEqual(yi, point.y))
                {

                    return null; // no result
                }

                if (_onSegment(new SvgPoint(xi, yi), new SvgPoint(xj, yj), point))
                {
                    return null; // exactly on the segment
                }

                if (_almostEqual(xi, xj) && _almostEqual(yi, yj))
                { // ignore very small lines
                    continue;
                }

                var intersect = ((yi > point.y) != (yj > point.y)) && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
                if (intersect) inside = !inside;
            }

            return inside;
        }
        // todo: swap this for a more efficient sweep-line implementation
        // returnEdges: if set, return all edges on A that have intersections

        public static bool intersect(NFP A, NFP B)
        {
            var Aoffsetx = A.offsetx ?? 0;
            var Aoffsety = A.offsety ?? 0;

            var Boffsetx = B.offsetx ?? 0;
            var Boffsety = B.offsety ?? 0;

            A = A.slice(0);
            B = B.slice(0);

            for (var i = 0; i < A.length - 1; i++)
            {
                for (var j = 0; j < B.length - 1; j++)
                {
                    var a1 = new SvgPoint(A[i].x + Aoffsetx, A[i].y + Aoffsety);
                    var a2 = new SvgPoint(A[i + 1].x + Aoffsetx, A[i + 1].y + Aoffsety);
                    var b1 = new SvgPoint(B[j].x + Boffsetx, B[j].y + Boffsety);
                    var b2 = new SvgPoint(B[j + 1].x + Boffsetx, B[j + 1].y + Boffsety);

                    var prevbindex = (j == 0) ? B.length - 1 : j - 1;
                    var prevaindex = (i == 0) ? A.length - 1 : i - 1;
                    var nextbindex = (j + 1 == B.length - 1) ? 0 : j + 2;
                    var nextaindex = (i + 1 == A.length - 1) ? 0 : i + 2;

                    // go even further back if we happen to hit on a loop end point
                    if (B[prevbindex] == B[j] || (_almostEqual(B[prevbindex].x, B[j].x) && _almostEqual(B[prevbindex].y, B[j].y)))
                    {
                        prevbindex = (prevbindex == 0) ? B.length - 1 : prevbindex - 1;
                    }

                    if (A[prevaindex] == A[i] || (_almostEqual(A[prevaindex].x, A[i].x) && _almostEqual(A[prevaindex].y, A[i].y)))
                    {
                        prevaindex = (prevaindex == 0) ? A.length - 1 : prevaindex - 1;
                    }

                    // go even further forward if we happen to hit on a loop end point
                    if (B[nextbindex] == B[j + 1] || (_almostEqual(B[nextbindex].x, B[j + 1].x) && _almostEqual(B[nextbindex].y, B[j + 1].y)))
                    {
                        nextbindex = (nextbindex == B.length - 1) ? 0 : nextbindex + 1;
                    }

                    if (A[nextaindex] == A[i + 1] || (_almostEqual(A[nextaindex].x, A[i + 1].x) && _almostEqual(A[nextaindex].y, A[i + 1].y)))
                    {
                        nextaindex = (nextaindex == A.length - 1) ? 0 : nextaindex + 1;
                    }

                    var a0 = new SvgPoint(A[prevaindex].x + Aoffsetx, A[prevaindex].y + Aoffsety);
                    var b0 = new SvgPoint(B[prevbindex].x + Boffsetx, B[prevbindex].y + Boffsety);

                    var a3 = new SvgPoint(A[nextaindex].x + Aoffsetx, A[nextaindex].y + Aoffsety);
                    var b3 = new SvgPoint(B[nextbindex].x + Boffsetx, B[nextbindex].y + Boffsety);

                    if (_onSegment(a1, a2, b1) || (_almostEqual(a1.x, b1.x) && _almostEqual(a1.y, b1.y)))
                    {
                        // if a point is on a segment, it could intersect or it could not. Check via the neighboring points
                        var b0in = pointInPolygon(b0, A);
                        var b2in = pointInPolygon(b2, A);
                        if ((b0in == true && b2in == false) || (b0in == false && b2in == true))
                        {
                            return true;
                        }
                        else
                        {
                            continue;
                        }
                    }

                    if (_onSegment(a1, a2, b2) || (_almostEqual(a2.x, b2.x) && _almostEqual(a2.y, b2.y)))
                    {
                        // if a point is on a segment, it could intersect or it could not. Check via the neighboring points
                        var b1in = pointInPolygon(b1, A);
                        var b3in = pointInPolygon(b3, A);

                        if ((b1in == true && b3in == false) || (b1in == false && b3in == true))
                        {
                            return true;
                        }
                        else
                        {
                            continue;
                        }
                    }

                    if (_onSegment(b1, b2, a1) || (_almostEqual(a1.x, b2.x) && _almostEqual(a1.y, b2.y)))
                    {
                        // if a point is on a segment, it could intersect or it could not. Check via the neighboring points
                        var a0in = pointInPolygon(a0, B);
                        var a2in = pointInPolygon(a2, B);

                        if ((a0in == true && a2in == false) || (a0in == false && a2in == true))
                        {
                            return true;
                        }
                        else
                        {
                            continue;
                        }
                    }

                    if (_onSegment(b1, b2, a2) || (_almostEqual(a2.x, b1.x) && _almostEqual(a2.y, b1.y)))
                    {
                        // if a point is on a segment, it could intersect or it could not. Check via the neighboring points
                        var a1in = pointInPolygon(a1, B);
                        var a3in = pointInPolygon(a3, B);

                        if ((a1in == true && a3in == false) || (a1in == false && a3in == true))
                        {
                            return true;
                        }
                        else
                        {
                            continue;
                        }
                    }

                    var p = _lineIntersect(b1, b2, a1, a2);

                    if (p != null)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public static bool isFinite(object obj)
        {
            return true;
        }
        // returns the intersection of AB and EF
        // or null if there are no intersections or other numerical error
        // if the infinite flag is set, AE and EF describe infinite lines without endpoints, they are finite line segments otherwise
        public static SvgPoint _lineIntersect(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, bool infinite = false)
        {
            double a1, a2, b1, b2, c1, c2, x, y;

            a1 = B.y - A.y;
            b1 = A.x - B.x;
            c1 = B.x * A.y - A.x * B.y;
            a2 = F.y - E.y;
            b2 = E.x - F.x;
            c2 = F.x * E.y - E.x * F.y;

            var denom = a1 * b2 - a2 * b1;

            x = (b1 * c2 - b2 * c1) / denom;
            y = (a2 * c1 - a1 * c2) / denom;


            if (!isFinite(x) || !isFinite(y))
            {
                return null;
            }

            // lines are colinear
            /*var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y);
            var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y);
            if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){
                return null;
            }*/

            if (!infinite)
            {
                // coincident points do not count as intersecting
                if (Math.Abs(A.x - B.x) > TOL && ((A.x < B.x) ? x < A.x || x > B.x : x > A.x || x < B.x)) return null;
                if (Math.Abs(A.y - B.y) > TOL && ((A.y < B.y) ? y < A.y || y > B.y : y > A.y || y < B.y)) return null;

                if (Math.Abs(E.x - F.x) > TOL && ((E.x < F.x) ? x < E.x || x > F.x : x > E.x || x < F.x)) return null;
                if (Math.Abs(E.y - F.y) > TOL && ((E.y < F.y) ? y < E.y || y > F.y : y > E.y || y < F.y)) return null;
            }

            return new SvgPoint(x, y);
        }

        // searches for an arrangement of A and B such that they do not overlap
        // if an NFP is given, only search for startpoints that have not already been traversed in the given NFP

        public static SvgPoint searchStartPoint(NFP A, NFP B, bool inside, NFP[] NFP = null)
        {
            // clone arrays
            A = A.slice(0);
            B = B.slice(0);

            // close the loop for polygons
            if (A[0] != A[A.length - 1])
            {
                A.push(A[0]);
            }

            if (B[0] != B[B.length - 1])
            {
                B.push(B[0]);
            }

            for (var i = 0; i < A.length - 1; i++)
            {
                if (!A[i].marked)
                {
                    A[i].marked = true;
                    for (var j = 0; j < B.length; j++)
                    {
                        B.offsetx = A[i].x - B[j].x;
                        B.offsety = A[i].y - B[j].y;

                        bool? Binside = null;
                        for (var k = 0; k < B.length; k++)
                        {
                            var inpoly = pointInPolygon(new SvgPoint(B[k].x + B.offsetx.Value,
                                B[k].y + B.offsety.Value), A);
                            if (inpoly != null)
                            {
                                Binside = inpoly;
                                break;
                            }
                        }

                        if (Binside == null)
                        { // A and B are the same
                            return null;
                        }

                        var startPoint = new SvgPoint(B.offsetx.Value, B.offsety.Value);
                        if (((Binside.Value && inside) || (!Binside.Value && !inside)) &&
                            !intersect(A, B) && !inNfp(startPoint, NFP))
                        {
                            return startPoint;
                        }

                        // slide B along vector
                        var vx = A[i + 1].x - A[i].x;
                        var vy = A[i + 1].y - A[i].y;

                        var d1 = polygonProjectionDistance(A, B, new SvgPoint(vx, vy));
                        var d2 = polygonProjectionDistance(B, A, new SvgPoint(-vx, -vy));

                        double? d = null;

                        // todo: clean this up
                        if (d1 == null && d2 == null)
                        {
                            // nothin
                        }
                        else if (d1 == null)
                        {
                            d = d2;
                        }
                        else if (d2 == null)
                        {
                            d = d1;
                        }
                        else
                        {
                            d = Math.Min(d1.Value, d2.Value);
                        }

                        // only slide until no longer negative
                        // todo: clean this up
                        if (d != null && !_almostEqual(d, 0) && d > 0)
                        {

                        }
                        else
                        {
                            continue;
                        }

                        var vd2 = vx * vx + vy * vy;

                        if (d * d < vd2 && !_almostEqual(d * d, vd2))
                        {
                            var vd = (float)Math.Sqrt(vx * vx + vy * vy);
                            vx *= d.Value / vd;
                            vy *= d.Value / vd;
                        }

                        B.offsetx += vx;
                        B.offsety += vy;

                        for (var k = 0; k < B.length; k++)
                        {
                            var inpoly = pointInPolygon(
                                new SvgPoint(
                                 B[k].x + B.offsetx.Value, B[k].y + B.offsety.Value), A);
                            if (inpoly != null)
                            {
                                Binside = inpoly;
                                break;
                            }
                        }
                        startPoint =
                                            new SvgPoint(B.offsetx.Value, B.offsety.Value);
                        if (((Binside.Value && inside) || (!Binside.Value && !inside)) &&
                            !intersect(A, B) && !inNfp(startPoint, NFP))
                        {
                            return startPoint;
                        }
                    }
                }
            }



            return null;
        }

        public class TouchingItem
        {
            public TouchingItem(int _type, int _a, int _b)
            {
                A = _a;
                B = _b;
                type = _type;
            }
            public int A;
            public int B;
            public int type;

        }

        public static double? segmentDistance(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, SvgPoint direction)
        {
            var normal = new SvgPoint(
                direction.y,
                -direction.x

            );

            var reverse = new SvgPoint(
                    -direction.x,
                     -direction.y
                );

            var dotA = A.x * normal.x + A.y * normal.y;
            var dotB = B.x * normal.x + B.y * normal.y;
            var dotE = E.x * normal.x + E.y * normal.y;
            var dotF = F.x * normal.x + F.y * normal.y;

            var crossA = A.x * direction.x + A.y * direction.y;
            var crossB = B.x * direction.x + B.y * direction.y;
            var crossE = E.x * direction.x + E.y * direction.y;
            var crossF = F.x * direction.x + F.y * direction.y;

            var crossABmin = Math.Min(crossA, crossB);
            var crossABmax = Math.Max(crossA, crossB);

            var crossEFmax = Math.Max(crossE, crossF);
            var crossEFmin = Math.Min(crossE, crossF);

            var ABmin = Math.Min(dotA, dotB);
            var ABmax = Math.Max(dotA, dotB);

            var EFmax = Math.Max(dotE, dotF);
            var EFmin = Math.Min(dotE, dotF);

            // segments that will merely touch at one point
            if (_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax, TOL))
            {
                return null;
            }
            // segments miss eachother completely
            if (ABmax < EFmin || ABmin > EFmax)
            {
                return null;
            }

            double overlap;

            if ((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin))
            {
                overlap = 1;
            }
            else
            {
                var minMax = Math.Min(ABmax, EFmax);
                var maxMin = Math.Max(ABmin, EFmin);

                var maxMax = Math.Max(ABmax, EFmax);
                var minMin = Math.Min(ABmin, EFmin);

                overlap = (minMax - maxMin) / (maxMax - minMin);
            }

            var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y);
            var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y);

            // lines are colinear
            if (_almostEqual(crossABE, 0) && _almostEqual(crossABF, 0))
            {

                var ABnorm = new SvgPoint(B.y - A.y, A.x - B.x);
                var EFnorm = new SvgPoint(F.y - E.y, E.x - F.x);

                var ABnormlength = (float)Math.Sqrt(ABnorm.x * ABnorm.x + ABnorm.y * ABnorm.y);
                ABnorm.x /= ABnormlength;
                ABnorm.y /= ABnormlength;

                var EFnormlength = (float)Math.Sqrt(EFnorm.x * EFnorm.x + EFnorm.y * EFnorm.y);
                EFnorm.x /= EFnormlength;
                EFnorm.y /= EFnormlength;

                // segment normals must point in opposite directions
                if (Math.Abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0)
                {
                    // normal of AB segment must point in same direction as given direction vector
                    var normdot = ABnorm.y * direction.y + ABnorm.x * direction.x;
                    // the segments merely slide along eachother
                    if (_almostEqual(normdot, 0, TOL))
                    {
                        return null;
                    }
                    if (normdot < 0)
                    {
                        return 0;
                    }
                }
                return null;
            }

            var distances = new List<double>();

            // coincident points
            if (_almostEqual(dotA, dotE))
            {
                distances.Add(crossA - crossE);
            }
            else if (_almostEqual(dotA, dotF))
            {
                distances.Add(crossA - crossF);
            }
            else if (dotA > EFmin && dotA < EFmax)
            {
                var d = pointDistance(A, E, F, reverse);
                if (d != null && _almostEqual(d, 0))
                { //  A currently touches EF, but AB is moving away from EF
                    var dB = pointDistance(B, E, F, reverse, true);
                    if (dB < 0 || _almostEqual(dB * overlap, 0))
                    {
                        d = null;
                    }
                }
                if (d != null)
                {
                    distances.Add(d.Value);
                }
            }

            if (_almostEqual(dotB, dotE))
            {
                distances.Add(crossB - crossE);
            }
            else if (_almostEqual(dotB, dotF))
            {
                distances.Add(crossB - crossF);
            }
            else if (dotB > EFmin && dotB < EFmax)
            {
                var d = pointDistance(B, E, F, reverse);

                if (d != null && _almostEqual(d, 0))
                { // crossA>crossB A currently touches EF, but AB is moving away from EF
                    var dA = pointDistance(A, E, F, reverse, true);
                    if (dA < 0 || _almostEqual(dA * overlap, 0))
                    {
                        d = null;
                    }
                }
                if (d != null)
                {
                    distances.Add(d.Value);
                }
            }

            if (dotE > ABmin && dotE < ABmax)
            {
                var d = pointDistance(E, A, B, direction);
                if (d != null && _almostEqual(d, 0))
                { // crossF<crossE A currently touches EF, but AB is moving away from EF
                    var dF = pointDistance(F, A, B, direction, true);
                    if (dF < 0 || _almostEqual(dF * overlap, 0))
                    {
                        d = null;
                    }
                }
                if (d != null)
                {
                    distances.Add(d.Value);
                }
            }

            if (dotF > ABmin && dotF < ABmax)
            {
                var d = pointDistance(F, A, B, direction);
                if (d != null && _almostEqual(d, 0))
                { // && crossE<crossF A currently touches EF, but AB is moving away from EF
                    var dE = pointDistance(E, A, B, direction, true);
                    if (dE < 0 || _almostEqual(dE * overlap, 0))
                    {
                        d = null;
                    }
                }
                if (d != null)
                {
                    distances.Add(d.Value);
                }
            }

            if (distances.Count == 0)
            {
                return null;
            }

            //return Math.min.apply(Math, distances);
            return distances.Min();
        }
        public static double? polygonSlideDistance(NFP A, NFP B, nVector direction, bool ignoreNegative)
        {

            SvgPoint A1, A2, B1, B2;
            double Aoffsetx, Aoffsety, Boffsetx, Boffsety;

            Aoffsetx = A.offsetx ?? 0;
            Aoffsety = A.offsety ?? 0;

            Boffsetx = B.offsetx ?? 0;
            Boffsety = B.offsety ?? 0;

            A = A.slice(0);
            B = B.slice(0);

            // close the loop for polygons
            if (A[0] != A[A.length - 1])
            {
                A.push(A[0]);
            }

            if (B[0] != B[B.length - 1])
            {
                B.push(B[0]);
            }

            var edgeA = A;
            var edgeB = B;

            double? distance = null;
            //var p, s1, s2;
            double? d;


            var dir = _normalizeVector(new SvgPoint(direction.x, direction.y));

            var normal = new SvgPoint(
                dir.y,
                 -dir.x
            );

            var reverse = new SvgPoint(-dir.x, -dir.y);

            for (var i = 0; i < edgeB.length - 1; i++)
            {
                //var mind = null;
                for (var j = 0; j < edgeA.length - 1; j++)
                {
                    A1 = new SvgPoint(
                         edgeA[j].x + Aoffsetx, edgeA[j].y + Aoffsety
        );
                    A2 = new SvgPoint(
                        edgeA[j + 1].x + Aoffsetx, edgeA[j + 1].y + Aoffsety
                );
                    B1 = new SvgPoint(edgeB[i].x + Boffsetx, edgeB[i].y + Boffsety);
                    B2 = new SvgPoint(edgeB[i + 1].x + Boffsetx, edgeB[i + 1].y + Boffsety);

                    if ((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) || (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y)))
                    {
                        continue; // ignore extremely small lines
                    }

                    d = segmentDistance(A1, A2, B1, B2, dir);

                    if (d != null && (distance == null || d < distance))
                    {
                        if (!ignoreNegative || d > 0 || _almostEqual(d, 0))
                        {
                            distance = d;
                        }
                    }
                }
            }
            return distance;
        }
        public class nVector
        {
            public SvgPoint start;
            public SvgPoint end;
            public double x;
            public double y;


            public nVector(double v1, double v2, SvgPoint _start, SvgPoint _end)
            {
                this.x = v1;
                this.y = v2;
                this.start = _start;
                this.end = _end;
            }
        }

        // given a static polygon A and a movable polygon B, compute a no fit polygon by orbiting B about A
        // if the inside flag is set, B is orbited inside of A rather than outside
        // if the searchEdges flag is set, all edges of A are explored for NFPs - multiple 
        public static NFP[] noFitPolygon(NFP A, NFP B, bool inside, bool searchEdges)
        {
            if (A == null || A.length < 3 || B == null || B.length < 3)
            {
                return null;
            }

            A.offsetx = 0;
            A.offsety = 0;

            int i = 0, j = 0;

            var minA = A[0].y;
            var minAindex = 0;

            var maxB = B[0].y;
            var maxBindex = 0;

            for (i = 1; i < A.length; i++)
            {
                A[i].marked = false;
                if (A[i].y < minA)
                {
                    minA = A[i].y;
                    minAindex = i;
                }
            }

            for (i = 1; i < B.length; i++)
            {
                B[i].marked = false;
                if (B[i].y > maxB)
                {
                    maxB = B[i].y;
                    maxBindex = i;
                }
            }
            SvgPoint startpoint;
            if (!inside)
            {
                // shift B such that the bottom-most point of B is at the top-most point of A. This guarantees an initial placement with no intersections
                startpoint = new SvgPoint(
                     A[minAindex].x - B[maxBindex].x,
                     A[minAindex].y - B[maxBindex].y);
            }
            else
            {
                // no reliable heuristic for inside

                startpoint = searchStartPoint(A, B, true);
            }

            List<NFP> NFPlist = new List<NFP>();



            while (startpoint != null)
            {

                B.offsetx = startpoint.x;
                B.offsety = startpoint.y;

                // maintain a list of touching points/edges
                List<TouchingItem> touching = null;

                nVector prevvector = null; // keep track of previous vector
                NFP NFP = new NFP();
                /*var NFP = [{
                    x: B[0].x + B.offsetx,
        y: B[0].y + B.offsety





                }];*/
                NFP.push(new SvgPoint(B[0].x + B.offsetx.Value, B[0].y + B.offsety.Value));

                double referencex = B[0].x + B.offsetx.Value;
                double referencey = B[0].y + B.offsety.Value;
                var startx = referencex;
                var starty = referencey;
                var counter = 0;

                while (counter < 10 * (A.length + B.length))
                { // sanity check, prevent infinite loop
                    touching = new List<GeometryUtil.TouchingItem>();
                    // find touching vertices/edges
                    for (i = 0; i < A.length; i++)
                    {
                        var nexti = (i == A.length - 1) ? 0 : i + 1;
                        for (j = 0; j < B.length; j++)
                        {
                            var nextj = (j == B.length - 1) ? 0 : j + 1;
                            if (_almostEqual(A[i].x, B[j].x + B.offsetx) && _almostEqual(A[i].y, B[j].y + B.offsety))
                            {
                                touching.Add(new TouchingItem(0, i, j));
                            }
                            else if (_onSegment(A[i], A[nexti],
                                new SvgPoint(B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value)))
                            {
                                touching.Add(new TouchingItem(1, nexti, j));
                            }
                            else if (_onSegment(
                                new SvgPoint(
                                 B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value),
                                new SvgPoint(
                                 B[nextj].x + B.offsetx.Value, B[nextj].y + B.offsety.Value), A[i]))
                            {
                                touching.Add(new TouchingItem(2, i, nextj));
                            }
                        }
                    }

                    // generate translation vectors from touching vertices/edges
                    var vectors = new List<nVector>();
                    for (i = 0; i < touching.Count; i++)
                    {
                        var vertexA = A[touching[i].A];
                        vertexA.marked = true;

                        // adjacent A vertices
                        var prevAindex = touching[i].A - 1;
                        var nextAindex = touching[i].A + 1;

                        prevAindex = (prevAindex < 0) ? A.length - 1 : prevAindex; // loop
                        nextAindex = (nextAindex >= A.length) ? 0 : nextAindex; // loop

                        var prevA = A[prevAindex];
                        var nextA = A[nextAindex];

                        // adjacent B vertices
                        var vertexB = B[touching[i].B];

                        var prevBindex = touching[i].B - 1;
                        var nextBindex = touching[i].B + 1;

                        prevBindex = (prevBindex < 0) ? B.length - 1 : prevBindex; // loop
                        nextBindex = (nextBindex >= B.length) ? 0 : nextBindex; // loop

                        var prevB = B[prevBindex];
                        var nextB = B[nextBindex];

                        if (touching[i].type == 0)
                        {

                            var vA1 = new nVector(
                                 prevA.x - vertexA.x,
                                 prevA.y - vertexA.y,
                                 vertexA,
                                 prevA
                                    );

                            var vA2 = new nVector(
                                     nextA.x - vertexA.x,
                                     nextA.y - vertexA.y,
                                     vertexA,
                                     nextA
                                        );

                            // B vectors need to be inverted
                            var vB1 = new nVector(
                                         vertexB.x - prevB.x,
                                         vertexB.y - prevB.y,
                                         prevB,
                                         vertexB
                                    );

                            var vB2 = new nVector(
                                             vertexB.x - nextB.x,
                                            vertexB.y - nextB.y,
                                             nextB,
                                             vertexB
                                        );

                            vectors.Add(vA1);
                            vectors.Add(vA2);
                            vectors.Add(vB1);
                            vectors.Add(vB2);
                        }
                        else if (touching[i].type == 1)
                        {
                            vectors.Add(new nVector(
                                 vertexA.x - (vertexB.x + B.offsetx.Value),
                                 vertexA.y - (vertexB.y + B.offsety.Value),
                                 prevA,
                                 vertexA
        ));

                            vectors.Add(new nVector(
                                 prevA.x - (vertexB.x + B.offsetx.Value),
                                prevA.y - (vertexB.y + B.offsety.Value),
                                 vertexA,
                                 prevA
        ));
                        }
                        else if (touching[i].type == 2)
                        {
                            vectors.Add(new nVector(
                                 vertexA.x - (vertexB.x + B.offsetx.Value),
                                vertexA.y - (vertexB.y + B.offsety.Value),
                                 prevB,
                                 vertexB
                            ));

                            vectors.Add(new nVector(
                                 vertexA.x - (prevB.x + B.offsetx.Value),
                                vertexA.y - (prevB.y + B.offsety.Value),
                                 vertexB,
                                 prevB

                            ));
                        }
                    }

                    // todo: there should be a faster way to reject vectors that will cause immediate intersection. For now just check them all

                    nVector translate = null;
                    double maxd = 0;

                    for (i = 0; i < vectors.Count; i++)
                    {
                        if (vectors[i].x == 0 && vectors[i].y == 0)
                        {
                            continue;
                        }

                        // if this vector points us back to where we came from, ignore it.
                        // ie cross product = 0, dot product < 0
                        if (prevvector != null &&
                            vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0)
                        {

                            // compare magnitude with unit vectors
                            var vectorlength = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y);
                            var unitv = new SvgPoint(vectors[i].x / vectorlength, vectors[i].y / vectorlength);

                            var prevlength = (float)Math.Sqrt(prevvector.x * prevvector.x + prevvector.y * prevvector.y);
                            var prevunit = new SvgPoint(prevvector.x / prevlength, prevvector.y / prevlength);

                            // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here
                            if (Math.Abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001)
                            {
                                continue;
                            }
                        }

                        var d = polygonSlideDistance(A, B, vectors[i], true);
                        var vecd2 = vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y;

                        if (d == null || d * d > vecd2)
                        {
                            var vecd = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y);
                            d = vecd;
                        }

                        if (d != null && d > maxd)
                        {
                            maxd = d.Value;
                            translate = vectors[i];
                        }
                    }


                    if (translate == null || _almostEqual(maxd, 0))
                    {
                        // didn't close the loop, something went wrong here
                        NFP = null;
                        break;
                    }

                    translate.start.marked = true;
                    translate.end.marked = true;

                    prevvector = translate;

                    // trim
                    var vlength2 = translate.x * translate.x + translate.y * translate.y;
                    if (maxd * maxd < vlength2 && !_almostEqual(maxd * maxd, vlength2))
                    {
                        var scale = (float)Math.Sqrt((maxd * maxd) / vlength2);
                        translate.x *= scale;
                        translate.y *= scale;
                    }

                    referencex += translate.x;
                    referencey += translate.y;

                    if (_almostEqual(referencex, startx) && _almostEqual(referencey, starty))
                    {
                        // we've made a full loop
                        break;
                    }

                    // if A and B start on a touching horizontal line, the end point may not be the start point
                    var looped = false;
                    if (NFP.length > 0)
                    {
                        for (i = 0; i < NFP.length - 1; i++)
                        {
                            if (_almostEqual(referencex, NFP[i].x) && _almostEqual(referencey, NFP[i].y))
                            {
                                looped = true;
                            }
                        }
                    }

                    if (looped)
                    {
                        // we've made a full loop
                        break;
                    }

                    NFP.push(new SvgPoint(
                         referencex, referencey
                    ));

                    B.offsetx += translate.x;
                    B.offsety += translate.y;

                    counter++;
                }

                if (NFP != null && NFP.length > 0)
                {
                    NFPlist.Add(NFP);

                }

                if (!searchEdges)
                {
                    // only get outer NFP or first inner NFP
                    break;
                }
                startpoint = searchStartPoint(A, B, inside, NFPlist.ToArray());
            }

            return NFPlist.ToArray();
        }
        public static bool pnpoly((double X, double Y)[] verts, double testx, double testy)
        {
            int nvert = verts.Length;
            int i, j;
            bool c = false;
            for (i = 0, j = nvert - 1; i < nvert; j = i++)
            {
                if (((verts[i].Y > testy) != (verts[j].Y > testy)) &&
                    (testx < (verts[j].X - verts[i].X) * (testy - verts[i].Y) / (verts[j].Y - verts[i].Y) + verts[i].X))
                    c = !c;
            }
            return c;
        }



    }
}
