using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Newtonsoft.Json;

namespace VCommon.VAutoMapper.Internals
{
    internal static class JsonConvertHolder
    {
        private static readonly ConcurrentDictionary<Type, JsonConverter> ConverterInstances = new ConcurrentDictionary<Type, JsonConverter>();
        private static readonly ConcurrentDictionary<Tuple<PropertyInfo, PropertyInfo>, Expression<Func<string, Type, object>>> CachedDeserializationExpression = new ConcurrentDictionary<Tuple<PropertyInfo, PropertyInfo>, Expression<Func<string, Type, object>>>();
        private static readonly ConcurrentDictionary<Tuple<PropertyInfo, PropertyInfo>, Expression<Func<object, string>>> CachedSerializationExpression = new ConcurrentDictionary<Tuple<PropertyInfo, PropertyInfo>, Expression<Func<object, string>>>();

        private static JsonConverter GetConverterInstance(Type converterType)
        {
            if (ConverterInstances.TryGetValue(converterType, out var instance)) return instance;

            instance = Activator.CreateInstance(converterType) as JsonConverter;
            ConverterInstances[converterType] = instance ?? throw new ArgumentException($"type must extend from {nameof(JsonConverter)}");
            return instance;
        }

        public static Expression<Func<string, Type, object>> GetDeserializationExpression(PropertyInfo fromProp, PropertyInfo toProp)
        {
            var pair = new Tuple<PropertyInfo, PropertyInfo>(fromProp, toProp);
            if (CachedDeserializationExpression.TryGetValue(pair, out var exp)) return exp;

            var attr = toProp.GetCustomAttribute<AutoMapJsonConvertAttribute>();
            if (0 == attr.Converters.Count)
            {
                exp = (s, t) => JsonConvert.DeserializeObject(s, t);

                CachedDeserializationExpression[pair] = exp;
            }
            else
            {
                var converters = attr.Converters.Select(GetConverterInstance).ToArray();
                var setting = new JsonSerializerSettings { Converters = converters };

                exp = CachedDeserializationExpression[pair] = (s, t) => JsonConvert.DeserializeObject(s, t, setting);
            }
            return exp;
        }

        public static Expression<Func<object, string>> GetSerializationExpression(PropertyInfo fromProp, PropertyInfo toProp)
        {
            var pair = new Tuple<PropertyInfo, PropertyInfo>(fromProp, toProp);
            if (CachedSerializationExpression.TryGetValue(pair, out var exp)) return exp;

            var attr = fromProp.GetCustomAttribute<AutoMapJsonConvertAttribute>();

            if (0 == attr.Converters.Count)
            {
                exp = CachedSerializationExpression[pair] = o => JsonConvert.SerializeObject(o);
            }
            else
            {
                var converters = attr.Converters.Select(GetConverterInstance).ToArray();
                var setting = new JsonSerializerSettings { Converters = converters };

                exp = CachedSerializationExpression[pair] = o => JsonConvert.SerializeObject(o, setting);
            }

            return exp;
        }
    }
}