using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Serialization;
using System;
using System.Text;
using System.Collections.Generic;
using System.Xml.Schema;
using System.Dynamic;
using System.Xml.Linq;
using System.Linq;

namespace EasyDevCore.Common
{
    /// <summary>
    /// Xml Utils
    /// </summary>
    public sealed class XmlHelper
    {
        #region NodeNavigator Class
        /// <summary>
        /// Class required to navigate through children nodes
        /// </summary>
        private class NodeNavigator
        {
            //  Recursively loop over a node subtree
            internal static void LoopThroughChildren(XmlTextWriter writer, XmlNode rootNode)
            {
                //  Write the start tag
                if (rootNode.NodeType == XmlNodeType.Element)
                {
                    writer.WriteStartElement(rootNode.Name);

                    //  Write any attributes 
                    foreach (XmlAttribute attr in rootNode.Attributes)
                    {
                        writer.WriteAttributeString(attr.Name, attr.Value);
                    }

                    //  Write any child nodes
                    foreach (XmlNode node in rootNode.ChildNodes)
                    {
                        LoopThroughChildren(writer, node);
                    }

                    //  Write the end tag
                    writer.WriteEndElement();
                }
                else
                {
                    //  Write any text
                    if (rootNode.NodeType == XmlNodeType.Text)
                    {
                        writer.WriteString(rootNode.Value);
                    }
                }
            }
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Converts XML document to string.
        /// </summary>
        /// <param name="xmlDoc">The XML doc.</param>
        /// <returns></returns>
        public static string DocToString(XmlDocument xmlDoc)
        {
            StringBuilder sb = new();
            StringWriter sw = new(sb);
            xmlDoc.Save(sw);
            return sw.ToString();
        }

        /// <summary>
        /// Gets the XML doc from URL
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns></returns>
        public static XmlDocument CreateDocument(Stream stream)
        {
            XmlDocument xmlDoc = new();
            XmlReaderSettings settings = new();
            //Bỏ qua ghi chú và các chỉ dẫn xử lý
            settings.IgnoreComments = true;
            settings.IgnoreProcessingInstructions = true;
            settings.IgnoreWhitespace = true;
            XmlReader reader = null;
            try
            {
                reader = XmlReader.Create(stream, settings);
                xmlDoc.Load(reader);
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                }
            }
            return xmlDoc;
        }

        /// <summary>
        /// Creates the XML document.
        /// </summary>
        /// <param name="rootName">Name of the root.</param>
        /// <returns></returns>
        public static XmlDocument CreateDocument(string rootName)
        {
            XmlDocument doc = new();
            XmlDeclaration decl = doc.CreateXmlDeclaration("1.0", "utf-8", "");
            doc.InsertBefore(decl, doc.DocumentElement);
            if (!string.IsNullOrEmpty(rootName))
            {
                XmlNode newNode = doc.CreateElement(rootName);
                doc.AppendChild(newNode);
            }
            return doc;
        }

        /// <summary>
        /// Creates the XML document.
        /// </summary>
        /// <returns></returns>
        public static XmlDocument CreateDocument()
        {
            return CreateDocument(string.Empty);
        }

        /// <summary>
        /// Adds the child node.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeNames">The node names.</param>
        /// <returns></returns>
        public static XmlNode[] AddChildNodes(XmlNode parentNode, params string[] nodeNames)
        {
            XmlDocument doc = parentNode.OwnerDocument;
            if (doc == null) //Root ?
            {
                doc = (XmlDocument)parentNode;
            }
            XmlNode[] list = new XmlNode[nodeNames.Length];
            for (int i = 0; i < nodeNames.Length; i++)
            {
                var newNode = doc.CreateElement(nodeNames[i]);
                list[i] = parentNode.AppendChild(newNode);
            }
            return list;
        }

        /// <summary>
        /// Adds the child node with attributes.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="value">The value.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="attrValue">The attr value.</param>
        /// <returns></returns>
        public static XmlNode AddSingleChildNode(XmlNode parentNode, string nodeName, object value, string attrName, string attrValue)
        {
            XmlDocument doc = parentNode.OwnerDocument;
            if (doc == null) //Root ?
            {
                doc = (XmlDocument)parentNode;
            }
            var newNode = doc.CreateElement(nodeName);
            if (attrName != null)
            {
                AddNodeAttribute(newNode, attrName, attrValue);
            }
            parentNode.AppendChild(newNode);
            newNode.InnerText = ConvertHelper.StringFrom(value);
            return newNode;
        }

