using CPF.Drawing;
using System;
using System.Collections.Generic;
using System.Text;
using CPF.Styling;
using System.Xml;
using CPF.Controls;
using System.ComponentModel;
using System.Linq;

namespace CPF.Svg
{
    /// <summary>
    /// 支持显示SVG图形，暂时不支持里面的滤镜，动画，图片引用，文字等，只能显示简单的图形
    /// </summary>
    [DefaultProperty(nameof(Source))]
    public class SVG : UIElement
    {
        /// <summary>
        /// 支持显示SVG图形，暂时不支持里面的滤镜，动画，图片引用，文字等，只能显示简单的图形
        /// </summary>
        public SVG()
        {

        }
        /// <summary>
        /// 可以是svg文件路径，或者直接是svg文档
        /// </summary>
        /// <param name="svgSource"></param>
        public SVG(string svgSource)
        {
            Source = svgSource;
        }
        /// <summary>
        /// svg文档
        /// </summary>
        [UIPropertyMetadata("", UIPropertyOptions.AffectsMeasure | UIPropertyOptions.AffectsRender)]
        public string Data
        {
            get { return GetValue<string>(); }
            private set { SetValue(value); }
        }

        /// <summary>
        /// SVG源，可以是路径、Url、或者svg文档字符串
        /// </summary>
        [Description("SVG源，可以是路径、Url、或者svg文档字符串")]
        [PropertyMetadata(null), CPF.Design.FileBrowser(".svg")]
        public string Source
        {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
        [PropertyChanged(nameof(Source))]
        void OnSourceChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
        {
            var svg = newValue as string;
            if (!string.IsNullOrWhiteSpace(svg))
            {
                svg = svg.Trim();
                if (svg.StartsWith("<") && svg.EndsWith(">"))
                {
                    Data = svg;
                }
                else
                {
                    ResourceManager.GetText(svg, a =>
                    {
                        Invoke(() =>
                        {
                            Data = a;
                        });
                    });
                }
            }
        }
        [PropertyChanged(nameof(Data))]
        void OnDataChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
        {
            m_elements.Clear();
            if (newValue is string str)
            {
                try
                {
                    XmlDocument doc = new XmlDocument();
                    doc.XmlResolver = null;
                    doc.LoadXml(str);
                    XmlNode n = doc.GetElementsByTagName("svg")[0];
                    this.Parse(n);


                    //bool success = false;
                    //canvasSize = GetCanvas(true, false, out success);
                    //if (!success)
                    //    canvasSize = GetCanvas(true, true, out success);
                    //if (!success)
                    //    canvasSize = GetCanvas(false, false, out success);
                    //if (!success)
                    //    canvasSize = GetCanvas(false, true, out success);
                    bool realSuccess = false;
                    realCanvasSize = GetCanvas(false, false, out realSuccess);
                    if (!realSuccess)
                        realCanvasSize = GetCanvas(false, true, out realSuccess);

                    bool viewBoxSuccess = false;
                    viewBoxCanvasSize = GetCanvas(true, false, out viewBoxSuccess);
                    if (!viewBoxSuccess)
                        viewBoxCanvasSize = GetCanvas(true, true, out viewBoxSuccess);

                    canvasSize = realSuccess ? viewBoxCanvasSize : realCanvasSize;
                }
                catch (Exception e)
                {
                    throw new Exception("svg格式不对", e);
                }
            }
        }

        SizeField viewBoxCanvasSize;
        SizeField realCanvasSize;

        SizeField canvasSize;

        SizeField GetCanvas(bool viewBox, bool px, out bool success)
        {
            try
            {
                string width = "0"; //宽 
                string height = "0"; //高 
                string pattern = viewBox ? (px ? "viewBox\\s?=\\s?\"(\\d+px)\\s(\\d+px)\\s(\\d+px)\\s(\\d+px)\"" : "viewBox\\s?=\\s?\"(\\d+)\\s(\\d+)\\s(\\d+)\\s(\\d+)\"") :
                    (px ? "width\\s?=\\s?\"(\\d+px)\"\\s+height\\s?=\\s?\"(\\d+px)" : "width\\s?=\\s?\"(\\d+)\"\\s+height\\s?=\\s?\"(\\d+)");
                string str = Data;

                System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
                System.Text.RegularExpressions.Match m = regex.Match(str);
                if (m.Success)
                {
                    if (viewBox)
                    {
                        width = m.Groups[3].Value.Replace("px", ""); //宽 
                        height = m.Groups[4].Value.Replace("px", ""); //高 
                    }
                    else
                    {
                        width = m.Groups[1].Value.Replace("px", ""); //宽 
                        height = m.Groups[2].Value.Replace("px", ""); //高 
                    }
                    success = true;
                }
                else
                    success = false;
                return new SizeField()
                {
                    Width = float.Parse(width),
                    Height = float.Parse(height)
                };
            }
            catch
            {
                success = false;
                return new SizeField();
            }
        }


        static PaintServerManager m_paintServers = new PaintServerManager();

        // for "use" shape only.
        Dictionary<string, SvgShape> m_shapes = new Dictionary<string, SvgShape>();
        //internal void AddShape(string id, SvgShape shape)
        //{
        //    //System.Diagnostics.Debug.Assert(id.Length > 0 && m_shapes.ContainsKey(id) == false);
        //    m_shapes[id] = shape;
        //}
        /// <summary>
        /// 通过ID获取Shape
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public ISvgShape GetShape(string id)
        {
            //SvgShape shape = null;
            m_shapes.TryGetValue(id, out var shape);
            return shape;
        }
        internal static PaintServerManager PaintServers
        {
            get { return m_paintServers; }
        }
        List<SvgShape> m_elements = new List<SvgShape>();
        /// <summary>
        /// 解析到的元素
        /// </summary>
        [NotCpfProperty, Browsable(false)]
        public IEnumerable<ISvgShape> Elements
        {
            get { return m_elements.Select(a => a as ISvgShape); }
        }
        void Parse(XmlNode node)
        {
            if (node == null || node.Name != "svg")
                throw new FormatException("Not a valide SVG node");
            foreach (XmlNode childnode in node.ChildNodes)
                Group.AddToList(/*this, */m_elements, childnode, null);

            var fill = Fill;
            foreach (var item in m_elements)
            {
                if (item.GetFill().Color == null)
                {
                    item.GetFill().FillBrush = fill;
                }
            }
        }

        /// <summary>
        /// 图片缩放模式
        /// </summary>
        [Description("图片缩放模式")]
        [UIPropertyMetadata(Stretch.None, UIPropertyOptions.AffectsMeasure)]
        public Stretch Stretch
        {
            get { return GetValue<Stretch>(); }
            set { SetValue(value); }
        }
        /// <summary>
        /// 描述如何对内容应用缩放，并限制对已命名像素类型的缩放。
        /// </summary>
        [Description("描述如何对内容应用缩放，并限制对已命名像素类型的缩放。")]
        [UIPropertyMetadata(StretchDirection.Both, UIPropertyOptions.AffectsMeasure)]
        public StretchDirection StretchDirection
        {
            get { return GetValue<StretchDirection>(); }
            set { SetValue(value); }
        }
        /// <summary>
        /// 图形的默认填充
        /// </summary>
        [UIPropertyMetadata(typeof(ViewFill), "#000", UIPropertyOptions.AffectsRender)]
        public ViewFill Fill
        {
            get { return GetValue<ViewFill>(); }
            set { SetValue(value); }
        }
        [PropertyChanged(nameof(Fill))]
        void OnFillChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
        {
            var fill = newValue as ViewFill;
            foreach (var item in m_elements)
            {
                if (item.GetFill().Color == null)
                {
                    item.GetFill().FillBrush = fill;
                }
            }
        }

        Size naturalSize;
        protected override Size MeasureOverride(in Size availableSize)
        {
            var b = base.MeasureOverride(availableSize);
            var list = m_elements;
            if (list.Count > 0)
            {
                if (canvasSize.Width.Value > 0 && canvasSize.Height.Value > 0)
                {
                    naturalSize = new Size(canvasSize.Width.Value, canvasSize.Height.Value);
                }
                else
                {
                    foreach (var item in list)
                    {
                        var s = Measure(item);
                        b = new Size(Math.Max(s.Width, b.Width), Math.Max(s.Height, b.Height));
                    }
                    naturalSize = new Size(b.Width, b.Height);
                }

                var size = availableSize;
                if (realCanvasSize.Width.Value > 0 && realCanvasSize.Width.Value < viewBoxCanvasSize.Width.Value &&
                   realCanvasSize.Height.Value > 0 && realCanvasSize.Height.Value < viewBoxCanvasSize.Height.Value)
                {
                    size = new Size(realCanvasSize.Width.Value, realCanvasSize.Height.Value);
                }

                var maxW = MaxWidth;
                if (!maxW.IsAuto && maxW.Unit == Unit.Default)
                {
                    size.Width = maxW.Value;
                }
                var maxH = MaxHeight;
                if (!maxH.IsAuto && maxH.Unit == Unit.Default)
                {
                    size.Height = maxH.Value;
                }
                Size scaleFactor = ComputeScaleFactor(size,
                                                         naturalSize,
                                                         this.Stretch, StretchDirection);
                Size sizeField = new Size(naturalSize.Width * scaleFactor.Width, naturalSize.Height * scaleFactor.Height);
                //Console.WriteLine("newSize:" + naturalSize.Width + "," + naturalSize.Height);
                // Returns our minimum size & sets DesiredSize.
                return sizeField;
            }
            return b;
        }

        internal static Size ComputeScaleFactor(Size availableSize,
                                          Size contentSize,
                                          Stretch stretch, StretchDirection stretchDirection)
        {
            // Compute scaling factors to use for axes
            float scaleX = 1.0f;
            float scaleY = 1.0f;

            bool isConstrainedWidth = !float.IsPositiveInfinity(availableSize.Width);
            bool isConstrainedHeight = !float.IsPositiveInfinity(availableSize.Height);

            if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
                 && (isConstrainedWidth || isConstrainedHeight))
            {
                // Compute scaling factors for both axes
                scaleX = (FloatUtil.IsZero(contentSize.Width)) ? 0f : availableSize.Width / contentSize.Width;
                scaleY = (FloatUtil.IsZero(contentSize.Height)) ? 0f : availableSize.Height / contentSize.Height;

                if (!isConstrainedWidth) scaleX = scaleY;
                else if (!isConstrainedHeight) scaleY = scaleX;
                else
                {
                    // If not preserving aspect ratio, then just apply transform to fit
                    switch (stretch)
                    {
                        case Stretch.Uniform:       //Find minimum scale that we use for both axes
                            float minscale = scaleX < scaleY ? scaleX : scaleY;
                            scaleX = scaleY = minscale;
                            break;

                        case Stretch.UniformToFill: //Find maximum scale that we use for both axes
                            float maxscale = scaleX > scaleY ? scaleX : scaleY;
                            scaleX = scaleY = maxscale;
                            break;

                        case Stretch.Fill:          //We already computed the fill scale factors above, so just use them
                            break;
                    }
                }

                //Apply stretch direction by bounding scales.
                //In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
                //In the uniform fill case, we have the same result too.
                //In the fill case, note that we change aspect ratio, but that is okay
                switch (stretchDirection)
                {
                    case StretchDirection.UpOnly:
                        if (scaleX < 1.0) scaleX = 1f;
                        if (scaleY < 1.0) scaleY = 1f;
                        break;

                    case StretchDirection.DownOnly:
                        if (scaleX > 1.0) scaleX = 1f;
                        if (scaleY > 1.0) scaleY = 1f;
                        break;

                    case StretchDirection.Both:
                        break;

                    default:
                        break;
                }
            }
            //Return this as a size now
            return new Size(scaleX, scaleY);
        }



