using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;

using Svg.Pathing;

namespace Svg
{
    public static class PointFExtensions
    {
        public static string ToSvgString(this PointF p)
        {
            return p.X.ToString() + " " + p.Y.ToString();
        }
    }

    public class SvgPathBuilder : TypeConverter
    {
        /// <summary>
        /// Parses the specified string into a collection of path segments.
        /// </summary>
        /// <param name="path">A <see cref="string"/> containing path data.</param>
        public static SvgPathSegmentList Parse(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

            var segments = new SvgPathSegmentList();

            try
            {
                char command;
                bool isRelative;

                foreach (var commandSet in SplitCommands(path.TrimEnd(null)))
                {
                    command = commandSet[0];
                    isRelative = char.IsLower(command);
                    // http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation

                    CreatePathSegment(command, segments, new CoordinateParser(commandSet.Trim()), isRelative);
                }
            }
            catch (Exception exc)
            {
                Trace.TraceError("Error parsing path \"{0}\": {1}", path, exc.Message);
            }

            return segments;
        }

        private static void CreatePathSegment(char command, SvgPathSegmentList segments, CoordinateParser parser, bool isRelative)
        {

            var coords = new float[6];

            switch (command)
            {
                case 'm': // relative moveto
                case 'M': // moveto
                    if (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
                    {
                        segments.Add(new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative)));
                    }

                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
                    {
                        segments.Add(new SvgLineSegment(segments.Last.End,
                            ToAbsolute(coords[0], coords[1], segments, isRelative)));
                    }
                    break;
                case 'a':
                case 'A':
                    bool size;
                    bool sweep;

                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
                           parser.TryGetFloat(out coords[2]) && parser.TryGetBool(out size) &&
                           parser.TryGetBool(out sweep) && parser.TryGetFloat(out coords[3]) &&
                           parser.TryGetFloat(out coords[4]))
                    {
                        // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y
                        segments.Add(new SvgArcSegment(segments.Last.End, coords[0], coords[1], coords[2],
                            (size ? SvgArcSize.Large : SvgArcSize.Small), 
                            (sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative), 
                            ToAbsolute(coords[3], coords[4], segments, isRelative)));
                    }
                    break;
                case 'l': // relative lineto
                case 'L': // lineto
                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
                    {
                        segments.Add(new SvgLineSegment(segments.Last.End,
                            ToAbsolute(coords[0], coords[1], segments, isRelative)));
                    }
                    break;
                case 'H': // horizontal lineto
                case 'h': // relative horizontal lineto
                    while (parser.TryGetFloat(out coords[0]))
                    {
                        segments.Add(new SvgLineSegment(segments.Last.End,
                            ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false)));
                    }
                    break;
                case 'V': // vertical lineto
                case 'v': // relative vertical lineto
                    while (parser.TryGetFloat(out coords[0]))
                    {
                        segments.Add(new SvgLineSegment(segments.Last.End,
                            ToAbsolute(segments.Last.End.X, coords[0], segments, false, isRelative)));
                    }
                    break;
                case 'Q': // curveto
                case 'q': // relative curveto
                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
                           parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]))
                    {
                        segments.Add(new SvgQuadraticCurveSegment(segments.Last.End,
                            ToAbsolute(coords[0], coords[1], segments, isRelative),
                            ToAbsolute(coords[2], coords[3], segments, isRelative)));
                    }
                    break;
                case 'T': // shorthand/smooth curveto
                case 't': // relative shorthand/smooth curveto
                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
                    {
                        var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment;

                        var controlPoint = lastQuadCurve != null
                            ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End)
                            : segments.Last.End;

                        segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint,
                            ToAbsolute(coords[0], coords[1], segments, isRelative)));
                    }
                    break;
                case 'C': // curveto
                case 'c': // relative curveto
                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
                           parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]) &&
                           parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5]))
                    {
                        segments.Add(new SvgCubicCurveSegment(segments.Last.End,
                            ToAbsolute(coords[0], coords[1], segments, isRelative),
                            ToAbsolute(coords[2], coords[3], segments, isRelative),
                            ToAbsolute(coords[4], coords[5], segments, isRelative)));
                    }
                    break;
                case 'S': // shorthand/smooth curveto
                case 's': // relative shorthand/smooth curveto
                    while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
                           parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]))
                    {
                        var lastCubicCurve = segments.Last as SvgCubicCurveSegment;

                        var controlPoint = lastCubicCurve != null
                            ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End)
                            : segments.Last.End;

                        segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint,
                            ToAbsolute(coords[0], coords[1], segments, isRelative),
                            ToAbsolute(coords[2], coords[3], segments, isRelative)));
                    }
                    break;
                case 'Z': // closepath
                case 'z': // relative closepath
                    segments.Add(new SvgClosePathSegment());
                    break;
            }
        }

        private static PointF Reflect(PointF point, PointF mirror)
        {
            float x, y, dx, dy;
            dx = Math.Abs(mirror.X - point.X);
            dy = Math.Abs(mirror.Y - point.Y);

            if (mirror.X >= point.X)
            {
                x = mirror.X + dx;
            }
            else
            {
                x = mirror.X - dx;
            }
            if (mirror.Y >= point.Y)
            {
                y = mirror.Y + dy;
            }
            else
            {
                y = mirror.Y - dy;
            }

            return new PointF(x, y);
        }

        /// <summary>
        /// Creates point with absolute coorindates.
        /// </summary>
        /// <param name="x">Raw X-coordinate value.</param>
        /// <param name="y">Raw Y-coordinate value.</param>
        /// <param name="segments">Current path segments.</param>
        /// <param name="isRelativeBoth"><b>true</b> if <paramref name="x"/> and <paramref name="y"/> contains relative coordinate values, otherwise <b>false</b>.</param>
        /// <returns><see cref="PointF"/> that contains absolute coordinates.</returns>
        private static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeBoth)
        {
            return ToAbsolute(x, y, segments, isRelativeBoth, isRelativeBoth);
        }

        /// <summary>
        /// Creates point with absolute coorindates.
        /// </summary>
        /// <param name="x">Raw X-coordinate value.</param>
        /// <param name="y">Raw Y-coordinate value.</param>
        /// <param name="segments">Current path segments.</param>
        /// <param name="isRelativeX"><b>true</b> if <paramref name="x"/> contains relative coordinate value, otherwise <b>false</b>.</param>
        /// <param name="isRelativeY"><b>true</b> if <paramref name="y"/> contains relative coordinate value, otherwise <b>false</b>.</param>
        /// <returns><see cref="PointF"/> that contains absolute coordinates.</returns>
        private static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeX, bool isRelativeY)
        {
            var point = new PointF(x, y);

            if ((isRelativeX || isRelativeY) && segments.Count > 0)
            {
                var lastSegment = segments.Last;

                // if the last element is a SvgClosePathSegment the position of the previous element should be used because the position of SvgClosePathSegment is 0,0
                if (lastSegment is SvgClosePathSegment) lastSegment = segments.Reverse().OfType<SvgMoveToSegment>().First();

                if (isRelativeX)
                {
                    point.X += lastSegment.End.X;
                }

                if (isRelativeY)
                {
                    point.Y += lastSegment.End.Y;
                }
            }

            return point;
        }

        private static IEnumerable<string> SplitCommands(string path)
        {
            var commandStart = 0;

            for (var i = 0; i < path.Length; i++)
            {
                string command;
                if (char.IsLetter(path[i]) && path[i] != 'e') //e is used in scientific notiation. but not svg path
                {
                    command = path.Substring(commandStart, i - commandStart).Trim();
                    commandStart = i;

                    if (!string.IsNullOrEmpty(command))
                    {
                        yield return command;
                    }

                    if (path.Length == i + 1)
                    {
                        yield return path[i].ToString();
                    }
                }
                else if (path.Length == i + 1)
                {
                    command = path.Substring(commandStart, i - commandStart + 1).Trim();

                    if (!string.IsNullOrEmpty(command))
                    {
                        yield return command;
                    }
                }
            }
        }

        private enum NumState
        {
            invalid,
            separator,
            prefix,
            integer,
            decPlace,
            fraction,
            exponent,
            expPrefix,
            expValue
        }

        private class CoordinateParser
        {
            private string _coords;
            private int _pos = 0;
            private NumState _currState = NumState.separator;
            private NumState _newState = NumState.separator;
            private int i = 1;
            private bool _parseWorked = true;

            public CoordinateParser(string coords)
            {
                _coords = coords;
            }

            public bool HasMore { get { return _parseWorked; } }

            private bool MarkState(bool state)
            {
                _parseWorked = state;
                i++;
                return state;
            }

            public bool TryGetBool(out bool result)
            {
                while (i < _coords.Length && _parseWorked)
                {
                    switch (_currState)
                    {
                        case NumState.separator:
                            if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else if (_coords[i] == '0')
                            {
                                result = false;
                                _newState = NumState.separator;
                                _pos = i + 1;
                                return MarkState(true);
                            }
                            else if (_coords[i] == '1')
                            {
                                result = true;
                                _newState = NumState.separator;
                                _pos = i + 1;
                                return MarkState(true);
                            }
                            else
                            {
                                result = false;
                                return MarkState(false);
                            }
                            break;
                        default:
                            result = false;
                            return MarkState(false);
                    }
                    i++;
                }
                result = false;
                return MarkState(false);
            }

            public bool TryGetFloat(out float result)
            {
                while (i < _coords.Length && _parseWorked)
                {
                    switch (_currState)
                    {
                        case NumState.separator:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.integer;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case '.':
                                        _newState = NumState.decPlace;
                                        break;
                                    case '+':
                                    case '-':
                                        _newState = NumState.prefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                        case NumState.prefix:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.integer;
                            }
                            else if (_coords[i] == '.')
                            {
                                _newState = NumState.decPlace;
                            }
                            else
                            {
                                _newState = NumState.invalid;
                            }
                            break;
                        case NumState.integer:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.integer;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case '.':
                                        _newState = NumState.decPlace;
                                        break;
                                    case 'e':
                                        _newState = NumState.exponent;
                                        break;
                                    case '+':
                                    case '-':
                                        _newState = NumState.prefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                        case NumState.decPlace:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.fraction;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case 'e':
                                        _newState = NumState.exponent;
                                        break;
                                    case '+':
                                    case '-':
                                        _newState = NumState.prefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                        case NumState.fraction:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.fraction;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case '.':
                                        _newState = NumState.decPlace;
                                        break;
                                    case 'e':
                                        _newState = NumState.exponent;
                                        break;
                                    case '+':
                                    case '-':
                                        _newState = NumState.prefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                        case NumState.exponent:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.expValue;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.invalid;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case '+':
                                    case '-':
                                        _newState = NumState.expPrefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                        case NumState.expPrefix:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.expValue;
                            }
                            else
                            {
                                _newState = NumState.invalid;
                            }
                            break;
                        case NumState.expValue:
                            if (char.IsNumber(_coords[i]))
                            {
                                _newState = NumState.expValue;
                            }
                            else if (IsCoordSeparator(_coords[i]))
                            {
                                _newState = NumState.separator;
                            }
                            else
                            {
                                switch (_coords[i])
                                {
                                    case '.':
                                        _newState = NumState.decPlace;
                                        break;
                                    case '+':
                                    case '-':
                                        _newState = NumState.prefix;
                                        break;
                                    default:
                                        _newState = NumState.invalid;
                                        break;
                                }
                            }
                            break;
                    }

                    if (_newState < _currState)
                    {
                        result = float.Parse(_coords.Substring(_pos, i - _pos), NumberStyles.Float, CultureInfo.InvariantCulture);
                        _pos = i;
                        _currState = _newState;
                        return MarkState(true);
                    }
                    else if (_newState != _currState && _currState == NumState.separator)
                    {
                        _pos = i;
                    }

                    if (_newState == NumState.invalid)
                    {
                        result = float.MinValue;
                        return MarkState(false);
                    }
                    _currState = _newState;
                    i++;
                }

                if (_currState == NumState.separator || !_parseWorked || _pos >= _coords.Length)
                {
                    result = float.MinValue;
                    return MarkState(false);
                }
                else
                {
                    result = float.Parse(_coords.Substring(_pos, _coords.Length - _pos), NumberStyles.Float, CultureInfo.InvariantCulture);
                    _pos = _coords.Length;
                    return MarkState(true);
                }
            }

            private static bool IsCoordSeparator(char value)
            {
                switch (value)
                {
                    case ' ':
                    case '\t':
                    case '\n':
                    case '\r':
                    case ',':
                        return true;
                }
                return false;
            }
        }

        //private static IEnumerable<float> ParseCoordinates(string coords)
        //{
        //    if (string.IsNullOrEmpty(coords) || coords.Length < 2) yield break;

        //    var pos = 0;
        //    var currState = NumState.separator;
        //    var newState = NumState.separator;

        //    for (int i = 1; i < coords.Length; i++)
        //    {
        //        switch (currState)
        //        {
        //            case NumState.separator:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.integer;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.separator;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case '.':
        //                            newState = NumState.decPlace;
        //                            break;
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.prefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //            case NumState.prefix:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.integer;
        //                }
        //                else if (coords[i] == '.')
        //                {
        //                    newState = NumState.decPlace;
        //                }
        //                else
        //                {
        //                    newState = NumState.invalid;
        //                }
        //                break;
        //            case NumState.integer:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.integer;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.separator;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case '.':
        //                            newState = NumState.decPlace;
        //                            break;
        //                        case 'e':
        //                            newState = NumState.exponent;
        //                            break;
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.prefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //            case NumState.decPlace:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.fraction;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.separator;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case 'e':
        //                            newState = NumState.exponent;
        //                            break;
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.prefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //            case NumState.fraction:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.fraction;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.separator;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case '.':
        //                            newState = NumState.decPlace;
        //                            break;
        //                        case 'e':
        //                            newState = NumState.exponent;
        //                            break;
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.prefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //            case NumState.exponent:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.expValue;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.invalid;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.expPrefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //            case NumState.expPrefix:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.expValue;
        //                }
        //                else
        //                {
        //                    newState = NumState.invalid;
        //                }
        //                break;
        //            case NumState.expValue:
        //                if (char.IsNumber(coords[i]))
        //                {
        //                    newState = NumState.expValue;
        //                }
        //                else if (IsCoordSeparator(coords[i]))
        //                {
        //                    newState = NumState.separator;
        //                }
        //                else
        //                {
        //                    switch (coords[i])
        //                    {
        //                        case '.':
        //                            newState = NumState.decPlace;
        //                            break;
        //                        case '+':
        //                        case '-':
        //                            newState = NumState.prefix;
        //                            break;
        //                        default:
        //                            newState = NumState.invalid;
        //                            break;
        //                    }
        //                }
        //                break;
        //        }

        //        if (newState < currState)
        //        {
        //            yield return float.Parse(coords.Substring(pos, i - pos), NumberStyles.Float, CultureInfo.InvariantCulture);
        //            pos = i;
        //        }
        //        else if (newState != currState && currState == NumState.separator)
        //        {
        //            pos = i;
        //        }

        //        if (newState == NumState.invalid) yield break;
        //        currState = newState;
        //    }

        //    if (currState != NumState.separator)
        //    {
        //        yield return float.Parse(coords.Substring(pos, coords.Length - pos), NumberStyles.Float, CultureInfo.InvariantCulture);
        //    }
        //}

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                return Parse((string)value);
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                var paths = value as SvgPathSegmentList;

                if (paths != null)
                {
                    var curretCulture = CultureInfo.CurrentCulture;
                    Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
                    var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray());
                    Thread.CurrentThread.CurrentCulture = curretCulture;
                    return s;
                }
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                return true;
            }

            return base.CanConvertTo(context, destinationType);
        }
    }
}