using System.Globalization;

namespace Kit.Helpers
{
    using System;
    using System.Xml;
    using System.Linq;
    using System.Collections.Generic;

    public enum ItemLogTypes
    {
        None = 0,
        Create = 1,
        Update = 2,
        Delete = 3,
    }

    public class ItemLogGenerator
    {
        public object ObjectId { get; set; }
        public string ObjectType { get; set; }

        public ItemLogGenerator(object objectId, string objectType)
        {
            ObjectId = objectId;
            ObjectType = objectType;
        }

        public List<ItemLog> Items { get; set; } = new List<ItemLog>();

        public ItemLogGenerator Add(ItemLog item)
        {
            if (item != null)
            {
                Items.Add(new ItemLog(ObjectId, ObjectType, item.Name, item.Title) { Type = item.Type, ValueNew = item.ValueNew, ValueOld = item.ValueOld });
            }

            return this;
        }

        public ItemLogGenerator AddRange(IEnumerable<ItemLog> items)
        {
            if (items.IsNullOrEmpty() == false)
            {
                items.ForEach(item =>
                {
                    Items.Add(new ItemLog(ObjectId, ObjectType, item.Name, item.Title) { Type = item.Type, ValueNew = item.ValueNew, ValueOld = item.ValueOld });
                });
            }

            return this;
        }
    }

    public class ItemLog
    {
        public ItemLogTypes Type { get; set; }

        public ItemLog(string name, string? title = null)
        {
            Type = ItemLogTypes.None;

            Name = name ?? string.Empty;
            Title = title ?? string.Empty;
        }

        public ItemLog(object objectId, string objectType, string name, string? title = null) : this(name, title)
        {
            ObjectId = objectId;
            ObjectType = objectType ?? string.Empty;
        }

        public object ObjectId { get; set; }
        public string ObjectType { get; set; }
        public string Name { get; set; }
        public string Title { get; set; }
        public string ValueOld { get; set; }
        public string ValueNew { get; set; }

        public ItemLog FillType()
        {
            bool hasValueOld = string.IsNullOrEmpty(ValueOld) == false;
            bool hasValueNew = string.IsNullOrEmpty(ValueNew) == false;

            if (hasValueOld == false && hasValueNew == false)
            {
                Type = ItemLogTypes.None;
                return this;
            }

            if (hasValueOld == false && hasValueNew)
            {
                Type = ItemLogTypes.Create;
                return this;
            }

            if (hasValueOld && hasValueNew == false)
            {
                Type = ItemLogTypes.Delete;
                return this;
            }

            if (ValueOld.Equals(ValueNew) == false)
            {
                Type = ItemLogTypes.Update;
                return this;
            }

            Type = ItemLogTypes.None;
            return this;
        }
    }

    public static class SystemXmlNodeExtensionMethods
    {
        private static readonly CultureInfo ci = new CultureInfo("en-US");

        public static void RemoveAttribute(this XmlDocument input, string name)
        {
            foreach (XmlNode item in input.SelectNodes("//*[@{0}]".ApplyFormat(name)))
            {
                item.Attributes.Remove(item.Attributes[name]);
            }
        }

        public static XmlNode SetElementValue(this XmlNode input, string name, string value)
        {
            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                element = input.OwnerDocument.CreateElement(name);
                input.AppendChild(element);
            }
            element.InnerText = value;
            return element;
        }

