using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Text.Json;
using System.ComponentModel;
using System.Reflection;

namespace EasyDevCore.Common
{
    /// <summary>
    /// 
    /// </summary>
    public static class CopyHelper
    {
        /// <summary>
        /// Clones the hashtable.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        public static Hashtable CloneHashtable(this Hashtable input)
        {
            Hashtable ret = new Hashtable();
            foreach (DictionaryEntry dictionaryEntry in input)
            {
                if (dictionaryEntry.Value is string)
                {
                    ret.Add(dictionaryEntry.Key, dictionaryEntry.Value != null ? (string)dictionaryEntry.Value : null);
                }
                else if (dictionaryEntry.Value is Hashtable)
                {
                    ret.Add(dictionaryEntry.Key, CloneHashtable((Hashtable)dictionaryEntry.Value));
                }
                else if (dictionaryEntry.Value is ArrayList)
                {
                    ret.Add(dictionaryEntry.Key, new ArrayList((ArrayList)dictionaryEntry.Value));
                }
            }

            return ret;
        }

        /// <summary>
        /// Clones the specified source.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <returns>T.</returns>
        /// <exception cref="ArgumentException">The type must be serializable.;source</exception>
        public static T CloneObject<T>(this T source)
        {
#pragma warning disable SYSLIB0050 // Type or member is obsolete
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }
#pragma warning restore SYSLIB0050 // Type or member is obsolete

            // Don't serialize a null object, simply return the default for that object
            if (source == null)
            {
                return default(T);
            }
            var json = JsonSerializer.Serialize(source, source.GetType());
            return string.IsNullOrWhiteSpace(json) ? default(T) : (T)JsonSerializer.Deserialize(json, source.GetType());
        }

        /// <summary>
        /// Simples the map.
        /// </summary>
        /// <typeparam name="TSource">The type of the source.</typeparam>
        /// <typeparam name="TDest">The type of the dest.</typeparam>
        /// <param name="source">The source.</param>
        /// <param name="destination">The destination.</param>
        /// <returns></returns>
        public static TDest SimpleMap<TSource, TDest>(this TSource source, TDest destination = null)
        where TSource : class
        where TDest : class
        {
            Type destType = typeof(TDest);
            if (destination == null)
            {
                destination = (TDest)Activator.CreateInstance(destType);
            }
            var sourceProperties = source.GetType().GetProperties().AsSpan();
            var destinationProperties = destType.GetProperties().AsSpan();

            for (int si = 0; si < sourceProperties.Length; si++)
            {
                PropertyInfo sourceProperty = sourceProperties[si];
                for (int di = 0; di < destinationProperties.Length; di++)
                {
                    PropertyInfo destinationProperty = destinationProperties[si];
                    if (sourceProperty.Name.Equals(destinationProperty.Name, StringComparison.InvariantCultureIgnoreCase) && destinationProperty.CanWrite)
                    {
                        var sourceValue = sourceProperty.GetValue(source);
                        destinationProperty.SetValue(destination, sourceProperty.PropertyType == destinationProperty.PropertyType ? sourceValue : sourceValue.ChangeType(destType));
                        break;
                    }
                }
            }
            return destination;
        }

        /// <summary>
        /// Copies the specified source.
        /// </summary>
        /// <typeparam name="TSource">The type of the source.</typeparam>
        /// <typeparam name="TDest">The type of the dest.</typeparam>
        /// <param name="dest">The dest.</param>
        /// <param name="source">The source.</param>
        /// <param name="caseSensitive">if set to <c>true</c> [case sensitive].</param>
        /// <param name="predicate">The predicate.</param>
        /// <param name="setter">The setter.</param>
        /// <returns></returns>
        public static TDest Copy<TSource, TDest>(this TDest dest, TSource source, bool caseSensitive = false, Func<string, object, bool> predicate = null, Func<string, object, object> setter = null)
            where TSource : class
            where TDest : class
        {
            if (source.IsDictionary() && source.HasGenerictArgumentType<string>(0))
            {
                var props = TypeDescriptor.GetProperties(dest);
                var machingNames = from prop in props.OfType<PropertyDescriptor>()
                                   from val in (IDictionary<string, object>)source
                                   where prop.Name.Equals(val.Key, (caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase))
                                        && (predicate == null || predicate(prop.Name, val.Value))
                                   select new { Property = prop, Value = val.Value };
                machingNames.ForEach(m => ReflectionHelper.SetPropertyValue(dest, m.Property, (setter == null ? m.Value : setter(m.Property.Name, m.Value))));
            }
            else
            {
                var sourceProps = TypeDescriptor.GetProperties(source);
                var destProps = TypeDescriptor.GetProperties(dest);
                var machingProps = from s in sourceProps.OfType<PropertyDescriptor>()
                                   from d in destProps.OfType<PropertyDescriptor>()
                                   where s.Name.Equals(d.Name, (caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase))
                                        && (predicate == null || predicate(d.Name, s.GetValue(source)))
                                   select new { SourceProp = s, DestProp = d };
                machingProps.ForEach(m =>
                {
                    var value = m.SourceProp.GetValue(source);
                    ReflectionHelper.SetPropertyValue(dest, m.DestProp, (setter == null ? value : setter(m.DestProp.Name, value)));
                });
            }
            return dest;
        }

    }
}
