using FastMember;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Knoks.Core
{
    public static class Extensions
    {
        public static string ToJson<T>(this T obj, Formatting formatting = Formatting.None)
        {
            return obj == null ? null : JsonConvert.SerializeObject(obj, formatting);
        }

        public static T JsonTo<T>(this string json)
        {
            return json == null ? default(T) : JsonConvert.DeserializeObject<T>(json);
        }


        private static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> distProperties = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
        private static ConcurrentDictionary<Type, Dictionary<string, Member>> srcProperties = new ConcurrentDictionary<Type, Dictionary<string, Member>>();

        public static T CopyMembersTo<T>(this object obj, T destinationObject) where T : class
        {
            var srcObj = obj;
            var dstObj = destinationObject;

            var srcType = srcObj.GetType();
            var dstType = dstObj.GetType();

            var srcAccessor = TypeAccessor.Create(srcType);
            var dstAccessor = TypeAccessor.Create(dstType);
            
            var srcMembers = srcProperties.GetOrAdd(srcType, (type) => srcAccessor.GetMembers().ToDictionary(k => k.Name));
            foreach (var dstMember in distProperties.GetOrAdd(dstType, (type) => type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite)))
            {
                if (srcMembers.TryGetValue(dstMember.Name, out Member srcMember))
                {
                    if (!dstMember.PropertyType.IsAssignableFrom(srcMember.Type))
                        continue;

                    dstAccessor[dstObj, dstMember.Name] = srcAccessor[srcObj, srcMember.Name];
                }
            }

            return dstObj;
        }

        public static T CopyMembersTo<T>(this object obj) where T : class, new()
        {
            return obj.CopyMembersTo(new T());
        }
    } 
}
