using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace JWH
{

    public static class ExtensionReflection
    {

        public static bool IsDesignMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime;


        #region [ PropertiesCopy ] --------------------------------------------

        /// <summary>
        /// 현재객체의 속성값을 대상객체의 속성에 복사합니다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="dest">대상객체</param>
        public static void PropertiesCopy(this object sender, object dest, bool overwrite = true)
        {
            try
            {
                foreach (PropertyInfo property in sender.GetType().GetProperties())
                {
                    try
                    {
                        PropertyInfo destProp = dest.GetType().GetProperty(property.Name, property.PropertyType);
                        if (destProp == null || property.GetType() != destProp.GetType()) continue;
                        //if (!property.CanWrite) continue;
                        if (!destProp.CanWrite) continue;

                        var destValue = destProp.GetValue(dest);
                        if (overwrite == false && destValue != null) continue;

                        destProp.SetValue(dest, property.GetValue(sender));
                    }
                    catch (Exception ex)
                    {
                        XLogger.Instance.Fatal(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// 현재객체의 속성값을 대상객체의 속성에 복사합니다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="dest">대상객체</param>
        public static void PropertiesCopy(this DataTable sender, object dest)
        {
            try
            {
                if (sender.Rows.Count < 1) return;

                DataRow row = sender.Rows[0];
                foreach (DataColumn column in sender.Columns)
                {
                    try
                    {
                        PropertyInfo destProp = dest.GetType().GetProperty(column.ColumnName, column.DataType);
                        if (destProp == null) continue;

                        try
                        {
                            var obj = row[column.ColumnName];
                            destProp.SetValue(dest, obj);
                        }
                        catch
                        {
                            // Object of type 'System.DBNull' cannot be converted to type 'System.String'.
                            // XLogger.Instance.Warn(ex);
                        }
                    }
                    catch (Exception ex)
                    {
                        XLogger.Instance.Fatal(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// Dictionary<string, object>의 값을 대상객체의 속성에 복사합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="dest"></param>
        public static void PropertiesCopy(this Dictionary<string, object> sender, object dest)
        {
            try
            {
                foreach (KeyValuePair<string, object> item in sender)
                {
                    try
                    {
                        PropertyInfo destProp = dest.GetType().GetProperty(item.Key);
                        if (destProp == null) continue;
                        if (!destProp.CanWrite) continue;

                        var obj = Convert.ChangeType(item.Value, destProp.PropertyType);
                        destProp.SetValue(dest, obj);
                    }
                    catch (Exception ex)
                    {
                        XLogger.Instance.Fatal(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        #endregion

        /// <summary>
        /// value 값을 현재객체의 속성에 복사합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void PropertySet(this object sender, string name, object value)
        {
            try
            {
                PropertyInfo property = sender.GetType().GetProperty(name);
                if (property == null)
                    foreach (PropertyInfo item in sender.GetType().GetProperties())
                        if (string.Compare(item.Name, name, true) == 0)
                        {
                            property = item;
                            break;
                        }
                if (property == null) return;
                if (property.PropertyType != value.GetType())
                {
                    try
                    { value = Convert.ChangeType(value, property.PropertyType); }
                    catch
                    { return; }
                }

                property.SetValue(sender, value);
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// 객체의 속성값을 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string PropertyGet(this object sender, string name)
        {
            try
            {
                PropertyInfo property = sender.GetType().GetProperty(name);
                if (property == null)
                {
                    foreach (PropertyInfo item in sender.GetType().GetProperties())
                    {
                        if (string.Compare(item.Name, name, true) == 0)
                        {
                            property = item;
                            break;
                        }
                    }
                }
                if (property == null) return string.Empty;
                return property.GetValue(sender)?.ToString();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// 객체의 메소드를 호출합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="name"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static object CallMethod(this object sender, string name, object[] parameters)
        {
            MethodInfo method = sender.GetType().GetMethod(name);
            if (method == null) return false;

            return method.Invoke(sender, parameters);
        }

        /// <summary>
        /// 객체의 속성 정보를 반환합니다.
        /// </summary>
        /// <param name="sender"></param>
        /// <returns></returns>
        public static string GetPropertiesString(this object sender)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"Type is {sender.GetType()}");
                foreach (PropertyInfo property in sender.GetType().GetProperties())
                {
                    try
                    {
                        sb.AppendLine($"  > {property.Name} = {property.GetValue(sender)}");
                    }
                    catch
                    {
                    }
                }

                return sb.ToString();
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// 객체를 복제합니다.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender"></param>
        /// <returns></returns>
        public static T Clone<T>(this T sender) where T : class, new()
        {
            T obj = new T();
            try
            {
                sender.PropertiesCopy(obj);
                return obj;
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                throw ex;
            }
        }

        #region [ GetStringValue(Enum) ] --------------------------------------

        /// <summary>
        /// Will get the string value for a given enums value, this will
        /// only work if you assign the StringValue attribute to
        /// the items in your enum.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetStringValue(this Enum value)
        {
            // Get the type
            Type type = value.GetType();

            // Get fieldinfo for this type
            FieldInfo fieldInfo = type.GetField(value.ToString());

            // Get the stringvalue attributes
            StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
                typeof(StringValueAttribute), false) as StringValueAttribute[];

            // Return the first if there was a match.
            return attribs.Length > 0 ? attribs[0].StringValue : null;
        }

        /// <summary>
        /// This attribute is used to represent a string value
        /// for a value in an enum.
        /// </summary>
        public class StringValueAttribute : Attribute
        {

            /// <summary>
            /// Holds the stringvalue for a value in an enum.
            /// </summary>
            public string StringValue { get; protected set; }

            /// <summary>
            /// Constructor used to init a StringValue Attribute
            /// </summary>
            /// <param name="value"></param>
            public StringValueAttribute(string value)
            {
                this.StringValue = value;
            }

        }

        #endregion

        /// <summary>
        /// GetDescription
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="e"></param>
        /// <returns></returns>
        public static string GetDescription<T>(this T e) where T : IConvertible
        {
            if (e is Enum)
            {
                Type type = e.GetType();
                Array values = Enum.GetValues(type);

                foreach (int val in values)
                {
                    if (val == e.ToInt32(CultureInfo.InvariantCulture))
                    {
                        var memInfo = type.GetMember(type.GetEnumName(val));
                        var descriptionAttribute = memInfo[0]
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault() as DescriptionAttribute;

                        if (descriptionAttribute != null)
                        {
                            return descriptionAttribute.Description;
                        }
                    }
                }
            }

            return null;
        }

        #region [ ToClass() ] -------------------------------------------------

        /// <summary>
        /// Converts a DataTable to a list with generic objects
        /// dataTable.ToClass<Employee>();
        /// </summary>
        /// <typeparam name="T">Generic object</typeparam>
        /// <param name="dataTable">DataTable</param>
        /// <returns>List with generic objects</returns>
        public static T[] ToClass<T>(this DataTable dataTable) where T : class, new()
        {
            try
            {
                List<T> list = new List<T>();
                foreach (var row in dataTable.AsEnumerable())
                {
                    T obj = new T();

                    foreach (PropertyInfo property in obj.GetType().GetProperties())
                    {
                        try
                        {
                            if (dataTable.Columns.Contains(property.Name) == false) continue;
                            if (row[property.Name] is System.DBNull) continue;

                            var val = row[property.Name];
                            //if (property.PropertyType == typeof(DateTime) && val.GetType() == typeof(string))
                            //    DateTime.TryParse(val.ToString(), out val);

                            property.SetValue(obj, Convert.ChangeType(val, property.PropertyType, CultureInfo.CurrentCulture), null);
                        }
                        catch
                        {
                            continue;
                        }
                    }

                    list.Add(obj);
                }

                return list.ToArray();
            }
            catch { throw; }
        }

        /// <summary>
        /// Converts a object to a list with generic objects
        /// dataTable.ToClass<Employee>();
        /// </summary>
        /// <typeparam name="T">Generic object</typeparam>
        /// <param name="src">DataTable</param>
        /// <returns>List with generic objects</returns>
        public static T ToClass<T>(this object src) where T : class, new()
        {
            try
            {
                T des = new T();
                Type desType = des.GetType();
                Type srcType = src.GetType();
                foreach (PropertyInfo srcProperty in srcType.GetProperties())
                {
                    try
                    {
                        PropertyInfo desProperty = desType.GetProperty(srcProperty.Name);
                        if (desProperty == null) continue;

                        var srcValue = srcProperty.GetValue(src);
                        if (srcProperty.PropertyType != desProperty.PropertyType)
                        {
                            srcValue = Convert.ChangeType(srcValue, desProperty.PropertyType);
                        }

                        desProperty.SetValue(des, srcValue);
                    }
                    catch (Exception ex)
                    {
                        XLogger.Instance.Warn(ex);
                    }
                }

                return des;
            }
            catch { throw; }
        }

        /// <summary>
        /// Converts a object to a list with generic objects
        /// dataTable.ToClass<Employee>();
        /// </summary>
        /// <typeparam name="T">Generic object</typeparam>
        /// <param name="srcArray">DataTable</param>
        /// <returns>List with generic objects</returns>
        public static T[] ToClass<T>(this object[] srcArray) where T : class, new()
        {
            try
            {
                List<T> list = new List<T>();

                foreach (object src in srcArray)
                {
                    T des = new T();
                    list.Add(des);
                    Type desType = des.GetType();

                    Type srcType = src.GetType();
                    foreach (PropertyInfo srcProperty in srcType.GetProperties())
                    {
                        try
                        {
                            PropertyInfo desProperty = desType.GetProperty(srcProperty.Name);
                            if (desProperty == null) continue;

                            var srcValue = srcProperty.GetValue(src);
                            if (srcProperty.PropertyType != desProperty.PropertyType)
                            {
                                srcValue = Convert.ChangeType(srcValue, desProperty.PropertyType);
                            }

                            desProperty.SetValue(des, srcValue);
                        }
                        catch (Exception ex)
                        {
                            XLogger.Instance.Warn(ex);
                        }
                    }

                }

                return list.ToArray();
            }
            catch { throw; }
        }

        #endregion

        public static CultureInfo CultureInfo { get; set; }

        public static string ToTitleCase(this string sender)
        {
            if (ExtensionReflection.CultureInfo == null)
                ExtensionReflection.CultureInfo = CultureInfo.CurrentCulture;

            TextInfo textInfo = CultureInfo.TextInfo;

            return textInfo.ToTitleCase(sender.ToLower());
        }

        public static Bitmap ControlCapture(this Control control, bool clipboard = true)
        {
            if (control == null) throw new ArgumentNullException("control");

            Bitmap bitmap = new Bitmap(control.Width, control.Height);
            control.DrawToBitmap(bitmap, new Rectangle(new Point(0, 0), control.Size));
            if (clipboard) Clipboard.SetImage(bitmap);

            return bitmap;
        }

        public static Bitmap ControlCapture(this Control control, Rectangle rect, bool clipboard = true)
        {
            if (control == null) throw new ArgumentNullException("control");

            Bitmap bitmap = new Bitmap(rect.Width, rect.Height);
            control.DrawToBitmap(bitmap, rect);
            if (clipboard) Clipboard.SetImage(bitmap);

            return bitmap;
        }

        /// <summary>
        /// 문자열을 열거형으로 반환한다
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender"></param>
        /// <returns></returns>
        public static T ToEnum<T>(this string sender, T value)
        {
            try
            {
                return (T)Enum.Parse(typeof(T), sender, true);
            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
                return value;
            }
        }

    }

}
