//------------------------------------------------------------------------------
//  此代码版权（除特别声明或在RRQMCore.XREF命名空间的代码）归作者本人若汝棋茗所有
//  源代码使用协议遵循本仓库的开源协议及附加协议，若本仓库没有设置，则按MIT开源协议授权
//  CSDN博客：https://blog.csdn.net/qq_40374647
//  哔哩哔哩视频：https://space.bilibili.com/94253567
//  Gitee源代码仓库：https://gitee.com/RRQM_Home
//  Github源代码仓库：https://github.com/RRQM
//  交流QQ群：234762506
//  感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using RRQMCore.ByteManager;
using RRQMCore.Helper;
using RRQMSocket.Http;
using System;
using System.Collections;
using System.Reflection;
using System.Text;
using System.Xml;

namespace RRQMSocket.RPC.XmlRpc
{
    internal static class XmlDataTool
    {
        public static object GetValue(XmlNode valueNode, Type type)
        {
            if (valueNode == null)
            {
                return type.GetDefault();
            }
            switch (valueNode.Name)
            {
                case "boolean":
                    {
                        return bool.Parse(valueNode.InnerText);
                    }
                case "i4":
                case "int":
                    {
                        return int.Parse(valueNode.InnerText);
                    }
                case "double":
                    {
                        return double.Parse(valueNode.InnerText);
                    }
                case "dateTime.iso8601":
                    {
                        return DateTime.Parse(valueNode.InnerText);
                    }
                case "base64":
                    {
                        return valueNode.InnerText;
                    }
                case "struct":
                    {
                        object instance = Activator.CreateInstance(type);
                        foreach (XmlNode memberNode in valueNode.ChildNodes)
                        {
                            string name = memberNode.SelectSingleNode("name").InnerText;
                            PropertyInfo property = type.GetProperty(name);
                            property.SetValue(instance, GetValue(memberNode.SelectSingleNode("value").FirstChild, property.PropertyType));
                        }
                        return instance;
                    }
                case "arrays":
                case "array":
                    {
                        if (type.GetElementType() != null)
                        {
                            XmlNode dataNode = valueNode.SelectSingleNode("data");
                            Array array = Array.CreateInstance(type.GetElementType(), dataNode.ChildNodes.Count);

                            int index = 0;
                            foreach (XmlNode arrayValueNode in dataNode.ChildNodes)
                            {
                                array.SetValue(GetValue(arrayValueNode.FirstChild, type.GetElementType()), index);
                                index++;
                            }
                            return array;
                        }
                        else if (type.GetGenericArguments().Length == 1)
                        {
                            XmlNode dataNode = valueNode.SelectSingleNode("data");
                            IList array = (IList)Activator.CreateInstance(type);

                            foreach (XmlNode arrayValueNode in dataNode.ChildNodes)
                            {
                                array.Add(GetValue(arrayValueNode.FirstChild, type.GetGenericArguments()[0]));
                            }
                            return array;
                        }
                        return type.GetDefault();
                    }
                default:
                case "string":
                    {
                        return valueNode.InnerText;
                    }
            }
        }

        public static void CreateRequest(ByteBlock byteBlock, string host, string method, object[] parameters)
        {
            XmlDocument xml = new XmlDocument();

            XmlDeclaration xmlDecl = xml.CreateXmlDeclaration("1.0", string.Empty, string.Empty);
            xml.AppendChild(xmlDecl);

            XmlElement xmlElement = xml.CreateElement("methodCall");
            xml.AppendChild(xmlElement);

            XmlElement methodNameElement = xml.CreateElement("methodName");
            methodNameElement.InnerText = method;
            xmlElement.AppendChild(methodNameElement);

            XmlElement paramsElement = xml.CreateElement("params");
            xmlElement.AppendChild(paramsElement);

            foreach (var param in parameters)
            {
                XmlElement paramElement = xml.CreateElement("param");
                paramsElement.AppendChild(paramElement);

                XmlElement valueElement = xml.CreateElement("value");
                paramElement.AppendChild(valueElement);

                CreateParam(xml, valueElement, param);
            }

            ByteBlock xmlBlock = BytePool.Default.GetByteBlock(byteBlock.Capacity);
            xml.Save(xmlBlock);

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.AppendLine("POST / HTTP/1.1");
            stringBuilder.AppendLine("Content-Type: text/xml");
            stringBuilder.AppendLine($"Host: {host}");
            stringBuilder.AppendLine("User-Agent: RRQMXmlRpc");
            stringBuilder.AppendLine($"Content-Length: {xmlBlock.Length}");
            //stringBuilder.AppendLine("Connection: Close");
            stringBuilder.AppendLine("Connection: keep-alive");
            stringBuilder.AppendLine();
            try
            {
                byteBlock.Write(Encoding.UTF8.GetBytes(stringBuilder.ToString()));
                byteBlock.Write(xmlBlock.Buffer, 0, xmlBlock.Len);
            }
            finally
            {
                xmlBlock.Dispose();
            }
        }