        protected override void OnRender(DrawingContext dc)
        {
            if (m_elements.Count > 0)
            {
                var size = ActualSize;
                var w = naturalSize.Width;
                var h = naturalSize.Height;
                var x = 0f;
                var y = 0f;
                var sw = 1f;
                var sh = 1f;
                switch (this.Stretch)
                {
                    case Stretch.None:
                        //dc.DrawImage(img, new Rect((size.Width - w) / 2, (size.Height - h) / 2, w, h), new Rect(0, 0, w, h));
                        x = (size.Width - w) / 2;
                        y = (size.Height - h) / 2;
                        break;
                    case Stretch.Fill:
                        //dc.DrawImage(img, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, w, h));
                        sw = size.Width / w;
                        sh = size.Height / h;
                        break;
                    case Stretch.Uniform:
                        var ww = size.Width;
                        var hh = size.Height;
                        if (w / size.Width > h / size.Height)
                        {
                            hh = size.Width * h / w;
                        }
                        else
                        {
                            ww = size.Height * w / h;
                        }
                        x = (size.Width - ww) / 2;
                        y = (size.Height - hh) / 2;
                        sw = ww / w;
                        sh = hh / h;
                        //dc.DrawImage(img, new Rect((size.Width - ww) / 2, (size.Height - hh) / 2, ww, hh), new Rect(0, 0, w, h));
                        break;
                    case Stretch.UniformToFill:
                        var www = size.Width;
                        var hhh = size.Height;
                        if (w / size.Width < h / size.Height)
                        {
                            hhh = size.Width * h / w;
                        }
                        else
                        {
                            www = size.Height * w / h;
                        }
                        x = (size.Width - www) / 2;
                        y = (size.Height - hhh) / 2;
                        sw = www / w;
                        sh = hhh / h;
                        //dc.DrawImage(img, new Rect((size.Width - www) / 2, (size.Height - hhh) / 2, www, hhh), new Rect(0, 0, w, h));
                        break;
                }
                var old = dc.Transform;
                var tran = old;
                tran.TranslatePrepend(x, y);
                tran.ScalePrepend(sw, sh);
                dc.Transform = tran;
                foreach (var item in m_elements)
                {
                    DrawShape(dc, item);
                }
                dc.Transform = old;
            }
        }

