using System;
using System.Collections.Generic;
using System.Windows;

namespace HandyControl.Expression.Drawing
{
    internal static class BezierCurveFlattener
    {
        public const double StandardFlatteningTolerance = 0.25;

        private static void DoCubicForwardDifferencing(Point[] controlPoints, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection<Point> resultPolyline, ICollection<double> resultParameters)
        {
            double num14;
            var num = controlPoints[1].X - controlPoints[0].X;
            var num2 = controlPoints[1].Y - controlPoints[0].Y;
            var num3 = controlPoints[2].X - controlPoints[1].X;
            var num4 = controlPoints[2].Y - controlPoints[1].Y;
            var num5 = controlPoints[3].X - controlPoints[2].X;
            var num6 = controlPoints[3].Y - controlPoints[2].Y;
            var num7 = num3 - num;
            var num8 = num4 - num2;
            var num9 = num5 - num3;
            var num10 = num6 - num4;
            var num11 = num9 - num7;
            var num12 = num10 - num8;
            var vector = controlPoints[3].Subtract(controlPoints[0]);
            var length = vector.Length;
            if (!MathHelper.IsVerySmall(length))
                num14 = Math.Max(0.0,
                    Math.Max(Math.Abs((num7 * vector.Y - num8 * vector.X) / length),
                        Math.Abs((num9 * vector.Y - num10 * vector.X) / length)));
            else
                num14 = Math.Max(0.0,
                    Math.Max(GeometryHelper.Distance(controlPoints[1], controlPoints[0]),
                        GeometryHelper.Distance(controlPoints[2], controlPoints[0])));
            uint num15 = 0;
            if (num14 > 0.0)
            {
                var d = num14 * inverseErrorTolerance;
                num15 = d < 2147483647.0 ? Log4UnsignedInt32((uint) (d + 0.5)) : Log4Double(d);
            }

            var exp = (int) -num15;
            var num18 = exp + exp;
            var num19 = num18 + exp;
            var num20 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num7, num18);
            var num21 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num8, num18);
            var num22 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num11, num19);
            var num23 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num12, num19);
            var num24 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num, exp) + num20 +
                        0.16666666666666666 * num22;
            var num25 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num2, exp) + num21 +
                        0.16666666666666666 * num23;
            var num26 = 2.0 * num20 + num22;
            var num27 = 2.0 * num21 + num23;
            var x = controlPoints[0].X;
            var y = controlPoints[0].Y;
            var item = new Point(0.0, 0.0);
            var num30 = 1 << (int) num15;
            var num31 = num30 > 0 ? (rightParameter - leftParameter) / num30 : 0.0;
            var num32 = leftParameter;
            for (var i = 1; i < num30; i++)
            {
                x += num24;
                y += num25;
                item.X = x;
                item.Y = y;
                resultPolyline.Add(item);
                num32 += num31;
                resultParameters?.Add(num32);
                num24 += num26;
                num25 += num27;
                num26 += num22;
                num27 += num23;
            }
        }

        private static void DoCubicMidpointSubdivision(Point[] controlPoints, uint depth, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection<Point> resultPolyline, ICollection<double> resultParameters)
        {
            Point[] pointArray = { controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3] };
            var pointArray2 = new Point[4];
            pointArray2[3] = pointArray[3];
            pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
            pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
            pointArray[1] = GeometryHelper.Midpoint(pointArray[1], pointArray[0]);
            pointArray2[2] = pointArray[3];
            pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
            pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
            pointArray2[1] = pointArray[3];
            pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
            pointArray2[0] = pointArray[3];
            depth--;
            var num = (leftParameter + rightParameter) * 0.5;
            if (depth > 0)
            {
                DoCubicMidpointSubdivision(pointArray, depth, leftParameter, num, inverseErrorTolerance,
                    resultPolyline, resultParameters);
                resultPolyline.Add(pointArray2[0]);
                resultParameters?.Add(num);
                DoCubicMidpointSubdivision(pointArray2, depth, num, rightParameter, inverseErrorTolerance,
                    resultPolyline, resultParameters);
            }
            else
            {
                DoCubicForwardDifferencing(pointArray, leftParameter, num, inverseErrorTolerance,
                    resultPolyline, resultParameters);
                resultPolyline.Add(pointArray2[0]);
                resultParameters?.Add(num);
                DoCubicForwardDifferencing(pointArray2, num, rightParameter, inverseErrorTolerance,
                    resultPolyline, resultParameters);
            }
        }

        private static void EnsureErrorTolerance(ref double errorTolerance)
        {
            if (errorTolerance <= 0.0)
            {
                errorTolerance = 0.25;
            }
        }

        public static void FlattenCubic(Point[] controlPoints, double errorTolerance, ICollection<Point> resultPolyline, bool skipFirstPoint, ICollection<double> resultParameters = null)
        {
            if (resultPolyline == null) throw new ArgumentNullException(nameof(resultPolyline));
            if (controlPoints == null) throw new ArgumentNullException(nameof(controlPoints));
            if (controlPoints.Length != 4) throw new ArgumentOutOfRangeException(nameof(controlPoints));
            EnsureErrorTolerance(ref errorTolerance);
            if (!skipFirstPoint)
            {
                resultPolyline.Add(controlPoints[0]);
                resultParameters?.Add(0.0);
            }

            if (IsCubicChordMonotone(controlPoints, errorTolerance * errorTolerance))
            {
                var flattener = new AdaptiveForwardDifferencingCubicFlattener(controlPoints, errorTolerance,
                    errorTolerance, true);
                var p = new Point();
                var u = 0.0;
                while (flattener.Next(ref p, ref u))
                {
                    resultPolyline.Add(p);
                    resultParameters?.Add(u);
                }
            }
            else
            {
                var x = controlPoints[3].X - controlPoints[2].X + controlPoints[1].X - controlPoints[0].X;
                var y = controlPoints[3].Y - controlPoints[2].Y + controlPoints[1].Y - controlPoints[0].Y;
                var num4 = 1.0 / errorTolerance;
                var depth = Log8UnsignedInt32((uint) (MathHelper.Hypotenuse(x, y) * num4 + 0.5));
                if (depth > 0) depth--;
                if (depth > 0)
                    DoCubicMidpointSubdivision(controlPoints, depth, 0.0, 1.0, 0.75 * num4, resultPolyline,
                        resultParameters);
                else
                    DoCubicForwardDifferencing(controlPoints, 0.0, 1.0, 0.75 * num4, resultPolyline,
                        resultParameters);
            }

            resultPolyline.Add(controlPoints[3]);
            resultParameters?.Add(1.0);
        }

        public static void FlattenQuadratic(Point[] controlPoints, double errorTolerance, ICollection<Point> resultPolyline, bool skipFirstPoint, ICollection<double> resultParameters = null)
        {
            if (resultPolyline == null)
            {
                throw new ArgumentNullException(nameof(resultPolyline));
            }
            if (controlPoints == null)
            {
                throw new ArgumentNullException(nameof(controlPoints));
            }
            if (controlPoints.Length != 3)
            {
                throw new ArgumentOutOfRangeException(nameof(controlPoints));
            }
            EnsureErrorTolerance(ref errorTolerance);
            Point[] pointArray = { controlPoints[0], GeometryHelper.Lerp(controlPoints[0], controlPoints[1], 0.66666666666666663), GeometryHelper.Lerp(controlPoints[1], controlPoints[2], 0.33333333333333331), controlPoints[2] };
            FlattenCubic(pointArray, errorTolerance, resultPolyline, skipFirstPoint, resultParameters);
        }

        private static bool IsCubicChordMonotone(Point[] controlPoints, double squaredTolerance)
        {
            double num = GeometryHelper.SquaredDistance(controlPoints[0], controlPoints[3]);
            if (num <= squaredTolerance)
            {
                return false;
            }
            Vector lhs = controlPoints[3].Subtract(controlPoints[0]);
            Vector rhs = controlPoints[1].Subtract(controlPoints[0]);
            double num2 = GeometryHelper.Dot(lhs, rhs);
            if ((num2 < 0.0) || (num2 > num))
            {
                return false;
            }
            Vector vector3 = controlPoints[2].Subtract(controlPoints[0]);
            double num3 = GeometryHelper.Dot(lhs, vector3);
            if ((num3 < 0.0) || (num3 > num))
            {
                return false;
            }
            if (num2 > num3)
            {
                return false;
            }
            return true;
        }

        private static uint Log4Double(double d)
        {
            uint num = 0;
            while (d > 1.0)
            {
                d *= 0.25;
                num++;
            }
            return num;
        }

        private static uint Log4UnsignedInt32(uint i)
        {
            uint num = 0;
            while (i > 0)
            {
                i = i >> 2;
                num++;
            }
            return num;
        }

        private static uint Log8UnsignedInt32(uint i)
        {
            uint num = 0;
            while (i > 0)
            {
                i = i >> 3;
                num++;
            }
            return num;
        }

        private class AdaptiveForwardDifferencingCubicFlattener
        {
            private double _aX;

            private double _aY;

            private double _bX;

            private double _bY;

            private double _cX;

            private double _cY;

            private readonly double _distanceTolerance;

            private readonly bool _doParameters;

            private double _dParameter;

            private double _dX;

            private double _dY;

            private readonly double _flatnessTolerance;

            private int _numSteps;

            private double _parameter;

            internal AdaptiveForwardDifferencingCubicFlattener(Point[] controlPoints,
                double flatnessTolerance, double distanceTolerance, bool doParameters)
            {
                _numSteps = 1;
                _dParameter = 1.0;
                _flatnessTolerance = 3.0 * flatnessTolerance;
                _distanceTolerance = distanceTolerance;
                _doParameters = doParameters;
                _aX = -controlPoints[0].X + 3.0 * (controlPoints[1].X - controlPoints[2].X) +
                     controlPoints[3].X;
                _aY = -controlPoints[0].Y + 3.0 * (controlPoints[1].Y - controlPoints[2].Y) +
                     controlPoints[3].Y;
                _bX = 3.0 * (controlPoints[0].X - 2.0 * controlPoints[1].X + controlPoints[2].X);
                _bY = 3.0 * (controlPoints[0].Y - 2.0 * controlPoints[1].Y + controlPoints[2].Y);
                _cX = 3.0 * (-controlPoints[0].X + controlPoints[1].X);
                _cY = 3.0 * (-controlPoints[0].Y + controlPoints[1].Y);
                _dX = controlPoints[0].X;
                _dY = controlPoints[0].Y;
            }

            private void DoubleStepSize()
            {
                _aX *= 8.0;
                _aY *= 8.0;
                _bX *= 4.0;
                _bY *= 4.0;
                _cX += _cX;
                _cY += _cY;
                if (_doParameters) _dParameter *= 2.0;
                _numSteps = _numSteps >> 1;
            }

            private void HalveStepSize()
            {
                _aX *= 0.125;
                _aY *= 0.125;
                _bX *= 0.25;
                _bY *= 0.25;
                _cX *= 0.5;
                _cY *= 0.5;
                if (_doParameters) _dParameter *= 0.5;
                _numSteps = _numSteps << 1;
            }

            private void IncrementDifferencesAndParameter()
            {
                _dX = _aX + _bX + _cX + _dX;
                _dY = _aY + _bY + _cY + _dY;
                _cX = _aX + _aX + _aX + _bX + _bX + _cX;
                _cY = _aY + _aY + _aY + _bY + _bY + _cY;
                _bX = _aX + _aX + _aX + _bX;
                _bY = _aY + _aY + _aY + _bY;
                _numSteps--;
                _parameter += _dParameter;
            }

            private bool MustSubdivide(double flatnessTolerance)
            {
                var num = -(_aY + _bY + _cY);
                var num2 = _aX + _bX + _cX;
                var num3 = Math.Abs(num) + Math.Abs(num2);
                if (num3 <= _distanceTolerance) return false;
                num3 *= flatnessTolerance;
                if (Math.Abs(_cX * num + _cY * num2) <= num3 &&
                    Math.Abs((_bX + _cX + _cX) * num + (_bY + _cY + _cY) * num2) <= num3) return false;
                return true;
            }

            // ReSharper disable once RedundantAssignment
            internal bool Next(ref Point p, ref double u)
            {
                while (MustSubdivide(_flatnessTolerance)) HalveStepSize();
                if ((_numSteps & 1) == 0)
                    while (_numSteps > 1 && !MustSubdivide(_flatnessTolerance * 0.25))
                        DoubleStepSize();
                IncrementDifferencesAndParameter();
                p.X = _dX;
                p.Y = _dY;
                u = _parameter;
                return _numSteps != 0;
            }
        }
    }
}