        public static void CreateParam(XmlDocument xml, XmlNode xmlNode, object value)
        {
            if (value == null)
            {
                return;
            }
            if (value is int)
            {
                XmlElement valueElement = xml.CreateElement("i4");
                valueElement.InnerText = value.ToString();
                xmlNode.AppendChild(valueElement);
            }
            else if (value is bool)
            {
                XmlElement valueElement = xml.CreateElement("boolean");
                valueElement.InnerText = ((int)value).ToString();
                xmlNode.AppendChild(valueElement);
            }
            else if (value is double)
            {
                XmlElement valueElement = xml.CreateElement("double");
                valueElement.InnerText = ((double)value).ToString();
                xmlNode.AppendChild(valueElement);
            }
            else if (value is string)
            {
                XmlElement valueElement = xml.CreateElement("string");
                valueElement.InnerText = value.ToString();
                xmlNode.AppendChild(valueElement);
            }
            else if (value is DateTime)
            {
                XmlElement valueElement = xml.CreateElement("dateTime.iso8601");
                valueElement.InnerText = ((DateTime)value).ToString();
                xmlNode.AppendChild(valueElement);
            }
            else if (value is byte[])
            {
                XmlElement valueElement = xml.CreateElement("base64");
                string str = Convert.ToBase64String((byte[])value);
                valueElement.InnerText = str;
                xmlNode.AppendChild(valueElement);
            }
            else if (typeof(IList).IsAssignableFrom(value.GetType()))
            {
                IList array = (IList)value;
                XmlElement arrayElement;

                arrayElement = xml.CreateElement("array");

                xmlNode.AppendChild(arrayElement);

                XmlElement dataElememt = xml.CreateElement("data");
                arrayElement.AppendChild(dataElememt);

                foreach (var item in array)
                {
                    XmlElement valueElement = xml.CreateElement("value");
                    dataElememt.AppendChild(valueElement);
                    CreateParam(xml, valueElement, item);
                }
            }
            else
            {
                XmlElement valueElement = xml.CreateElement("struct");
                xmlNode.AppendChild(valueElement);

                PropertyInfo[] propertyInfos = value.GetType().GetProperties();
                foreach (var propertyInfo in propertyInfos)
                {
                    XmlElement memberElement = xml.CreateElement("member");
                    valueElement.AppendChild(memberElement);

                    XmlElement nameElement = xml.CreateElement("name");
                    nameElement.InnerText = propertyInfo.Name;
                    memberElement.AppendChild(nameElement);

                    XmlElement oValueElement = xml.CreateElement("value");
                    memberElement.AppendChild(oValueElement);

                    object oValue = propertyInfo.GetValue(value);
                    CreateParam(xml, oValueElement, oValue);
                }
            }
        }

        public static void CreatResponse(HttpResponse httpResponse, object value)
        {
            XmlDocument xml = new XmlDocument();

            XmlDeclaration xmlDecl = xml.CreateXmlDeclaration("1.0", string.Empty, string.Empty);
            xml.AppendChild(xmlDecl);

            XmlElement xmlElement = xml.CreateElement("methodResponse");

            xml.AppendChild(xmlElement);

            XmlElement paramsElement = xml.CreateElement("params");
            xmlElement.AppendChild(paramsElement);

            XmlElement paramElement = xml.CreateElement("param");
            paramsElement.AppendChild(paramElement);

            XmlElement valueElement = xml.CreateElement("value");
            paramElement.AppendChild(valueElement);

            CreateParam(xml, valueElement, value);

            ByteBlock xmlBlock = BytePool.Default.GetByteBlock(1024 * 4);
            xml.Save(xmlBlock);

            string xmlString = Encoding.UTF8.GetString(xmlBlock.Buffer, 0, xmlBlock.Len);

            httpResponse.FromXML(xmlString);
            xmlBlock.Dispose();
        }
    }
}