using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Kit.Helpers
{
    public static class SystemCollectionExtensionMethods
    {
        public static IList<TItem> NullToEmpty<TItem>(this IList<TItem> list)
        {
            return list ?? new List<TItem>();
        }

        public static IEnumerable<TItem> NullToEmpty<TItem>(this IEnumerable<TItem> list)
        {
            return list ?? new List<TItem>();
        }

        public static IList<TItem> AddRange<TItem>(this IList<TItem> list, IEnumerable<TItem> items)
        {
	        if (items == null) return list;
			foreach (TItem item in items) list.Add(item);
            return list;
        }

        public static IList<TItem> AddRangeEx<TItem>(this IList<TItem> list, IEnumerable<TItem> items)
        {
			if (items == null) return list;
			foreach (TItem item in items) list.Add(item);
            return list;
        }

        public static IList<TItem> AddEx<TItem>(this IList<TItem> list, TItem item)
        {
            list.Add(item);
            return list;
        }

        public static IEnumerable<TItem> AddToNewList<TItem>(this IEnumerable<TItem> list, TItem item)
        {
            return list.ToList().AddEx(item);
        }

        public static IList<TItem> AddToNewList<TItem>(this IList<TItem> list, TItem item)
        {
            return list.AddEx(item);
        }

        public static IDictionary<TKey, TValue> AddRange<TKey, TValue>(this IDictionary<TKey, TValue> target, IDictionary<TKey, TValue> source)
        {
            foreach (var item in source) target.SetByKey(item.Key, item.Value);
            return target;
        }
        
        public static bool IsNullOrEmpty<TItem>([NotNullWhen(false)] this IEnumerable<TItem>? collection)
        {
            return collection == null || collection.Any() == false;
        }

        public static TValue GetByKey<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
        {
            return dictionary != null && dictionary.ContainsKey(key)
                   ? dictionary[key]
                   : defaultValue;
        }

        public static TValue GetByKey<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
        {
            return dictionary.GetByKey(key, default(TValue));
        }

        public static TValue SetByKey<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
        {
            if (dictionary == null) throw new ArgumentNullException("dictionary");
            lock (dictionary)
            {
                if (!dictionary.ContainsKey(key)) dictionary.Add(key, value);
                else dictionary[key] = value;
            }
            return value;
        }

        public static object CastToTypedEnumerable(this IEnumerable<object> collection, Type targetType)
        {
            var resultListType = typeof(List<>).MakeGenericType(targetType);
            var resultList = (IList)Activator.CreateInstance(resultListType);
            foreach (var collectionItem in collection) resultList.Add(collectionItem);
            return resultList;
        }

        public static Dictionary<string, string> ParseToDictionaryString(this NameValueCollection collection)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (string item in collection.Keys)
            {
                result.Add(item, collection[item]);
            }
            return result;
        }

        public static string ParseToString(this NameValueCollection collection, char parameterSeparator, char nameValueSeparator)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string item in collection.Keys)
            {
                sb.AppendFormat("{0}{1}{2}{3}",item,nameValueSeparator,collection[item],parameterSeparator);
            }
            return sb.ToString();
        }

        public static void Add<TKey, TValue>(this NameValueCollection collection, IEnumerable<KeyValuePair<TKey, TValue>> items)
        {
            foreach (KeyValuePair<TKey, TValue> kv in items)
            {
                if (Equals(kv.Key, default(TKey))) continue;
                string value = Equals(kv.Value, default(TValue)) ? string.Empty : kv.Value.ToString();
                collection.Add(kv.Key.ToString(), value);
            }
        }

        public static NameValueCollection ToNameValueCollection(this IEnumerable<KeyValuePair<string, string>> kvpCollection)
        {
            NameValueCollection collection = new NameValueCollection();
            collection.Add(kvpCollection);
            return collection;
        }
        public static string Join(this string[] array, string separator)
        {
            return string.Join(separator, array);
        }
        public static string Join(this string[] array)
        {
            return array.Join(",");
        }
        public static string Join<TObject>(this IEnumerable<TObject> collection, string separator)
        {
            if (collection == null) return null;
            IEnumerable<string> stringCollection = collection.Select(x => x.ToString());
                
                
                //(from o in collection
                //                                   where Equals(o, default(TObject))
                //                                   select o.ToString());
            return stringCollection.ToArray().Join(separator);
        }
        public static string Join<TObject>(this IEnumerable<TObject> collection)
        {
            return collection.Join(",");
        }
        public static IEnumerable<KeyValuePair<string, string>> ToKeyValuePairs(this NameValueCollection nvc)
        {
            List<KeyValuePair<string, string>> kvc = new List<KeyValuePair<string,string>>();
            if (nvc == null) return kvc;
            foreach(string key in nvc.Keys) kvc.Add(new KeyValuePair<string,string>(key, nvc[key]));
            return kvc;
        }
        public static IEnumerable<TObject> Random<TObject>(this IEnumerable<TObject> collection, int limit = -1)
        {
            if (collection == null) return null;
            var random = new Random();
            collection = collection.OrderBy(o => random.Next());
            if (limit > -1) collection = collection.Take(limit);
            return collection;
        }

        public static IList<T> ForEach<T>(this IList<T> collection, Action<T> action)
        {
            if (collection == null) return null;
            if (action == null) return collection;

	        foreach (T element in collection) action(element);
            return collection;
        }

        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
        {
            if (collection == null) return null;
            if (action == null) return collection;
	        IEnumerable<T> forEach = collection as IList<T> ?? collection.ToList();
	        foreach (T element in forEach) action(element);
            return forEach;
        }

        public static T[] ForEach<T>(this T[] collection, Action<T> action)
        {
            return ((IEnumerable<T>)collection).ForEach(action).ToArray();
        }

        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T, int> action)
        {
            if (collection == null) return null;
            if (action == null) return collection;
	        IEnumerable<T> forEach = collection as IList<T> ?? collection.ToList();

            int indexEnd = forEach.Count();
            for (int index = 0; index < indexEnd; index++)
            {
                T element = forEach.ElementAt(index);
                action(element, index);
            }
            return forEach;
        }

        public static IEnumerable<TItem> InsertFirstToNewList<TItem>(this IEnumerable<TItem> list, TItem item)
        {
            var lists = list.ToList();
            lists.Insert(0, item);
            return lists;
        }

        public static IEnumerable<TItem> InsertEx<TItem>(this IList<TItem> list, int index, TItem item)
        {
            list.Insert(index, item);
            return list;
        }


        public static List<int> SearchBytePattern(this byte[] Source, byte[] Pattern, int Start)
        {
            int SourceLen = Source.Length - Pattern.Length + 1;//Получаем длину исходного массива.
            int j;
            var positions = new List<int>();//Переменная для хранения списка результатов поиска.

            for (int i = Start; i < SourceLen; i++)
            {
                if (Source[i] != Pattern[0]) continue;//Сравниваем первый искомый байт и если он совпадает, то проверяем остальные байты за ним идущие, иначе идем дальше по массиву.
                for (j = Pattern.Length - 1; j >= 1 && Source[i + j] == Pattern[j]; j--) ;//Сравниваем остальные байты с нашим значением.
                if (j == 0)//Переменная будет равна нулю, если все байты совпали
                {
                    positions.Add(i);//Добавляем адрес конца совпадения первого байта в список
                    i += Pattern.Length - 1;//Увеличиваем значение переменной на величину искомого, так как мы его уже проверили и это ускоряет поиск.
                }
            }
            return positions;//Отдаем список адресов, которые совпадают с искомым значением.
        }

        public static IEnumerable<IEnumerable<TItem>> GetPages<TItem>(this IEnumerable<TItem> list, int pageSize)
        {
            int pageCount = (int)Math.Ceiling((list.Count() * 1.0) / (pageSize * 1.0));

            var lists = new List<List<TItem>>();

            for (int pageNum = 0; pageNum < pageCount; pageNum++)
            {
                lists.Add(list.Skip(pageNum * pageSize).Take(pageSize).ToList());
            }

            return lists;
        }

        public static IEnumerable<TItem> GetPage<TItem>(this IEnumerable<TItem> list, int page, int pageSize)
        {
            return list.Skip(page * pageSize).Take(pageSize);
        }
        
        public static void AddDistinctValue<K, TValue>(this IDictionary<K, IEnumerable<TValue>> dictionary, K key, params TValue[] newValue)
        {
            AddDistinctValue(dictionary, key, comparer: null, newValue: newValue);
        }
        public static void AddDistinctValue<K, TValue>(this IDictionary<K, IEnumerable<TValue>> dictionary, K key, IEqualityComparer<TValue> comparer, params TValue[] newValue)
        {
            if (dictionary.ContainsKey(key))
            {
                List<TValue> values = dictionary[key].ToList();

                if (values.Any(x => x.Equals(newValue)) == false)
                {
                    IEnumerable<TValue> newValues = comparer == null ? values.Union(newValue).ToList() : values.Union(newValue, comparer).ToList();
                    dictionary[key] = newValues;
                }
            }
            else
            {
                dictionary.Add(key, newValue.ToList());
            }
        }

        public static void AddListValue<K, TValue>(this IDictionary<K, IEnumerable<TValue>> dictionary, K key, params TValue[] newValue)
        {
            if (dictionary.ContainsKey(key))
            {
                List<TValue> values = dictionary[key].ToList();
                values.AddRange(newValue);
                dictionary[key] = values;
            }
            else
            {
                dictionary.Add(key, newValue.ToList());
            }
        }

        public static bool SetEquals<TItem>(this IEnumerable<TItem> firstCollection, IEnumerable<TItem> secondCollection)
        {
            if (firstCollection == null && secondCollection == null) return true;
            if (firstCollection == null || secondCollection == null) return false;
            return new HashSet<TItem>(firstCollection).SetEquals(new HashSet<TItem>(secondCollection));
        }
    }
}
