using System;
using System.Text;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
using System.Dynamic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Collections;

namespace EasyDevCore.Common
{   
    /// <summary>
    /// Helper for convert string to/from type
    /// </summary>
    public static class ConvertHelper
    {
        /// <summary>
        /// Hexes to byte.
        /// </summary>
        /// <param name="hexString">The hex string.</param>
        /// <returns></returns>
        public static byte[] HexToByte(this string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        /// <summary>
        /// Changes the type.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="conversionType">Type of the conversion.</param>
        /// <returns></returns>
        public static object ChangeType(this object value, Type conversionType)
        {
            if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if (value == null || value == DBNull.Value)
                {
                    return null;
                }
                else
                {
                    NullableConverter nullableConverter = new NullableConverter(conversionType);
                    conversionType = nullableConverter.UnderlyingType;
                }
            }
            if (typeof(Enum).IsAssignableFrom(conversionType))
            {
                return Enum.GetName(value.GetType(), value);
            }
            return Convert.ChangeType(value, conversionType);
        }

        /// <summary>
        /// To the dynamic.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static dynamic ToDynamic(this object value)
        {
            return TypeDescriptor.GetProperties(value.GetType()).OfType<PropertyDescriptor>().Aggregate(new ExpandoObject() as IDictionary<string, object>,
                                        (a, p) => { a.Add(p.Name, p.GetValue(value)); return a; });
        }

        /// <summary>
        /// Extension method that turns a dictionary of string and object to an ExpandoObject
        /// </summary>
        public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary)
        {
            var expando = new ExpandoObject();
            var expandoDic = expando as IDictionary<string, object>;

            // go through the items in the dictionary and copy over the key value pairs)
            foreach (var kvp in dictionary)
            {
                // if the value can also be turned into an ExpandoObject, then do it!
                if (kvp.Value is IDictionary<string, object>)
                {
                    var expandoValue = ((IDictionary<string, object>)kvp.Value).ToExpando();
                    expandoDic.Add(kvp.Key, expandoValue);
                }
                else if (kvp.Value is ICollection)
                {
                    // iterate through the collection and convert any strin-object dictionaries
                    // along the way into expando objects
                    var itemList = new List<object>();
                    foreach (var item in (ICollection)kvp.Value)
                    {
                        if (item is IDictionary<string, object>)
                        {
                            var expandoItem = ((IDictionary<string, object>)item).ToExpando();
                            itemList.Add(expandoItem);
                        }
                        else
                        {
                            itemList.Add(item);
                        }
                    }

                    expandoDic.Add(kvp.Key, itemList);
                }
                else
                {
                    expandoDic.Add(kvp);
                }
            }

            return expando;
        }

        /// <summary>
        /// Changes the type.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="conversionType">Type of the conversion.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static object ChangeType(this object value, Type conversionType, object defaultValue)
        {
            try
            {
                return ChangeType(value, conversionType);
            }
            catch
            {
                return defaultValue;

            }
        }

        /// <summary>
        /// Converts the specified value.
        /// </summary>
        /// <typeparam name="T">Type of the result value</typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static T ConvertTo<T>(this object value)
        {
            if((value != null) && (value.GetType() == typeof(string)))
            {
                return StringTo<T>((string)value);
            }
            if (value == null) return default(T);
            return (T)ChangeType(value, typeof(T));
        }

