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

namespace DeepNestLib
{
    public class Simplify
    {

        // to suit your point format, run search/replace for '.x' and '.y';
        // for 3D version, see 3d branch (configurability would draw significant performance overhead)

        // square distance between 2 points
        public static double getSqDist(SvgPoint p1, SvgPoint p2)
        {

            var dx = p1.x - p2.x;
            var dy = p1.y - p2.y;

            return dx * dx + dy * dy;
        }

        // square distance from a point to a segment
        public static double getSqSegDist(SvgPoint p, SvgPoint p1, SvgPoint p2)
        {

            var x = p1.x;
            var y = p1.y;
            var dx = p2.x - x;
            var dy = p2.y - y;

            if (dx != 0 || dy != 0)
            {

                var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);

                if (t > 1)
                {
                    x = p2.x;
                    y = p2.y;

                }
                else if (t > 0)
                {
                    x += dx * t;
                    y += dy * t;
                }
            }

            dx = p.x - x;
            dy = p.y - y;

            return dx * dx + dy * dy;
        }
        // rest of the code doesn't care about point format

        // basic distance-based simplification
        public static NFP simplifyRadialDist(NFP points, double? sqTolerance)
        {

            var prevPoint = points[0];
            var newPoints = new NFP();
            newPoints.AddPoint(prevPoint);

            SvgPoint point = null;
            int i = 1;
            for (var len = points.length; i < len; i++)
            {
                point = points[i];

                if (point.marked || getSqDist(point, prevPoint) > sqTolerance)
                {
                    newPoints.AddPoint(point);
                    prevPoint = point;
                }
            }

            if (prevPoint != point) newPoints.AddPoint(point);
            return newPoints;
        }


        public static void simplifyDPStep(NFP points, int first, int last, double? sqTolerance, NFP simplified)
        {
            var maxSqDist = sqTolerance;
            var index = -1;
            var marked = false;
            for (var i = first + 1; i < last; i++)
            {
                var sqDist = getSqSegDist(points[i], points[first], points[last]);

                if (sqDist > maxSqDist)
                {
                    index = i;
                    maxSqDist = sqDist;
                }
                /*if(points[i].marked && maxSqDist <= sqTolerance){
                    index = i;
                    marked = true;
                }*/
            }

            /*if(!points[index] && maxSqDist > sqTolerance){
                console.log('shit shit shit');
            }*/

            if (maxSqDist > sqTolerance || marked)
            {
                if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
                simplified.push(points[index]);
                if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
            }
        }

        // simplification using Ramer-Douglas-Peucker algorithm
        public static NFP simplifyDouglasPeucker(NFP points, double? sqTolerance)
        {
            var last = points.length - 1;

            var simplified = new NFP();
            simplified.AddPoint(points[0]);
            simplifyDPStep(points, 0, last, sqTolerance, simplified);
            simplified.push(points[last]);

            return simplified;
        }

        // both algorithms combined for awesome performance
        public static NFP simplify(NFP points, double? tolerance, bool highestQuality)
        {

            if (points.length <= 2) return points;

            var sqTolerance = (tolerance != null) ? (tolerance * tolerance) : 1;

            points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
            points = simplifyDouglasPeucker(points, sqTolerance);

            return points;
        }
    }
}