        /// <summary>
        /// Adds the single child node with value
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static XmlNode AddSingleChildNode(XmlNode parentNode, string nodeName, object value)
        {
            return AddSingleChildNode(parentNode, nodeName, value, null, null);
        }

        
        /// <summary>
        /// Selects the node.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns></returns>
        public static XmlNode SelectSingleNode(XmlNode parentNode, string nodeName)
        {
            return parentNode.SelectSingleNode(nodeName);
        }

        /// <summary>
        /// Selects the nodes.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns></returns>
        public static XmlNodeList SelectNodes(XmlNode parentNode, string nodeName)
        {
            return parentNode.SelectNodes(nodeName);
        }

        /// <summary>
        /// Selects the node.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="attrValue">The attr value.</param>
        /// <returns></returns>
        public static XmlNode SelectSingleNode(XmlNode parentNode, string attrName, object attrValue)
        {
            XmlNode childNode = null;
            if (parentNode.HasChildNodes)
            {
                string nodeName = parentNode.ChildNodes[0].Name;
                string path = nodeName + "[@" + attrName + "='" + attrValue.ToString() + "']";
                childNode = parentNode.SelectSingleNode(path);
            }
            return childNode;
        }

        /// <summary>
        /// Sets the node value.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool SetNodeValue(XmlNode node, string nodeName, object value)
        {
            bool success = false;
            try
            {
                node[nodeName].InnerText = ConvertHelper.StringFrom(value);
                success = true;
            }
            catch(Exception)
            {
            }
            return success;
        }

        /// <summary>
        /// Gets the node value string.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string GetNodeValue(XmlNode node, string nodeName, string defaultValue)
        {
            string value = defaultValue;
            if(node[nodeName] != null)
            {
                value = node[nodeName].InnerText;
            }
            return value;
        }

        /// <summary>
        /// Gets the node value string.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns></returns>
        public static string GetNodeValue(XmlNode node, string nodeName)
        {
            return GetNodeValue(node, nodeName, string.Empty);
        }

        /// <summary>
        /// Gets the node value.
        /// </summary>
        /// <typeparam name="T">The type of value.</typeparam>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetNodeValue<T>(XmlNode parentNode, string nodeName, T defaultValue)
        {
#pragma warning disable CS8602 // Dereference of a possibly null reference.
            return ConvertHelper.StringTo<T>(parentNode[nodeName].InnerText, defaultValue);
#pragma warning restore CS8602 // Dereference of a possibly null reference.

        }

        /// <summary>
        /// Gets the node value.
        /// </summary>
        /// <typeparam name="T">The type of value.</typeparam>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns></returns>
        public static T GetNodeValue<T>(XmlNode parentNode, string nodeName)
        {
            return GetNodeValue<T>(parentNode, nodeName, default);
        }

        /// <summary>
        /// Creates the node attribute.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static XmlAttribute AddNodeAttribute(XmlNode node, string attrName, object value)
        {
            XmlAttribute attr = null;
            try
            {
                attr = node.OwnerDocument.CreateAttribute(attrName);
                attr.Value = value.ToString();
                node.Attributes.SetNamedItem(attr);
            }
            catch(Exception)
            {
            }
            return attr;
        }

        /// <summary>
        /// Sets the node attribute.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool SetNodeAttribute(XmlNode node, string attrName, object value)
        {
            bool success = false;
            try
            {
                node.Attributes[attrName].Value = ConvertHelper.StringFrom(value);
                success = true;
            }
            catch(Exception)
            {
            }
            return success;
        }

        /// <summary>
        /// Copies the node attribute.
        /// </summary>
        /// <param name="fromNode">From node.</param>
        /// <param name="toNode">To node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <returns></returns>
        public static bool CopyNodeAttribute(XmlNode fromNode, XmlNode toNode, string attrName)
        {
            bool success = true;
            try
            {
                XmlAttribute fromAttr = fromNode.Attributes[attrName];
                XmlAttribute toAttr = toNode.Attributes[attrName];
                if (fromAttr != null)
                {
                    if (toAttr == null)
                    {
                        toAttr = AddNodeAttribute(toNode, attrName, fromAttr.Value);
                    }
                    else
                    {
                        SetNodeAttribute(toNode, attrName, fromAttr.Value);
                    }
                }
            }
            catch(Exception)
            {
                success = false;
            }
            return success;
        }
        /// <summary>
        /// Gets the node attribute string.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string GetNodeAttribute(XmlNode node, string attrName, string defaultValue)
        {
            string value = defaultValue;
            if (node != null && node.Attributes[attrName] != null)
            {
                try
                {
                    value = node.Attributes[attrName].InnerText;
                }
                catch (Exception)
                {
                }
            }
            return value;
        }

