using ServiceCenter.Extensions;
using System.Text;

namespace ServiceCenter.Helpers
{
    public class TypeConversionHelper
    {
        #region 序列化写入，反序列化

        public static Func<object, object> Serialize;
        public static Func<string, Type, object> Deserialize;
        public static Func<byte[], Type, object> DeserializeRaw;

        public static object SerializeRedisValue(object value)
        {
            switch (value)
            {
                case null: return null;
                case string _:
                case byte[] _: return value;

                case bool b: return b ? "1" : "0";
                case char c: return value;
                case decimal _:
                case double _:
                case float _:
                case int _:
                case long _:
                case sbyte _:
                case short _:
                case uint _:
                case ulong _:
                case ushort _: return value.ToInvariantCultureToString();

                case DateTime time: return time.ToString("yyyy-MM-ddTHH:mm:sszzzz", System.Globalization.DateTimeFormatInfo.InvariantInfo);
                case DateTimeOffset _: return value.ToString();
                case TimeSpan span: return span.Ticks;
                case Guid _: return value.ToString();
                default:
                    if (Serialize != null) return Serialize(value);
                    return value.ConvertTo<string>();
            }
        }

        public static T DeserializeRedisValue<T>(byte[] valueRaw, Encoding encoding)
        {
            if (valueRaw == null) return default(T);
            var type = typeof(T);
            var typename = type.ToString().TrimEnd(']');
            if (typename == "System.Byte[") return (T)Convert.ChangeType(valueRaw, type);
            if (typename == "System.String") return (T)Convert.ChangeType(encoding.GetString(valueRaw), type);
            if (typename == "System.Boolean[") return (T)Convert.ChangeType(valueRaw.Select(a => a == 49).ToArray(), type);
            if (valueRaw.Length == 0) return default(T);

            string valueStr = null;
            if (type.IsValueType)
            {
                valueStr = encoding.GetString(valueRaw);
                bool isNullable = typename.StartsWith("System.Nullable`1[");
                var basename = isNullable ? typename.Substring(18) : typename;

                bool isElse = false;
                object obj = null;
                switch (basename)
                {
                    case "System.Boolean":
                        if (valueStr == "1") obj = true;
                        else if (valueStr == "0") obj = false;
                        break;

                    case "System.Byte":
                        if (byte.TryParse(valueStr, out var trybyte)) obj = trybyte;
                        break;

                    case "System.Char":
                        if (valueStr.Length > 0) obj = valueStr[0];
                        break;

                    case "System.Decimal":
                        if (Decimal.TryParse(valueStr, out var trydec)) obj = trydec;
                        break;

                    case "System.Double":
                        if (Double.TryParse(valueStr, out var trydb)) obj = trydb;
                        break;

                    case "System.Single":
                        if (Single.TryParse(valueStr, out var trysg)) obj = trysg;
                        break;

                    case "System.Int32":
                        if (Int32.TryParse(valueStr, out var tryint32)) obj = tryint32;
                        break;

                    case "System.Int64":
                        if (Int64.TryParse(valueStr, out var tryint64)) obj = tryint64;
                        break;

                    case "System.SByte":
                        if (SByte.TryParse(valueStr, out var trysb)) obj = trysb;
                        break;

                    case "System.Int16":
                        if (Int16.TryParse(valueStr, out var tryint16)) obj = tryint16;
                        break;

                    case "System.UInt32":
                        if (UInt32.TryParse(valueStr, out var tryuint32)) obj = tryuint32;
                        break;

                    case "System.UInt64":
                        if (UInt64.TryParse(valueStr, out var tryuint64)) obj = tryuint64;
                        break;

                    case "System.UInt16":
                        if (UInt16.TryParse(valueStr, out var tryuint16)) obj = tryuint16;
                        break;

                    case "System.DateTime":
                        if (DateTime.TryParse(valueStr, out var trydt)) obj = trydt;
                        break;

                    case "System.DateTimeOffset":
                        if (DateTimeOffset.TryParse(valueStr, out var trydtos)) obj = trydtos;
                        break;

                    case "System.TimeSpan":
                        if (Int64.TryParse(valueStr, out tryint64)) obj = new TimeSpan(tryint64);
                        break;

                    case "System.Guid":
                        if (Guid.TryParse(valueStr, out var tryguid)) obj = tryguid;
                        break;

                    default:
                        isElse = true;
                        break;
                }

                if (isElse == false)
                {
                    if (obj == null) return default(T);
                    return (T)obj;
                }
            }
            if (DeserializeRaw != null) return (T)DeserializeRaw(valueRaw, typeof(T));

            if (valueStr == null) valueStr = encoding.GetString(valueRaw);
            if (Deserialize != null) return (T)Deserialize(valueStr, typeof(T));
            return valueStr.ConvertTo<T>();
        }

        #endregion 序列化写入，反序列化
    }
}
