using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using UnityEngine;
using UnityEngine.Networking;

namespace UVC.Util
{
    /// <summary>
    /// XML 데이터 처리 유틸리티 클래스
    /// </summary>
    public class XmlDataUtil : MonoBehaviour
    {
        private static XmlDataUtil __instance = null;

        public static XmlDataUtil Instance
        {
            get
            {
                if(__instance == null)
                {
                    __instance = new GameObject("_XmlDataUtil").AddComponent<XmlDataUtil>();
                }
                return __instance;
            }
        }


        private void Awake()
        {
            DontDestroyOnLoad(gameObject);
        }


        /// <summary>
        /// 데이터 직렬화 + XML 파일 저장
        /// </summary>
        /// <param name="filePath">저장될 파일 경로</param>
        /// <param name="data">직렬화할 데이터</param>
        /// <example>
        /// <code>
        /// var data = new MyData { Name = "Test", Age = 25 };
        /// XmlDataUtil.Instance.SerializeXmlData("path/to/file.xml", data);
        /// </code>
        /// </example>
        public void SerializeXmlData(string filePath, object data)
        {
            XmlSerializer serializer = new XmlSerializer(data.GetType());
            using (TextWriter writer = new StreamWriter(filePath))
            {
                serializer.Serialize(writer, data);
            }
        }

