#nullable enable

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace UVC.Extension
{
    /// <summary>
    /// 직렬화 관련 확장 메서드를 제공하는 클래스입니다.
    /// </summary>
    public static class SerializableEx
    {
        /// <summary>
        /// 객체의 깊은 복사본을 생성합니다.
        /// </summary>
        /// <typeparam name="T">복사할 객체의 타입 (Serializable 속성이 필요)</typeparam>
        /// <param name="source">복사할 원본 객체</param>
        /// <returns>원본 객체의 깊은 복사본 또는 직렬화 불가능한 경우 기본값</returns>
        /// <remarks>
        /// 이 메서드는 BinaryFormatter를 사용하여 객체를 깊은 복사합니다.
        /// 대상 타입은 반드시 [Serializable] 속성을 가져야 합니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// [Serializable]
        /// public class Person
        /// {
        ///     public string Name { get; set; }
        ///     public int Age { get; set; }
        /// }
        /// 
        /// // 사용 예시
        /// var original = new Person { Name = "홍길동", Age = 30 };
        /// var copy = original.DeepCopy();
        /// 
        /// // copy는 original과 동일한 값을 가지지만 서로 다른 메모리 참조
        /// copy.Name = "김철수"; // original.Name은 여전히 "홍길동"
        /// </code>
        /// </example>
        public static T? DeepCopy<T>(this T source)// where T : new()
        {
            if (!typeof(T).IsSerializable)
            {
                return default(T);// 직렬화 불가능한 경우 기본값 반환
            }

            try
            {
                object result = null;
                using (var ms = new MemoryStream())
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(ms, source);// 객체를 메모리 스트림에 직렬화
                    ms.Position = 0;// 스트림 위치 초기화
                    result = (T)formatter.Deserialize(ms);// 역직렬화하여 새 객체 생성
                    ms.Close();// 스트림 닫기
                }

                return (T)result;
            }
            catch (Exception ex)
            {   
                return default(T);// 예외 발생 시 기본값 반환
            }
        }

        /// <summary>
        /// 객체를 바이트 배열로 직렬화합니다.
        /// </summary>
        /// <typeparam name="T">직렬화할 객체의 타입 (Serializable 속성이 필요)</typeparam>
        /// <param name="obj">직렬화할 객체</param>
        /// <returns>직렬화된 바이트 배열 또는 실패 시 null</returns>
        /// <example>
        /// <code>
        /// [Serializable]
        /// public class Person
        /// {
        ///     public string Name { get; set; }
        ///     public int Age { get; set; }
        /// }
        /// 
        /// // 사용 예시
        /// var person = new Person { Name = "홍길동", Age = 30 };
        /// byte[] data = person.SerializeToBytes();
        /// // 직렬화된 바이트 배열을 저장하거나 네트워크 전송 가능
        /// </code>
        /// </example>
        public static byte[] SerializeToBytes<T>(this T obj) where T : class
        {
            if (obj == null || !typeof(T).IsSerializable)
                return null;

            try
            {
                using (var ms = new MemoryStream())
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(ms, obj);
                    return ms.ToArray();
                }
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// 바이트 배열에서 객체로 역직렬화합니다.
        /// </summary>
        /// <typeparam name="T">역직렬화할 객체 타입 (Serializable 속성이 필요)</typeparam>
        /// <param name="bytes">역직렬화할 바이트 배열</param>
        /// <returns>역직렬화된 객체 또는 실패 시 null</returns>
        /// <example>
        /// <code>
        /// // 바이트 배열에서 객체 복원
        /// byte[] savedData = GetSavedDataFromSomewhere();
        /// Person restoredPerson = savedData.DeserializeFromBytes<Person>();
        /// 
        /// if (restoredPerson != null)
        /// {
        ///     Console.WriteLine($"이름: {restoredPerson.Name}, 나이: {restoredPerson.Age}");
        /// }
        /// </code>
        /// </example>
        public static T DeserializeFromBytes<T>(this byte[] bytes) where T : class
        {
            if (bytes == null || bytes.Length == 0 || !typeof(T).IsSerializable)
                return null;

            try
            {
                using (var ms = new MemoryStream(bytes))
                {
                    var formatter = new BinaryFormatter();
                    return (T)formatter.Deserialize(ms);
                }
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// 객체를 파일에 직렬화하여 저장합니다.
        /// </summary>
        /// <typeparam name="T">직렬화할 객체의 타입 (Serializable 속성이 필요)</typeparam>
        /// <param name="obj">저장할 객체</param>
        /// <param name="filePath">저장할 파일 경로</param>
        /// <returns>성공 여부</returns>
        /// <example>
        /// <code>
        /// var settings = new GameSettings { Volume = 0.8f, Difficulty = "Hard" };
        /// bool success = settings.SerializeToFile("Assets/settings.dat");
        /// </code>
        /// </example>
        public static bool SerializeToFile<T>(this T obj, string filePath) where T : class
        {
            if (obj == null || !typeof(T).IsSerializable || string.IsNullOrEmpty(filePath))
                return false;

            try
            {
                using (var fs = new FileStream(filePath, FileMode.Create))
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(fs, obj);
                    return true;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 파일에서 객체를 역직렬화하여 로드합니다.
        /// </summary>
        /// <typeparam name="T">역직렬화할 객체 타입 (Serializable 속성이 필요)</typeparam>
        /// <param name="filePath">로드할 파일 경로</param>
        /// <returns>역직렬화된 객체 또는 실패 시 null</returns>
        /// <example>
        /// <code>
        /// GameSettings settings = DeserializeFromFile<GameSettings>("Assets/settings.dat");
        /// if (settings != null)
        /// {
        ///     ApplySettings(settings.Volume, settings.Difficulty);
        /// }
        /// </code>
        /// </example>
        public static T DeserializeFromFile<T>(string filePath) where T : class
        {
            if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath) || !typeof(T).IsSerializable)
                return null;

            try
            {
                using (var fs = new FileStream(filePath, FileMode.Open))
                {
                    var formatter = new BinaryFormatter();
                    return (T)formatter.Deserialize(fs);
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
