using PCUT.DeepNestApi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace PCUT.Extensions
{
    public static class XmlExtensions
    {
        public static string GetId(this XElement element)
        {
            return element.Attribute("id")?.Value;
        }

        public static void SetId(this XElement element, string id)
        {
            element.SetAttributeValue("id", id);
        }

        public static XElement GetDescendantById(this XDocument doc, string id)
        {
            return doc.Root.GetDescendantById(id);
        }

        public static XElement GetDescendantById(this XElement element, string id)
        {
            return element.Descendants().FirstOrDefault(x => x.GetId() == id);
        }

        public static string GetViewBox(this XDocument doc)
        {
            return doc.Root.Attribute("viewBox")?.Value;
        }

        public static XElement RemoveAttribute(this XElement element, string attributeName)
        {
            var attribute = element.Attribute(attributeName);
            attribute?.Remove();
            return element;
        }

        public static int Count(this XElement element, params string[] names)
        {
            return names.Sum(name => element.Elements(XName.Get(name, element.GetDefaultNamespace().NamespaceName)).Count());
        }

        public static XDocument SetAutoSize(this XDocument doc)
        {
            doc.Root.SetAttributeValue("height", "auto");
            doc.Root.SetAttributeValue("width", "auto");
            return doc;
        }

        public static void SetSize(this XDocument doc, double width, double height)
        {
            doc.Root.SetAttributeValue("width", width);
            doc.Root.SetAttributeValue("height", height);
        }

        public static XElement GetChild(this XDocument doc, string tag)
        {
            return doc.Root.GetChild(tag);
        }

        public static XElement GetChild(this XElement root, string tag)
        {
            var name = XName.Get(tag, root.GetDefaultNamespace().NamespaceName);
            return root.Element(name);
        }

        //public static IEnumerable<XElement> GetChildren(this XDocument doc, string path = null, params string[] tags)
        //{
        //    var root = doc.Root;
        //    if (path != null)
        //        foreach (var tag in path.TrimStart('.').Split("/"))
        //            root = root.GetChild(tag);
        //    return root.GetChildren(tags);
        //}

        public static IEnumerable<XElement> GetChildren(this XElement root, params string[] tags)
        {
            return root.Elements().Where(x => tags.Contains(x.Name.LocalName));
        }

        public static bool IsHidden(this XElement element)
        {
            if (element.Attribute("visibility")?.Value == "hidden")
                return true;

            while (element.Parent != null)
            {
                element = element.Parent;
                if (element.Attribute("visibility")?.Value == "hidden")
                    return true;
            }
            return false;
        }

        public static void HideById(this XDocument doc, string id)
        {
            var element = doc.GetDescendantById(id);
            element?.SetAttributeValue("visibility", "hidden");
        }

        public static void RestoreById(this XDocument doc, string id)
        {
            var element = doc.GetDescendantById(id);
            element?.Attribute("visibility")?.Remove();
        }

        public static void RemoveHidden(this XDocument doc)
        {
            var elements = doc.Descendants().Where(element => element.IsHidden()).ToArray();
            foreach (var element in elements)
            {
                element.Remove();
            }
        }

        public static void ApplyTransform(this XDocument doc, string id, string transform)
        {
            var element = doc.GetDescendantById(id);
            if (element.Attribute("transform") == null)
            {
                element.SetAttributeValue("transform", transform);
            }
            else
            {
                var parent = element.Parent;

                var transformElement = XElement.Parse($"<g></g>");
                transformElement.SetId(id);
                if (element.IsHidden())
                    transformElement.SetAttributeValue("visibility", "hidden");
                transformElement.SetAttributeValue("transform", transform);

                element.Attribute("id")?.Remove();
                element.Attribute("visibility")?.Remove();
                element.Remove();
                transformElement.Add(element);
                parent.Add(transformElement);
            }
        }

        public static IEnumerable<(string Id, bool Deleted, XDocument Component, double BoundWidth, double BoundHeight)> SplitDocument(this XDocument doc, double width, double height)
        {
            var root = doc.Root;
            while (root.Count("g") == 1 && root.Count("path") == 0)
            {
                root = root.GetChild("g");
            }
            foreach (var element in root.GetChildren("g", "path"))
            {
                var componentElement = new XElement(element);
                var deleted = componentElement.IsHidden();
                componentElement.Attribute("visibility")?.Remove();
                var id = componentElement.GetId();

                var rootElement = CreateRoot(doc);
                if (element.Name.LocalName != "g")
                {
                    var groupElement = new XElement(XName.Get("g", root.GetDefaultNamespace().NamespaceName));
                    groupElement.Add(componentElement);
                    componentElement = groupElement;
                }
                rootElement.Add(componentElement);
                var componentDoc = new XDocument(rootElement);

                var (bWidth, bHeight) = componentDoc.ProcessComponent(width, height);
                yield return (id, deleted, componentDoc, bWidth, bHeight);
            }
            yield break;
        }

        private static XElement CreateRoot(XDocument doc)
        {
            var newRoot = new XElement(XName.Get("svg", doc.Root.GetDefaultNamespace().NamespaceName));
            newRoot.SetAttributeValue("width", "auto");
            newRoot.SetAttributeValue("height", "auto");
            newRoot.SetAttributeValue("viewBox", doc.Root.Attribute("viewBox")?.Value);
            return newRoot;
        }

        private static (double BoundWidth, double BoundHeight) ProcessComponent(this XDocument component, double width, double height)
        {
            var bbox = SvgParser.GetBBox(component);
            var boundWidth = bbox.Width;
            var boundHeight = bbox.Height;
            var size = bbox.Width > bbox.Height ? bbox.Width : bbox.Height;
            var scale = 0.8 * (bbox.Width > bbox.Height ? width : height) / size;
            var tx = (width - (scale * bbox.Width)) / 2 - (scale * bbox.Left);
            var ty = (height - (scale * bbox.Height)) / 2 - (scale * bbox.Top);
            var element = component.Root.Elements().First();
            element.SetAttributeValue("transform", $"translate({tx} {ty}) scale({scale})");
            return (boundWidth, boundHeight);
        }

        public static (double MinX, double MinY, double Width, double Height) GetViewBoxData(this string viewBox)
        {
            var viewBoxData = viewBox.Split(' ', 4);
            return (
                double.Parse(viewBoxData[0]),
                double.Parse(viewBoxData[1]),
                double.Parse(viewBoxData[2]),
                double.Parse(viewBoxData[3])
            );
        }

        //public static void IdentifyComponents(this XDocument document, string path, params string[] tags)
        //{
        //    foreach (var component in document.GetChildren(path: path, tags))
        //    {
        //        if (component.GetId() == null)
        //        {
        //            component.SetId($"_{Guid.NewGuid():N}");
        //        }
        //    }
        //}

        //public static void PropagateTransform(this XElement element, string previousTransform = null)
        //{
        //    if (element.Name.LocalName == "g" || element.Name.LocalName == "path")
        //    {
        //        var transformAttribute = element.Attribute("transform");
        //        var transformAttributeValue = transformAttribute?.Value;
        //        transformAttributeValue = $"{previousTransform} {transformAttributeValue}".Trim();
        //        if (transformAttribute != null)
        //            transformAttribute.Remove();
        //        if (element.Name.LocalName == "path")
        //        {
        //            if (!string.IsNullOrWhiteSpace(transformAttributeValue))
        //                element.SetAttributeValue("transform", transformAttributeValue);
        //            return;
        //        }
        //        else if (element.Name.LocalName == "g")
        //        {
        //            foreach (var child in element.Elements())
        //            {
        //                PropagateTransform(child, transformAttributeValue);
        //            }
        //        }
        //    }
        //    else if (element.Name.LocalName == "svg")
        //    {
        //        foreach (var child in element.Elements())
        //        {
        //            PropagateTransform(child, previousTransform);
        //        }
        //    }
        //}
    }
}