        /// <summary>
        /// 데이터 비직렬화 + XML 파일 로드
        /// </summary>
        /// <typeparam name="T">비직렬화할 데이터 타입</typeparam>
        /// <param name="filePath">XML 파일 경로</param>
        /// <param name="completeAction">로드 완료 후 실행할 콜백</param>
        /// <example>
        /// <code>
        /// XmlDataUtil.Instance.DeserializeXmlData<MyData>("path/to/file.xml", (data) =>
        /// {
        ///     Debug.Log($"Name: {data.Name}, Age: {data.Age}");
        /// });
        /// </code>
        /// </example>
        public void DeserializeXmlData<T>(string filePath, Action<T> completeAction)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));

            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                XmlReader reader = XmlReader.Create(fs);

                if (completeAction != null)
                {
                    completeAction.Invoke((T)serializer.Deserialize(reader));
                }
            }
        }

        /// <summary>
        /// XML 데이터 저장
        /// </summary>
        /// <param name="filePath">저장될 파일 경로</param>
        /// <param name="data">저장할 데이터</param>
        /// <example>
        /// <code>
        /// var data = new MyData { Name = "Test", Age = 25 };
        /// XmlDataUtil.Instance.SaveXmlData("path/to/file.xml", data);
        /// </code>
        /// </example>
        public void SaveXmlData(string filePath, object data)
        {
            if (data == null) return;

            //파일이 있으면 기존 파일 로드 후 수정
            if(File.Exists(filePath))
            {
                StartCoroutine(LoadXml(filePath, (XDocument xdoc) => 
                {
                    UpdateData(xdoc, data, (isChanged, isDataError) => 
                    {
                        if (isChanged && !isDataError)
                        {
                            xdoc.Save(filePath);
                            Debug.Log("SaveXmlData!");
                        }
                        else if (isDataError)
                        {
                            Debug.Log("XML 데이터 노드 순서가 잘못되었다.! 다시 강제로 직렬화한다.");
                            SerializeXmlData(filePath, data);
                        }
                    });
                }));
            }
            //파일이 없으면 새롭게 직렬화, 저장
            else
            {
                SerializeXmlData(filePath, data);
            }
        }

        /// <summary>
        /// XML 데이터 로드
        /// </summary>
        /// <param name="filePath">XML 파일 경로</param>
        /// <param name="OnLoadComplete">로드 완료 이벤트 핸들러</param>
        /// <param name="OnError">에러 핸들러</param>
        /// <returns>코루틴</returns>
        /// <example>
        /// <code>
        /// StartCoroutine(XmlDataUtil.Instance.LoadXml("path/to/file.xml", (xdoc) =>
        /// {
        ///     Debug.Log(xdoc.ToString());
        /// }, (error) =>
        /// {
        ///     Debug.LogError(error);
        /// }));
        /// </code>
        /// </example>
        public IEnumerator LoadXml(string filePath, Action<XDocument> OnLoadComplete, Action<string> OnError = null)
        {
            if (!filePath.Contains("://"))
            {
                filePath = "file://" + filePath;
            }
            using (UnityWebRequest www = UnityWebRequest.Get(filePath))
            {
                www.timeout = 5;//5초
                yield return www.SendWebRequest();

                if (www.isNetworkError || www.isHttpError)
                {
                    if (OnError != null)
                    {
                        OnError(www.error);
                    }
                }
                else
                {
                    using (MemoryStream memStream = new MemoryStream())
                    {
                        memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length);
                        memStream.Position = 0;

                        XmlReader reader = XmlReader.Create(memStream);
                        XDocument xdoc = XDocument.Load(reader);

                        if (OnLoadComplete != null)
                        {
                            OnLoadComplete(xdoc);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// XML 데이터를 비직렬화하여 로드합니다.
        /// </summary>
        /// <typeparam name="T">비직렬화할 데이터 타입</typeparam>
        /// <param name="filePath">XML 파일 경로</param>
        /// <param name="OnLoadComplete">로드 완료 후 실행할 콜백 (비직렬화된 데이터 전달)</param>
        /// <param name="OnError">에러 발생 시 실행할 콜백 (에러 메시지 전달)</param>
        /// <returns>코루틴</returns>
        /// <example>
        /// <code>
        /// StartCoroutine(XmlDataUtil.Instance.LoadXml<MyData>("path/to/file.xml", (data) =>
        /// {
        ///     Debug.Log($"Name: {data.Name}, Age: {data.Age}");
        /// }, (error) =>
        /// {
        ///     Debug.LogError($"Error loading XML: {error}");
        /// }));
        /// </code>
        /// </example>
        [Obsolete]
        public IEnumerator LoadXml<T>(string filePath, Action<T> OnLoadComplete, Action<string> OnError = null)
        {
            if (!filePath.Contains("://"))
            {
                filePath = "file://" + filePath;
            }
            using (UnityWebRequest www = UnityWebRequest.Get(filePath))
            {
                www.timeout = 5;//5초
                yield return www.SendWebRequest();

                if (www.isNetworkError || www.isHttpError)
                {
                    if (OnError != null)
                    {
                        OnError(www.error);
                    }
                }
                else
                {

                    using (MemoryStream memStream = new MemoryStream())
                    {
                        memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length);
                        memStream.Position = 0;

                        XmlSerializer serializer = new XmlSerializer(typeof(T));
                        XmlReader reader = XmlReader.Create(memStream);
                        T result = (T)serializer.Deserialize(reader);

                        if (OnLoadComplete != null)
                        {
                            OnLoadComplete(result);
                        }
                    }
                }
            }
        }


        private void UpdateData(XDocument xdoc, object data, Action<bool, bool> OnComplete)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(data.GetType());
            using (StringWriter textWriter = new StringWriter())
            {
                xmlSerializer.Serialize(textWriter, data);
                var currentData = XElement.Parse(textWriter.ToString());
                bool isChanged = false;
                bool isDataError = false;

                var currentItemList = currentData.Descendants().ToList();
                var originItemList = xdoc.Root.Descendants().ToList();

                foreach (var currentItem in currentItemList)
                {
                    foreach (var originItem in originItemList)
                    {
                        if (currentItemList.IndexOf(currentItem) == originItemList.IndexOf(originItem))
                        {
                            if (currentItem.Name.Equals(originItem.Name))
                            {
                                if (!currentItem.Value.Equals(originItem.Value) && !originItem.HasElements)
                                {
                                    originItem.SetValue(currentItem.Value);
                                    isChanged = true;
                                }

                                foreach (var currentAtt in currentItem.Attributes())
                                {
                                    foreach (var originAtt in originItem.Attributes())
                                    {
                                        if (!currentAtt.Value.Equals(originAtt.Value))
                                        {
                                            originAtt.SetValue(currentAtt.Value);
                                            isChanged = true;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                isDataError = true;
                                break;
                            }
                        }
                    }
                }

                if(OnComplete != null)
                {
                    OnComplete(isChanged, isDataError);
                }
            }
        }

        /// <summary>
        /// XML 데이터를 문자열로 직렬화
        /// </summary>
        /// <param name="toSerialize">직렬화할 객체</param>
        /// <returns>XML 문자열</returns>
        /// <example>
        /// <code>
        /// var xmlString = XmlDataUtil.ToXmlString(new MyData { Name = "Test", Age = 25 });
        /// Debug.Log(xmlString);
        /// </code>
        /// </example>
        public static string ToXmlString(object toSerialize)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

            using (StringWriter textWriter = new StringWriter())
            {
                xmlSerializer.Serialize(textWriter, toSerialize);
                return textWriter.ToString();
            }
        }

    }
}