        private void DrawShape(DrawingContext dc, SvgShape item)
        {
            var old = dc.Transform;
            if (item.Transform != null)
            {
                var tran = old;
                tran.Prepend(item.Transform.Value);
                dc.Transform = tran;
            }

            if (item is Group group)
            {
                foreach (SvgShape shape in group.Elements)
                {
                    DrawShape(dc, shape);
                }
            }
            else if (item is UseShape use)
            {
                var shape = GetShape(use.hRef);
                if (shape != null)
                {
                    DrawShape(dc, (SvgShape)shape);
                }
            }
            else
            {
                var stroke = item.Stroke;
                if (stroke != null && stroke.Width > 0 && stroke.StrokeBrush != null)
                {
                    using (var brush = stroke.StrokeBrush.CreateBrush(item.Geometry.GetBounds(), Root.RenderScaling))
                    {
                        var stro = new Drawing.Stroke((float)stroke.Width, DashStyles.Solid);
                        if (stroke.StrokeArray != null)
                        {
                            stro.DashStyle = DashStyles.Custom;
                            stro.DashPattern = stroke.StrokeArray;
                            switch (stroke.LineJoin)
                            {
                                case Stroke.eLineJoin.miter:
                                    stro.LineJoin = LineJoins.Miter;
                                    break;
                                case Stroke.eLineJoin.round:
                                    stro.LineJoin = LineJoins.Round;
                                    break;
                                case Stroke.eLineJoin.bevel:
                                    stro.LineJoin = LineJoins.Bevel;
                                    break;
                            }
                            switch (stroke.LineCap)
                            {
                                case Stroke.eLineCap.butt:
                                    stro.StrokeCap = CapStyles.Flat;
                                    break;
                                case Stroke.eLineCap.round:
                                    stro.StrokeCap = CapStyles.Round;
                                    break;
                                case Stroke.eLineCap.square:
                                    stro.StrokeCap = CapStyles.Square;
                                    break;
                            }
                        }
                        dc.DrawPath(brush, stro, item.Geometry);
                    }
                }
                if (item.Fill != null && item.Fill.FillBrush != null)
                {
                    if (item.Fill.Color is GradientColor svgFill)
                    {
                        using (var brush = item.Fill.FillBrush.CreateBrush(svgFill.GradientUnits == SVGTags.sGradientUserSpace ? new Rect(0, 0, naturalSize.Width, naturalSize.Height) : item.Geometry.GetBounds(), Root.RenderScaling))
                        {
                            dc.FillPath(brush, item.Geometry);
                        }
                    }
                    else
                    {
                        //if (item.Fill.Color != null)
                        {
                            using (var brush = item.Fill.FillBrush.CreateBrush(item.Geometry.GetBounds(), Root.RenderScaling))
                            {
                                dc.FillPath(brush, item.Geometry);
                            }
                        }
                    }
                }
            }
            if (item.Transform != null)
            {
                dc.Transform = old;
            }
        }

        private Size Measure(SvgShape item)
        {
            if (item is Group group)
            {
                Size b = new Size();
                foreach (SvgShape shape in group.Elements)
                {
                    var s = Measure(shape);
                    b = new Size(Math.Max(s.Width, b.Width), Math.Max(s.Height, b.Height));
                }
                return b;
            }
            else if (item is UseShape useShape)
            {
                var shape = GetShape(useShape.hRef);
                if (shape != null)
                {
                    return Measure((SvgShape)shape);
                }
                return new Size();
            }
            else
            {
                var bound = item.Geometry.GetBounds();
                return new Size(bound.Right, bound.Bottom);
            }
        }
    }
}
