using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FLocal.Core {
	
	public static class ExtensionMethods {

        public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> kvps) {

            Dictionary<TKey, TValue> res = new Dictionary<TKey,TValue>();
            foreach(KeyValuePair<TKey, TValue> kvp in kvps) {
                res[kvp.Key] = kvp.Value;
            }
            return res;
        }

        public static TDestinationDictionary
            Convert<TSourceDictionary, TDestinationDictionary, TKey, TSource, TDestination>
            (this TSourceDictionary dict, Func<TSource, TDestination> converter)
            where TSourceDictionary : IDictionary<TKey, TSource>
            where TDestinationDictionary : IDictionary<TKey, TDestination>, new()
            {

                TDestinationDictionary res = new TDestinationDictionary();
                foreach(KeyValuePair<TKey, TSource> kvp in dict) {
                    res[kvp.Key] = converter(kvp.Value);
                }
                return res;
        }

        public delegate TResult RefFunc<T, TResult>(ref T arg);

        public static RefFunc<T, T> getProperty<T>(this object obj, Func<T> creator) {
            return (ref T arg) => {
                if(arg == null) {
                    arg = creator();
                }
                return arg;
            };
        }

        public static T getProperty<T>(this object obj, ref T arg, Func<T> creator) {
            return obj.getProperty<T>(creator)(ref arg);
        }

        public static string ToPrintableString(this bool val) {
            return val ? "Enabled" : "Disabled";
        }

		public static string ToPlainString(this bool val) {
			return val ? "true" : "false";
		}

		public static string ToPrintableString<T>(this IEnumerable<T> list) {
			return string.Join(",", (from elem in list select elem.ToString()).ToArray());
		}

        public static string ToDBString(this bool val) {
            return val ? "1" : "0";
        }

        public static T Safe<T>(this T obj, T defaultValue) {
            if(obj == null) {
                return defaultValue;
            } else {
                return obj;
            }
        }

        public static HashSet<T> Clone<T>(this HashSet<T> obj) {
            return new HashSet<T>(obj);
        }

        /// <summary>
        /// Note that actions from todoArr can be executed in any order!
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="todoArr"></param>
        /// <returns></returns>
        public static T Tee<T>(this T obj, params Action<T>[] todoArr) {
            foreach(Action<T> todo in todoArr) todo(obj);
            return obj;
        }

        public static T Tee<T>(this T obj, Action todo) {
            todo();
            return obj;
        }

        private static List<TAttribute> GetAttributeArray<TAttribute>(this System.Reflection.PropertyInfo property) where TAttribute : Attribute {
            return new List<TAttribute>(from attribute in property.GetCustomAttributes(true) where /*Tie(attribute, obj => Console.WriteLine(obj.GetType().FullName))*/ attribute is TAttribute select (TAttribute)attribute);
        }

        public static TAttribute GetAttribute<TAttribute>(this System.Reflection.PropertyInfo property) where TAttribute : Attribute {
            return property.GetAttributeArray<TAttribute>().Single();
        }

        public static bool HasAttribute<TAttribute>(this System.Reflection.PropertyInfo property) where TAttribute : Attribute {
            return property.GetAttributeArray<TAttribute>().Count > 0;
        }

        public static TResult Invoke<TResult>(this System.Reflection.MethodBase method, object obj, params object[] args) {
            return (TResult)method.Invoke(obj, args);
        }

        public static void Invoke(this System.Reflection.MethodBase method, object obj, params object[] args) {
            method.Invoke(obj, args);
        }

        public static Dictionary<TKey, TResult> Replace<TKey, TValue, TResult>(this IDictionary<TKey, TValue> dict, Func<TValue, TResult> Replacer) {
            return new List<KeyValuePair<TKey, TResult>>(
                from kvp in dict
                select new KeyValuePair<TKey, TResult>(kvp.Key, Replacer(kvp.Value))
            ).ToDictionary();
        }

        public static Dictionary<TKey, TValue> Replace<TKey, TValue>(this IDictionary<TKey, TValue> dict, Func<TValue, TValue> Replacer) {
            return dict.Replace<TKey, TValue, TValue>(Replacer);
        }

        public static T Single<T>(this IEnumerable<T> list, Predicate<T> checker, Func<T> onNotFound) {
            try {
                return list.Single(checker.ToFunc());
            } catch(InvalidOperationException) {
                return onNotFound();
            }
        }

        public static IEnumerable<string> Enquote(this IEnumerable<string> strings) {
            return from str in strings select str.Enquote();
        }

        public static TResult Check<TSource, TResult>(this TSource obj, Predicate<TSource> checker, Func<TSource, TResult> returnOnFail, TResult returnOnSuccess) {
            if(!checker(obj)) {
                return returnOnFail(obj);
            } else {
                return returnOnSuccess;
            }
        }

        public static TResult Check<TSource, TResult>(this TSource obj, Predicate<TSource> checker, Func<TSource, Exception> onFail, Func<TSource, TResult> returnOnSuccess) {
            if(!checker(obj)) {
                throw onFail(obj);
            } else {
                return returnOnSuccess(obj);
            }
        }
        
        public static void Check<T>(this T obj, Predicate<T> checker, Func<T, Exception> onFail) {
            if(!checker(obj)) {
                throw onFail(obj);
            }
        }

        public static bool Empty<T>(this IEnumerable<T> list) {
            return list.Count() == 0;
        }

        /// <summary>
        /// Checks arg given against the predicate given; in case of type mismatch returns false
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="arg"></param>
        /// <param name="checker"></param>
        /// <returns></returns>
        public static bool TypedLoosePredicate<T>(this object arg, Predicate<T> checker) {
            return (arg is T) && checker((T)arg);
        }

        public static Func<object, string> SafeToStringConvertor() {
            return obj => obj == null ? "" : obj.ToString();
        }

        public static T Recurse<T>(this T obj, Func<T, T> step, Predicate<T> checker) {
            //Debugger.Debug("recurse(" + typeof(T).FullName + ")");
            int i=0;
            while(checker(obj)) {
                //Debugger.Debug("Iteration " + i.ToString());
                obj = step(obj);
                i++;
            }
            //Debugger.Debug("endrecurse(" + typeof(T).Name + ")");
            return obj;
        }

        public static T[] Slice<T>(this T[] obj, int offset, int length) {
            if(offset<0) throw new IndexOutOfRangeException();
            if(offset+length > obj.Length) throw new IndexOutOfRangeException();
            T[] res = new T[length];
            Array.Copy(obj, offset, res, 0, length);
            return res;
        }

        public static T[] Slice<T>(this T[] arr, int offset) {
            return arr.Slice(offset, arr.Length-offset);
        }

        public static T TryValues<T>(this T obj, params Func<T>[] trials) {
            //Debugger.Debug("TryValues(" + typeof(T).FullName + ")");
            return new KeyValuePair<T, Func<T>[]>(obj, trials).Recurse(
                kvp => new KeyValuePair<T, Func<T>[]>(kvp.Value[0](), kvp.Value.Slice(1)),
                kvp => !kvp.Value.Empty() && kvp.Key == null
            ).Key;
        }

        public static IEnumerable<T> Replace<T>(this IEnumerable<T> list, Func<T, T> replacer) {
            return from elem in list select replacer(elem);
        }

        internal static string ToStringOnFail(this bool test, Lazy<string> onFail) {
            return test ? "" : onFail();
        }

        public static string Join(this IEnumerable<string> strings, string separator) {
            return String.Join(separator, strings.ToArray());
        }

        public static IEnumerable<T> GetSorted<T>(this IEnumerable<T> list) {
            return from elem in list orderby elem select elem;
        }

        public static Int64 ToInt64(this decimal number) {
            return decimal.ToInt64(number);
        }

		public static string ToUTCString(this DateTime date) {
			return date.ToUniversalTime().ToString("u");
		}

		public static IEnumerable<T> ToSequence<T>(this T obj, Func<T, IEnumerable<T>> nextElementsGenerator) where T : class {
			yield return obj;
			foreach(var sub in nextElementsGenerator(obj)) {
				foreach(var subsub in sub.ToSequence(nextElementsGenerator)) {
					yield return subsub;
				}
			}
		}

		public static T Last<T>(this List<T> list) {
			return list[list.Count-1];
		}

		public static IEnumerable<T> Union<T>(this IEnumerable<T> enumerable, params T[] second) {
			return enumerable.Union((IEnumerable<T>)second);
		}

    }
}