        /// <summary>
        /// Converts the specified value.
        /// </summary>
        /// <typeparam name="T">Type of the result value</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value if convert failed.</param>
        /// <returns></returns>
        public static T ConvertTo<T>(this object value, T defaultValue)
        {            
            try
            {
                return ConvertTo<T>(value);
            }
            catch
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Converts byte array to binary string
        /// </summary>
        /// <param name="array">The array.</param>
        /// <returns></returns>
        public static string ToString(this byte[] array)
        {
            char[] chars = new char[array.Length / sizeof(char)];
            System.Buffer.BlockCopy(array, 0, chars, 0, array.Length);
            return new string(chars);

        }

        /// <summary>
        /// Converts char array to binary string
        /// </summary>
        /// <param name="array">The array.</param>
        /// <returns></returns>
        public static string ToString(this char[] array)
        {
            return new string(array);
        }

        /// <summary>
        /// Converts binary string to byte array.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static byte[] ToByteArray(this string value)
        {
            byte[] bytes = new byte[value.Length * sizeof(char)];
            System.Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }        

        /// <summary>
        /// Strings to base24.
        /// </summary> 
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string ToBase24(this string value)
        {
            const string sel = "BCDFGHJKMPQRTVWXY2346789";
            int i = 0;
            int Pos = 0;
            char[] Buf = new char[value.Length << 1];

            while ((i = Pos) < value.Length)
            {
                Buf[i << 1] = sel[(value[Pos]) >> 4];
                Buf[(i << 1) + 1] = sel[23 - (value[Pos] & 0x0F)];
                Pos++;
            }

            return new string(Buf);
        }

        /// <summary>
        /// Strings from base24.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string FromBase24(this string value)
        {
            const string sel = "BCDFGHJKMPQRTVWXY2346789";
            if (value.Length % 2 != 0)
                return null;

            int[] NPos = new int[2];
            char[] N = new char[2];
            char[] Buf = new char[value.Length >> 1];

            for (int i = 0; i < (value.Length >> 1); i++)
            {
                NPos[0] = sel.IndexOf(value[i << 1]);
                NPos[1] = 23 - sel.IndexOf(value[(i << 1) + 1]);
                if (NPos[0] < 0 || NPos[1] < 0)
                {
                    return null;
                }

                Buf[i] = ((char)((NPos[0] << 4) | NPos[1]));
            }
            return new string(Buf);
        }

        /// <summary>
        /// Converts to base64 (UTF8).
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string ToBase64(this string value)
        {
            return ToBase64(value, Encoding.UTF8);
        }

        /// <summary>
        /// String to base64 string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        public static string ToBase64(this string value, Encoding encoding)
        {
            return Convert.ToBase64String(encoding.GetBytes(value));
        }

        /// <summary>
        /// String from base64 string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string FromBase64(this string value)
        {
            return Encoding.UTF8.GetString(Convert.FromBase64String(value));
        }

        /// <summary>
        /// Convert strings to type T
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="type">The type.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static object StringTo(string value, Type type, object defaultValue)
        {
            if (value != null)
            {
                try
                {
                    return StringTo(value, type);
                }
                catch
                {
                }
            }
            return defaultValue;
        }

        /// <summary>
        /// Convert strings to type T
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static object StringTo(this string value, Type type)
        {
            if (type == typeof(Guid))
            {
                if (string.IsNullOrWhiteSpace(value)) return Guid.Empty;
                System.ComponentModel.GuidConverter guidConverter = new System.ComponentModel.GuidConverter();
                return guidConverter.ConvertFromString(value);
            }
            else if (type == typeof(byte[]))
            {
                return Convert.FromBase64String(value);
            }
            else if (type == typeof(char[]))
            {
                return Encoding.GetEncoding(850).GetChars((Convert.FromBase64String(value)));
            }
            else if (typeof(Enum).IsAssignableFrom(type))
            {
                return Enum.Parse(type, value);
            }
            else
            {
                return Convert.ChangeType(value, type);
            }
        }

        /// <summary>
        /// Convert strings to type T
        /// </summary>
        /// <typeparam name="T">The type string convert to.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T StringTo<T>(this string value, T defaultValue)
        {
            return (T)StringTo(value, typeof(T), defaultValue);
        }

        /// <summary>
        /// Convert strings to type T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static T StringTo<T>(this string value)
        {
            return (T)StringTo(value, typeof(T), default(T));
        }

        /// <summary>
        /// Convert the type to string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string StringFrom(this object value, string defaultValue)
        {
            try
            {
                return StringFrom(value);
            }
            catch
            {
            }
            return defaultValue;
        }

        /// <summary>
        /// Convert the type to string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string StringFrom(this object value)
        {
            if ((value == null) || (value == DBNull.Value)) return null;
            if (value.GetType() == typeof(Guid))
            {
                System.ComponentModel.GuidConverter guidConverter = new System.ComponentModel.GuidConverter();
                return guidConverter.ConvertToString(value);

            }
            if (value.GetType() == typeof(DateTime))
            {
                return ((DateTime)value).ToString("o");
            }
            if (value.GetType() == typeof(byte[]))
            {
                return Convert.ToBase64String((byte[])value);
            }
            if (value.GetType() == typeof(char[]))
            {
                return Convert.ToBase64String(Encoding.GetEncoding(850).GetBytes((char[])value));
            }
            if (typeof(Enum).IsAssignableFrom(value.GetType()))
            {
                return Enum.GetName(value.GetType(), value);
            }
            return (string)Convert.ChangeType(value, typeof(string));
        }
    }   
}
