using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace JWH
{

    public static class ExtensionXml
    {

        /// <summary>
        /// 이름으로 노드를 검색하여 첫번째 노드를 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="fullScan">하위검색</param>
        /// <returns></returns>
        public static XmlNode GetSingleNodeByName(this XmlDocument sender, string name, bool fullScan = true)
        {
            try
            {
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, name, true) == 0) return node;
                }
                if (fullScan == false) return null;

                foreach (XmlNode node in sender.ChildNodes)
                {
                    XmlNode result = node.GetSingleNodeByName(name);
                    if (result != null) return result;
                }

                return null;
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 이름으로 노드를 검색하여 첫번째 노드를 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="fullScan">하위검색</param>
        /// <returns></returns>
        public static XmlNode GetSingleNodeByName(this XmlNode sender, string name, bool fullScan = true)
        {
            try
            {
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, name, true) == 0) return node;
                }
                if (fullScan == false) return null;

                foreach (XmlNode node in sender.ChildNodes)
                {
                    XmlNode result = node.GetSingleNodeByName(name);
                    if (result != null) return result;
                }

                return null;
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 이름으로 노드를 검색하여 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="fullScan">하위검색</param>
        /// <returns></returns>
        public static XmlNode[] GetNodesByName(this XmlDocument sender, string name, bool fullScan = true)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            try
            {
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, name, true) == 0)
                        nodes.Add(node);
                }
                if (fullScan == false) return nodes.ToArray();

                foreach (XmlNode node in sender.ChildNodes)
                {
                    nodes.AddRange(node.GetNodesByName(name));
                }

                return nodes.ToArray();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 이름으로 노드를 검색하여 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="fullScan">하위검색</param>
        /// <returns></returns>
        public static XmlNode[] GetNodesByName(this XmlNode sender, string name, bool fullScan = true)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            try
            {
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, name, true) == 0)
                        nodes.Add(node);
                }
                if (fullScan == false) return nodes.ToArray();

                foreach (XmlNode node in sender.ChildNodes)
                {
                    nodes.AddRange(node.GetNodesByName(name));
                }

                return nodes.ToArray();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 경로를 검색하여 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static XmlNode[] GetNodesByPath(this XmlDocument sender, string path)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            List<XmlNode> childs = new List<XmlNode>();
            try
            {
                string[] names = path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, names[0], true) == 0)
                    {
                        if (names.Length > 1) childs.Add(node);
                        else nodes.Add(node);
                    }
                }

                StringBuilder sb = new StringBuilder();
                for (int i = 1; i < names.Length; i++)
                    sb.AppendFormat($"/{names[i]}");

                foreach (XmlNode node in childs)
                {
                    nodes.AddRange(node.GetNodesByPath(sb.ToString()));
                }

                return nodes.ToArray();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 경로를 검색하여 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static XmlNode[] GetNodesByPath(this XmlNode sender, string path)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            List<XmlNode> childs = new List<XmlNode>();
            try
            {
                string[] names = path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (XmlNode node in sender.ChildNodes)
                {
                    if (string.Compare(node.Name, names[0], true) == 0)
                    {
                        if (names.Length > 1) childs.Add(node);
                        else nodes.Add(node);
                    }
                }

                StringBuilder sb = new StringBuilder();
                for (int i = 1; i < names.Length; i++)
                    sb.AppendFormat($"/{names[i]}");

                foreach (XmlNode node in childs)
                {
                    nodes.AddRange(node.GetNodesByPath(sb.ToString()));
                }

                return nodes.ToArray();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return null;
            }
        }

        /// <summary>
        /// 속성이 동일한 노드를 검색하여 반환한다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ignorsCaseName">Name의 대/소문자 무시</param>
        /// <param name="ignorsCaseValue">Value의 대/소문자 무시</param>
        /// <param name="attributes"></param>
        /// <returns></returns>
        private static bool CheckAttributes(this XmlNode sender, bool ignorsCaseName = true, bool ignorsCaseValue = false, params KeyValuePair<string, string>[] attributes)
        {
            try
            {
                foreach (KeyValuePair<string, string> pair in attributes)
                {
                    foreach (XmlAttribute attribute in sender.Attributes)
                    {
                        if (string.Compare(pair.Key, attribute.Name, ignorsCaseName) == 0)
                            if (string.Compare(pair.Value, attribute.Value, ignorsCaseValue) == 0) continue;
                            else return false;
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return false;
            }
        }

        /// <summary>
        /// 노드의 속성을 검색한다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="ignorsCaseName">Attribute.Name 대/소문자 무시</param>
        /// <returns></returns>
        public static XmlAttribute GetAttribute(this XmlNode sender, string name, bool ignorsCase = true)
        {
            if (sender == null || sender.Attributes == null) return null;

            foreach (XmlAttribute attribute in sender.Attributes)
            {
                if (string.Compare(attribute.Name, name, ignorsCase) == 0)
                    return attribute;
            }

            return null;
        }

        /// <summary>
        /// 노드의 속성값을 반환한다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="ignorsCaseName">Attribute.Name 대/소문자 무시</param>
        /// <returns></returns>
        public static string GetAttributeValue(this XmlNode sender, string name, bool ignorsCase = true)
        {
            if (sender == null) return null;
            foreach (XmlAttribute attribute in sender.Attributes)
            {
                if (string.Compare(attribute.Name, name, ignorsCase) == 0)
                    return attribute.Value;
            }

            return null;
        }

        /// <summary>
        /// 노드의 속성값을 변경한다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="ignorsCaseName">Attribute.Name 대/소문자 무시</param>
        /// <returns></returns>
        public static string SetAttributeValue(this XmlNode sender, string name, string value, bool ignorsCase = true)
        {
            foreach (XmlAttribute attribute in sender.Attributes)
            {
                if (string.Compare(attribute.Name, name, ignorsCase) == 0)
                {
                    attribute.Value = value;
                    return null;
                }
            }

            return null;
        }

        public static void PropertiesCopyAttribute(this XmlNode sender, object dest)
        {
            try
            {
                foreach (XmlAttribute attribute in sender.Attributes)
                    dest.PropertySet(attribute.Name, attribute.Value);
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
            }
        }

        public static void PropertiesCopy(this XmlNode sender, object dest)
        {
            try
            {
                foreach (XmlNode node in sender.ChildNodes)
                    dest.PropertySet(node.Name, node.InnerText);
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
            }
        }

        public static string GetText(this XmlNode sender)
        {
            if (sender.ChildNodes.Count <= 0) return string.Empty;

            string rtnValue = null;
            foreach (XmlNode node in sender.ChildNodes)
            {
                if (node.GetType() == typeof(XmlText))
                    rtnValue = node.InnerText;
            }

            return rtnValue;
        }

        public static void SetText(this XmlNode sender, string value)
        {
            XmlNode nodeText = null;
            foreach (XmlNode node in sender.ChildNodes)
            {
                if (node.GetType() == typeof(XmlText))
                {
                    nodeText.InnerText = value;
                    return;
                }
            }

            if (sender.ChildNodes.Count == 0)
            {
                sender.InnerText = value;
            }
        }


        /// <summary>
        /// XmlDocument 내용을 문자열로 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="indent"></param>
        /// <param name="haeder"></param>
        /// <returns></returns>
        public static string GetBeautify(this XmlDocument sender, int indent = 2, bool haeder = true)
        {
            try
            {
                string rtn = string.Empty;

                using (MemoryStream stream = new MemoryStream())
                {
                    XmlWriterSettings settings = new XmlWriterSettings
                    {
                        Encoding = new UTF8Encoding(false),
                        Indent = true,
                        IndentChars = "".PadLeft(indent),
                        NewLineChars = Environment.NewLine,
                        NewLineHandling = NewLineHandling.Replace,
                    };

                    using (XmlWriter writer = XmlWriter.Create(stream, settings))
                        sender.Save(writer);

                    rtn = Encoding.UTF8.GetString(stream.ToArray());
                }

                if (haeder == false)
                {
                    int index = rtn.IndexOf("<?");
                    if (index >= 0)
                    {
                        index = rtn.IndexOf("<", index + 1);
                        rtn = rtn.Substring(index);
                    }
                }

                return rtn;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 노드를 XmlDocument에 복사합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="document"></param>
        /// <param name="isChild"></param>
        public static void CopyValue(this XmlNode sender, XmlDocument document, bool isChild = true)
        {
            try
            {
                string path = sender.GetFullPath();
                foreach (XmlNode child in sender.ChildNodes)
                {
                    if (child.NodeType == XmlNodeType.Text)
                    {
                        XmlNode dest = document.SelectSingleNode(path);
                        if (dest == null) continue;

                        if (dest.ChildNodes.Count == 0)
                        {
                            XmlNode valueNode = document.CreateNode("text", "", "");
                            dest.AppendChild(valueNode);
                        }
                        foreach (XmlNode destChild in dest.ChildNodes)
                            if (destChild.NodeType == XmlNodeType.Text) destChild.Value = child.Value;
                    }
                    else
                    {
                        if (isChild) child.CopyValue(document);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 하위 노드를 생성하고 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="innerText"></param>
        /// <returns></returns>
        public static XmlNode CreateChildNode(this XmlNode sender, string name, string innerText = "", int index = -1, bool isAfter = true)
        {
            try
            {
                XmlDocument document = sender.OwnerDocument;
                XmlNode childNode = document.CreateElement(name);
                if (!string.IsNullOrEmpty(innerText)) childNode.InnerText = innerText;

                if (index < 0 || index > sender.ChildNodes.Count)
                {
                    sender.AppendChild(childNode);
                    return childNode;
                }

                XmlNode refNode = sender.ChildNodes[index];
                if (isAfter) sender.InsertAfter(childNode, refNode);
                else sender.InsertBefore(childNode, refNode);

                return childNode;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 노드를 삭제합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static bool RemoveChildNode(this XmlNode sender, int index)
        {
            try
            {
                if (index >= sender.ChildNodes.Count) return false;
                XmlNode node = sender.ChildNodes[index];
                if (node != null) sender.RemoveChild(node);

                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 노드를 삭제합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool RemoveChildNode(this XmlNode sender, string name)
        {
            try
            {
                XmlNode node = sender.GetSingleNodeByName(name);
                if (node == null) return false;
                sender.RemoveChild(node);

                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 노드를 삭제합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool RemoveChildNodes(this XmlNode sender, string name)
        {
            try
            {
                foreach (XmlNode node in sender.GetNodesByName(name))
                    sender.RemoveChild(node);

                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private static string GetFullPath(this XmlNode sender)
        {
            if (sender == null) return string.Empty;
            if (sender.ParentNode == null || sender.ParentNode.NodeType != XmlNodeType.Element)
                return $"//{sender.Name}";

            return $"{sender.ParentNode.GetFullPath()}/{sender.Name}";
        }

        /// <summary>
        /// 문자열에 있는 잘못된 XML 문자를 해당하는 올바른 XML로 바꿉니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <returns>잘못된 문자가 있었으나 바뀐 입력 문자열입니다.</returns>
        public static string XMLEscape(this string sender)
        {
            try
            {
                return System.Security.SecurityElement.Escape(sender);
            }
            catch
            {
                return sender;
            }
        }

        /// <summary>
        /// 입력 문자열에서 이스케이프된 문자를 변환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <returns></returns>
        public static string XMLUnescape(this string sender)
        {
            try
            {
                return System.Text.RegularExpressions.Regex.Unescape(sender);
            }
            catch
            {
                return sender;
            }
        }

    }

}