        public static XmlNode SetElementValue(this XmlNode input, string name, DateTime value)
        {
            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                element = input.OwnerDocument.CreateElement(name);
                input.AppendChild(element);
            }
            element.InnerText = XmlConvert.ToString(value, XmlDateTimeSerializationMode.RoundtripKind);
            return element;
        }

        public static XmlNode SetElement(this XmlNode input, string name, Object value)
        {
            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                element = input.OwnerDocument.CreateElement(name);
                input.AppendChild(element);
            }
            element.InnerText = value.ToString();
            return element;
        }


        public static XmlNode SetElementCDataValue(this XmlNode input, string name, string value)
        {
            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                var root = input.OwnerDocument.CreateElement(name);
                element = input.OwnerDocument.CreateNode(XmlNodeType.CDATA, name, null);
                root.AppendChild(element);
                input.AppendChild(root);
            }
            element.InnerText = value;
            return element;
        }


        public static XmlNode SetElementValue(this XmlNode input, string name, decimal value)
        {
            return input.SetElementValue(name, value.ToString(System.Globalization.CultureInfo.InvariantCulture));
        }

        public static XmlNode SetElementValue(this XmlNode input, string name, decimal? value, string elementsAfterPostion)
        {
            if (value.HasValue)
            {
                return input.SetElementNullableValue(name, elementsAfterPostion, value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
            }
            else
            {
                return input.SetElementNullableValue(name, null);
            }
        }

        public static XmlNode SetElementValue(this XmlNode input, string name, int? value, string elementsAfterPostion)
        {
            if (value.HasValue && value != 0)
            {
                return input.SetElementNullableValue(name, elementsAfterPostion, value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
            }
            else
            {
                return input.SetElementNullableValue(name, null);
            }
        }

        private static XmlNode GetElementAfterInsert(XmlNode root, string elementsAfterPostion)
        {
            if (!elementsAfterPostion.IsNullOrEmpty())
            {
                var elements = root.SelectNodes(elementsAfterPostion);
                return elements.Cast<XmlNode>().LastOrDefault();
            }
            return null;
        }

        public static XmlNode SetElementNullableValue(this XmlNode input, string name, string value)
        {
            return input.SetElementNullableValue(name, "", value);
        }

        public static XmlNode SetElementNullableValue(this XmlNode input, string name, string elementsAfterPostion, string value)
        {
            if (!value.IsNullOrEmpty())
            {
                var element = input.SelectSingleNode(name);
                if (element != null)
                {
                    input.RemoveChild(element);
                }

                element = input.OwnerDocument.CreateElement(name);
                var after = GetElementAfterInsert(input, elementsAfterPostion);
                if (after == null)
                    input.InsertBefore(element, input.FirstChild);
                else
                    input.InsertAfter(element, after);

                element.InnerText = value;

                return element;
            }
            else
            {
                var child = input.SelectSingleNode(name);
                if (child != null)
                    input.RemoveChild(child);
                return null;
            }
        }

        public static XmlNode CreateElement(this XmlNode input, string name) => input.CreateElement(input.OwnerDocument, name);

        public static XmlNode CreateElement(this XmlNode input, XmlDocument xmlDocument, string name)
        {
            //var element = input.SelectSingleNode(name);
            //if (element == null)
            //{
            var element = xmlDocument.CreateElement(name);
            input.AppendChild(element);
            //}
            return element;
        }

        public static XmlAttribute CreateAttributeValue(this XmlNode input, string name, string value)
        {
            XmlAttribute element = null;
            //try
            {
                element = input.Attributes[name];
            }
            //catch
            {

                if (element == null)
                {
                    element = input.OwnerDocument.CreateAttribute(name);
                    input.Attributes.Append(element);
                }
                element.InnerText = value;
            }
            return element;
        }

        public static bool HasAttribute(this XmlNode input, string name)
        {
            return input.Attributes[name] != null;
        }

        public static void SetAttribute(this XmlNode input, string name, Object value)
        {
            XmlAttribute element = null;
            //try
            {
                element = input.Attributes[name];
            }
            //catch { }
            if (element == null)
            {
                element = input.OwnerDocument.CreateAttribute(name);
                input.Attributes.Append(element);
            }
            element.InnerText = value.ToString();
        }

        public static void SetAttributeNullable(this XmlNode input, string name, string? value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                input.RemoveAttribute(name);
            }
            else
            {
                input.SetAttribute(name, value);
            }
        }

        public static void RemoveAttribute(this XmlNode input, string name)
        {
            if (input.Attributes == null || input.Attributes.Count == 0) return;

            XmlAttribute? element = input.Attributes[name];
            if (element == null) return;

            ((XmlElement)input).RemoveAttribute(name);
        }

        public static void SetAttributeValue(this XmlNode input, string name, string value)
        {
            XmlAttribute element = null;
            //try
            {
                element = input.Attributes[name];
            }
            //catch { }
            if (element == null)
            {
                element = input.OwnerDocument.CreateAttribute(name);
                input.Attributes.Append(element);
            }
            element.InnerText = value;
        }

        public static void SetAttributeValue(this XmlNode input, string name, Guid? value)
        {
            XmlAttribute element = null;
            //try
            {
                element = input.Attributes[name];
            }
            //catch { }
            if (element == null)
            {
                element = input.OwnerDocument.CreateAttribute(name);
                input.Attributes.Append(element);
            }
            element.InnerText = value.ToStringOrEmpty();
        }
        public static string GetValueAttribute(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, string.Empty);
        }

        public static string GetValueAttribute(this XmlNode input, string name, string empty)
        {
            XmlAttribute element = null;
            //try
            {
                if (input == null) return empty;
                element = input.Attributes[name];

                if (element != null)
                    return element.InnerText;
                else
                    return empty;
            }
            //catch
            //{
            //    return empty;
            //}
        }
        public static string GetValueAttribute(this XmlNode input, string name, string empty, XmlNamespaceManager namespaces)
        {
            XmlNode element = null;
            //try
            {
                if (input == null) return empty;
                element = input.SelectSingleNode(name, namespaces);

                if (element != null)
                    return element.InnerText;
                else
                    return empty;
            }
            //catch
            //{
            //    return empty;
            //}
        }
        public static string GetValue(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty);
        }

        public static DateTime GetValueDateTime(this XmlNode input, string name)
        {
            return XmlConvert.ToDateTime(input.GetValue(name, String.Empty), XmlDateTimeSerializationMode.RoundtripKind);
        }

        public static string GetValue(this XmlNode input, string name, string empty)
        {
            var element = input.SelectSingleNode(name);
            if (element == null) return empty;
            return element.InnerText;
        }

        public static string GetValue(this XmlNode input, string name, XmlNamespaceManager namespaces)
        {
            return input.GetValue(name, String.Empty, namespaces);
        }

        public static string GetValue(this XmlNode input, string name, string empty, XmlNamespaceManager namespaces)
        {
            var element = input.SelectSingleNode(name, namespaces);
            if (element == null) return empty;
            return element.InnerText;
        }

        public static string GetValueCData(this XmlNode input, string name)
        {
            return input.GetValueCData(name, string.Empty);
        }

        public static string GetValueCData(this XmlNode input, string name, string empty)
        {
            var element = input.SelectSingleNode(name);
            if (element == null) return empty;
            if (element.FirstChild == null) return element.InnerText;
            return element.FirstChild.InnerText;
        }

        public static DateTime? GetValueDate(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToDateXMLNullable();
        }

        public static DateTime GetValueDate(this XmlNode input, string name, DateTime empty)
        {
            return input.GetValue(name, String.Empty).TryParseToDateXMLNullable(empty);
        }

        public static short GetValueShort(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToShort();
        }

        public static bool GetValueBool(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToBool(false);
        }
        public static bool GetValueBool(this XmlNode input, string name, bool empty)
        {
            return input.GetValue(name, String.Empty).TryParseToBool(empty);
        }
        public static Int32? GetValueInt32Nulable(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToInt32Nulable();
        }

        public static Int32 GetValueInt32(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToInt32();
        }
        public static Int32 GetValueInt32(this XmlNode input, string name, int empty)
        {
            return input.GetValue(name, String.Empty).TryParseToInt32(empty);
        }
        public static byte GetValueByte(this XmlNode input, string name, byte empty)
        {
            return input.GetValue(name, String.Empty).TryParseToByte(empty);
        }
        public static decimal? GetValueDecimal(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToDecimalNulable();
        }
        public static decimal GetValueDecimal(this XmlNode input, string name, decimal empty)
        {
            return input.GetValue(name, String.Empty).TryParseToDecimal(empty);
        }
        public static Guid GetValueGuid(this XmlNode input, string name, Guid empty)
        {
            return input.GetValue(name, String.Empty).TryParseToGuid(empty);
        }

        public static Guid GetValueGuid(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToGuid(Guid.Empty);
        }
        public static Guid? GetValueGuidNullable(this XmlNode input, string name)
        {
            return input.GetValue(name, String.Empty).TryParseToGuidNulable(null);
        }

        public static Guid GetValueAttributeGuid(this XmlNode input, string name)
        {
            return input.GetValueAttributeGuid(name, null) ?? Guid.Empty;
        }

        public static Guid? GetValueAttributeGuid(this XmlNode input, string name, Guid? empty)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToGuidNulable(empty);
        }

        public static Guid? GetValueAttributeGuid(this XmlNode input, string name, XmlNamespaceManager namespaces, Guid? empty)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToGuidNulable(empty);
        }


        public static int GetValueAttributeInt32(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToInt32(0);
        }

        public static int? GetValueAttributeInt32Nullable(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToInt32Nulable();
        }

        public static double GetValueAttributeDouble(this XmlNode input, string name)
        {
            return GetValueAttributeDouble(input, name, 0);
        }

        public static double GetValueAttributeDouble(this XmlNode input, string name, double @default)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToDouble(@default);
        }

        public static double? GetValueAttributeDoubleNullable(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToDoubleNulable();
        }

        public static long GetValueAttributeInt64(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToInt64(0);
        }

        public static long? GetValueAttributeInt64Nullable(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToInt64Nulable();
        }

        public static ulong GetValueAttributeUInt64(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToUInt64(0);
        }

        public static ulong? GetValueAttributeUInt64Nullable(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToUInt64Nulable();
        }

        public static bool GetValueAttributeBool(this XmlNode input, string name)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToBool(false);
        }
        public static bool GetValueAttributeBool(this XmlNode input, string name, bool empty)
        {
            return input.GetValueAttribute(name, String.Empty).TryParseToBool(empty);
        }


        public static string ToStringXMLShema(this DateTime dt)
        {
            if (dt == null) return String.Empty;
            return dt.ToString("yyyy-MM-ddTHH:mm:ss");
        }

        public static string ToStringXMLShema(this DateTime? dt)
        {
            if (dt == null) return String.Empty;
            return (dt ?? DateTime.UtcNow).ToString("yyyy-MM-ddTHH:mm:ss");
        }

        public static string ToStringSoutXML(this DateTime dt)
        {
            if (dt == null) return String.Empty;
            return dt.ToString("yyyy-MM-dd");
        }

        public static string ToStringSoutXML(this DateTime? dt)
        {
            if (dt == null) return String.Empty;
            return (dt ?? DateTime.UtcNow).ToString("yyyy-MM-dd");
        }

        public static double GetValueDouble_(this XmlNode input, string name)
        {
            double value;
            if (double.TryParse(input.GetValue(name), NumberStyles.Any, ci.NumberFormat, out value))
                return value;
            return double.MinValue;
        }
        public static double GetValueDouble_(this XmlNode input)
        {
            double value;
            if (double.TryParse(input.InnerText, NumberStyles.Any, ci.NumberFormat, out value))
                return value;
            return double.MinValue;
        }
        public static void ForEach(this XmlNodeList nodeList, Action<XmlNode> action)
        {
            foreach (XmlNode node in nodeList)
            {
                action(node);
            }
        }
        public static IEnumerable<XmlNode> ToList<TResult>(this XmlNodeList nodeList)
        {
            var resultList = new List<XmlNode>();
            foreach (XmlNode node in nodeList)
            {
                resultList.Add(node);
            }
            return resultList;
        }
        public static IEnumerable<TResult> Select<TResult>(this XmlNodeList nodeList, Func<XmlNode, TResult> action)
        {
            var resultList = new List<TResult>();
            foreach (XmlNode node in nodeList)
            {
                resultList.Add(action(node));
            }
            return resultList;
        }

        /// <summary>
        /// Get the node located along the <paramref name="path"/>. <br />
        /// If the element could not be found, the element itself and the path to it will be created.
        /// </summary>
        /// <param name="xmlNodeRoot">Root node</param>
        /// <param name="path">XPath to the node. XPath should only include tag names and separators.</param>
        /// <returns>Target child node</returns>
        public static XmlNode GetOrRestoreByPath(this XmlNode xmlNodeRoot, string path)
        {
            XmlNode xmlNodeTarget = xmlNodeRoot.SelectSingleNode(path);
            if (xmlNodeTarget != null)
            {
                return xmlNodeTarget;
            }

            string[] pathItems = path.Trim('/').Split('/');

            XmlNode xmlNodeCurrent = xmlNodeRoot;

            foreach (string pathItem in pathItems)
            {
                XmlNode xmlNodeItem = xmlNodeCurrent.SelectSingleNode(pathItem);
                if (xmlNodeItem == null)
                {
                    xmlNodeItem = xmlNodeCurrent.AppendChild((xmlNodeRoot.OwnerDocument ?? (XmlDocument)xmlNodeRoot).CreateElement(pathItem));
                }

                xmlNodeCurrent = xmlNodeItem;
            }

            return xmlNodeCurrent;
        }

        public static XmlNode RemoveAllEx(this XmlNode xmlNode)
        {
            xmlNode.RemoveAll();
            return xmlNode;
        }

        public static ItemLog ModifyAttribute(this XmlNode input, ItemLog itemLog, string name, object? value)
        {
            itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name);
            itemLog.ValueNew = value?.ToString() ?? string.Empty;

            XmlAttribute element = input.Attributes[name];

            if (element == null)
            {
                element = input.OwnerDocument.CreateAttribute(name);
                input.Attributes.Append(element);
            }
            else
            {
                itemLog.ValueOld = element.InnerText;
            }

            element.InnerText = itemLog.ValueNew;

            return itemLog.FillType();
        }

        public static ItemLog ModifyElement(this XmlNode input, ItemLog itemLog, string name, object value)
        {
            itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name);
            itemLog.ValueNew = value?.ToString() ?? string.Empty;

            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                element = input.OwnerDocument.CreateElement(name);
                input.AppendChild(element);
            }
            else
            {
                itemLog.ValueOld = element.InnerText;
            }

            element.InnerText = itemLog.ValueNew;

            return itemLog.FillType();
        }

        public static ItemLog ModifyElementCData(this XmlNode input, ItemLog itemLog, string name, string value)
        {
            itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name);
            itemLog.ValueNew = value?.ToString() ?? string.Empty;

            var element = input.SelectSingleNode(name);
            if (element == null)
            {
                var root = input.OwnerDocument.CreateElement(name);
                element = input.OwnerDocument.CreateNode(XmlNodeType.CDATA, name, null);
                root.AppendChild(element);
                input.AppendChild(root);
            }
            else
            {
                itemLog.ValueOld = GetValueCData(element, name);
            }

            element.InnerText = itemLog.ValueNew;

            return itemLog.FillType();
        }
    }
}