        /// <summary>
        /// Gets the node attribute string.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <returns></returns>
        public static string GetNodeAttribute(XmlNode node, string attrName)
        {
            return GetNodeAttribute(node, attrName, null);
        }

        /// <summary>
        /// Gets the node attribute.
        /// </summary>
        /// <typeparam name="T">The type of attribute.</typeparam>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetNodeAttribute<T>(XmlNode node, string attrName, T defaultValue)
        {
            return ConvertHelper.StringTo<T>(node.Attributes[attrName].InnerText, defaultValue);
        }

        /// <summary>
        /// Gets the node attribute.
        /// </summary>
        /// <typeparam name="T">The type of attribute.</typeparam>
        /// <param name="node">The node.</param>
        /// <param name="attrName">Name of the attr.</param>
        /// <returns></returns>
        public static T GetNodeAttribute<T>(XmlNode node, string attrName)
        {
            return GetNodeAttribute<T>(node, attrName, default);
        }

        /// <summary>
        /// The XML node to string.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static string NodeToString(XmlNode node)
        {
            StringWriter sw = new(new StringBuilder(""));
            XmlTextWriter writer = new(sw);
            writer.Formatting = Formatting.Indented;
            if (node == null)
            {
                writer.WriteStartElement(string.Empty);
            }
            else
            {
                writer.WriteStartElement(node.Name);

                //  Write any attributes 
                foreach (XmlAttribute attr in node.Attributes)
                {
                    writer.WriteAttributeString(attr.Name, attr.Value);
                }

                //  Write child nodes
                XmlNodeList nodes = node.SelectNodes("child::*");
                if (nodes != null)
                {
                    foreach (XmlNode n in nodes)
                    {
                        NodeNavigator.LoopThroughChildren(writer, n);
                    }
                }
            }

            writer.WriteEndElement();
            writer.Close();

            return sw.ToString();
        }

        /// <summary>
        /// The XmlNodeList to string.
        /// </summary>
        /// <param name="nodeList">The node list.</param>
        /// <returns></returns>
        public static string NodeListToString(XmlNodeList nodeList)
        {
            if (nodeList != null)
            {
                StringBuilder sb = new();
                foreach (XmlNode node in nodeList)
                {
                    if (sb.Length == 0)
                        sb.Append(NodeToString(node));
                    else
                        sb.Append("\r\n" + NodeToString(node));
                }
                return sb.ToString();
            }
            return string.Empty;
        }

        /// <summary>
        /// Serializes the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Serialize(object value)
        {
            XmlSerializer serialize = new(value.GetType());
            XmlDocument doc = new();
            using (StringWriter writer = new())
            {
                serialize.Serialize(writer, value);
                doc.LoadXml(writer.GetStringBuilder().ToString());
            }
            return doc.OuterXml; 
        }

        /// <summary>
        /// Deserializes the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static T Deserialize<T>(string value)
        {
            object result = Deserialize(value, typeof(T));
            if (result == null) return default;
            return (T)result;
        }

        /// <summary>
        /// Deserializes the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static object Deserialize(string value, Type type)
        {
            XmlSerializer serializer = new(type);
            StringReader reader = new(value);
            try
            {
                return serializer.Deserialize(reader);
            }
            catch
            {
            }
            return null;
        }

        /// <summary>
        /// Replaces invalid XML characters in a string with their valid XML equivalent.
        /// </summary>
        /// <param name="value">The value within which to escape invalid characters.</param>
        public static string EscapeXML(string value)
        {
            return System.Security.SecurityElement.Escape(value);
        }

        /// <summary>
        /// Unescapes the XML.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string UnescapeXML(string value)
        {
            if (string.IsNullOrEmpty(value)) return value;
            if(value.IndexOf("&apos;") > -1) value = value.Replace("&apos;", "'");
            if (value.IndexOf("&quot;") > -1) value = value.Replace("&quot;", "\"");
            if (value.IndexOf("&gt;") > -1) value = value.Replace("&gt;", ">");
            if (value.IndexOf("&lt;") > -1) value = value.Replace("&lt;", "<");
            if (value.IndexOf("&amp;") > -1) value = value.Replace("&amp;", "&");
            return value;
        }
        #endregion
    }

    
}