using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Kreta.Core.CustomAttributes;

namespace Kreta.Core
{
    /// <summary>
    /// Extension
    /// </summary>
    public static class Extensions
    {
        #region Convert Extensions

        /// <summary>
        /// Converts to a bool? value to int
        /// </summary>
        /// <param name="origin">The origin.</param>
        /// <returns></returns>
        public static int ToNullableInt(this bool? origin) => origin == true ? 1 : 0;

        /// <summary>
        /// Converts to a bool value to int
        /// </summary>
        /// <param name="origin">if set to <c>true</c> [origin].</param>
        /// <returns></returns>
        public static int ToNullableInt(this bool origin) => origin ? 1 : 0;

        /// <summary>
        /// Converts to a bool? value to int
        /// </summary>
        /// <param name="origin">The origin.</param>
        /// <returns></returns>
        public static int ToBit(this bool? origin) => origin.ToNullableInt();

        /// <summary>
        /// Converts to a bool value to int
        /// </summary>
        /// <param name="origin">if set to <c>true</c> [origin].</param>
        /// <returns></returns>
        public static int ToBit(this bool origin) => origin.ToNullableInt();

        /// <summary>
        /// Converts to a int? value to bool
        /// </summary>
        /// <param name="origin">The origin.</param>
        /// <returns></returns>
        public static bool ToBool(this int? origin) => origin == 1;

        /// <summary>
        /// Converts to a string value to bool
        /// </summary>
        /// <param name="origin">The origin.</param>
        /// <returns></returns>
        public static bool ToBool(this string origin) => string.Equals(origin, "T", StringComparison.OrdinalIgnoreCase) || string.Equals(origin, "TRUE", StringComparison.OrdinalIgnoreCase);

        /// <summary>
        /// Determines whether [is not null and positive] [the specified value].
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool IsNotNullAndPositive(this int? value)
        {
            return value.HasValue && 0 < value;
        }

        /// <summary>
        /// Determines whether [is not null and positive] [the specified value].
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool IsNotNullAndPositive(this int value)
        {
            return 0 < value;
        }

        /// <summary>
        /// Megvizsgálja, hogy az int? lehetséges EntityId-e (nem null és nagyobb mint 0)
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool IsEntityId(this int? value)
        {
            return value.IsNotNullAndPositive();
        }

        /// <summary>
        /// Megvizsgálja, hogy az int lehetséges EntityId-e (nagyobb mint 0)
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool IsEntityId(this int value)
        {
            return 0 < value;
        }

        public static int? ToNullableInt(this string stringValue)
        {
            if (int.TryParse(stringValue, out int intValue))
            {
                return intValue;
            }

            return null;
        }

        /// <summary>
        /// Converts to a bool? value to bool
        /// </summary>
        /// <param name="value">The origin.</param>
        /// <returns>False when it is null</returns>
        public static bool ToBool(this bool? value)
        {
            return value ?? false;
        }

        public static string ToSDABoolean(this bool value) => value ? "T" : "F";

        public static bool? ToNullableBoolean(this int? value) => value.HasValue ? (value.Value == 1) : (bool?)null;

        #endregion

        #region Validation Extensions

        public static bool IsValidEmail(this string value)
        {
            //NOTE: A null-t és az üres string-et is validnak vesszük, ha azt szeretnénk, hogy ezek ne legyenek validok, azokat külön kint kell vizsgálni string.IsNullOrWhiteSpace()-vel!
            bool result = new EmailAddressExtendedAttribute(true, true).IsValid(value);
            return result;
        }

        public static bool IsValidPhone(this string value)
        {
            //NOTE: A null-t és az üres string-et is validnak vesszük, ha azt szeretnénk, hogy ezek ne legyenek validok, azokat külön kint kell vizsgálni string.IsNullOrWhiteSpace()-vel!
            bool result = new PhoneExtendedAttribute(true, true).IsValid(value);
            return result;
        }

        public static bool Is18EvesElmult(this DateTime szuletesiIdo)
        {
            bool result = szuletesiIdo.Date.AddYears(18) < DateTime.Today;
            return result;
        }

        #endregion

        #region String Extensions

        public static string Truncate(this string value, int maxChars)
        {
            return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
        }

        public static string RemoveDiacritics(this string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return text;
            }

            text = text.Normalize(NormalizationForm.FormD);

            var chars = text
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
                .ToArray();

            return new string(chars).Normalize(NormalizationForm.FormC);
        }

        public static string ReplaceMultipleSpacesAndTrim(this string text, string defaultValue = null)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return defaultValue;
            }

            //NOTE: "\u00A0" = NO-BREAK SPACE
            //      "\u0009" = CHARACTER TABULATION
            //      "\u0020" = SPACE
            //      Replace-elni kell a NO-BREAK SPACE-eket, mert a SPACE-el nem ekvivalens és jöhet be ilyen adat és okozhat problémát.
            //      Replace-elni kell a CHARACTER TABULATION-öket, mert a SPACE-el nem ekvivalens és jöhet be ilyen adat és okozhat problémát.
            //      Ezt követően replace-eljük a többszörös szóközöket egyre.
            //      Ezt követően trim-elünk.
            string result = Regex.Replace(text.Replace("\u00A0", "\u0020").Replace("\u0009", "\u0020"), @"\s+", " ").Trim();
            if (string.IsNullOrWhiteSpace(result))
            {
                return defaultValue;
            }
            return result;
        }

        public static string RemoveSpecialCharacters(this string text)
            => Regex.Replace(text, @"[ \-'`~!@#$%^&*()_|+=?;:"",.<>{ }[\]\\/]", "");

        #endregion

        #region Collection Extensions

        public static int GetSequenceHashCode<T>(this IReadOnlyCollection<T> sequence)
        {
            if (sequence == null || sequence.Count == 0)
            {
                return 0;
            }

            return sequence.Select(item => item.GetHashCode()).Aggregate((total, nextCode) => total ^ nextCode);
        }

        public static void RemoveRange<T>(this List<T> self, IEnumerable<T> items)
        {
            var exceptItems = self.Except(items).ToList();
            self.Clear();
            self.AddRange(exceptItems);
        }

        public static bool NotNullAndAny<T>(this IEnumerable<T> enumerable)
        {
            return enumerable != null && enumerable.Any();
        }

        /// <summary>
        /// A generikus objektumlistában sorba rendezzük az adatokat és beállítjuk a lapozást a bejövő paraméterek alapján reflection-el.
        /// </summary>
        /// <typeparam name="T">A T bármilyen típus lehet.</typeparam>
        /// <param name="itemList">A T típusú objektumlista, amivel dolgozunk. Ha ez az érték null, akkor null-al térünk vissza, hogy ne legyen exception.</param>
        /// <param name="orderDictionary">A dictionary, amiben benne van, hogy mely property-k(Key) alapján és milyen irányba(Value) kell rendezni az adatokat. Ha ez az érték null vagy üres, akkor nincs sorbarendezés, hanem az eredeti sorrenddel dolgozunk tovább.</param>
        /// <param name="firstItemIndex">A lapozáshoz az első elem indexe. Ha ez az érték null, akkor nincs lapozás és visszaadunk minden elemet.</param>
        /// <param name="lastItemIndex">A lapozáshoz az utolsó elem indexe. Ha ez az érték null, akkor nincs lapozás és visszaadunk minden elemet.</param>
        /// <returns></returns>
        public static List<T> SortingAndPaging<T>(this IEnumerable<T> itemList, Dictionary<string, ListSortDirection> orderDictionary = null, int? firstItemIndex = null, int? lastItemIndex = null)
        {
            if (itemList == null)
            {
                return null;
            }

            List<T> result;
            if (orderDictionary != null && orderDictionary.Count > 0)
            {
                IOrderedEnumerable<T> orderedList = null;
                bool isOrderBy = true;
                foreach (var orderItem in orderDictionary)
                {
                    PropertyInfo propInfo = typeof(T).GetProperty(orderItem.Key);
                    if (isOrderBy)
                    {
                        orderedList = orderItem.Value == ListSortDirection.Ascending ?
                            itemList.OrderBy(x => propInfo.GetValue(x, null)) :
                            itemList.OrderByDescending(x => propInfo.GetValue(x, null));
                        isOrderBy = false;
                    }
                    else
                    {
                        orderedList = orderItem.Value == ListSortDirection.Ascending ?
                            orderedList.ThenBy(x => propInfo.GetValue(x, null)) :
                            orderedList.ThenByDescending(x => propInfo.GetValue(x, null));
                    }
                }

                result = orderedList.ToList();
            }
            else
            {
                result = itemList.ToList();
            }

            if (firstItemIndex.HasValue && lastItemIndex.HasValue)
            {
                if (firstItemIndex <= lastItemIndex)
                {
                    int pageSize = lastItemIndex.Value - firstItemIndex.Value + 1;
                    if (pageSize < result.Count)
                    {
                        result = result.Skip(firstItemIndex.Value).Take(pageSize).ToList();
                    }
                }
            }

            return result;
        }

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            var keys = new HashSet<TKey>();
            foreach (TSource element in source)
            {
                if (keys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }

        public static IEnumerable<T> Difference<T>(this IEnumerable<T> first, IEnumerable<T> second)
        {
            return first.Except(second).Union(second.Except(first));
        }

        public static void RemoveDictionaryItemByValue(this Dictionary<string, string> self, string value)
        {
            var valuesList = self.Where(s => s.Value == value).ToList();
            valuesList.ForEach(i => self.Remove(i.Key));
        }

        #endregion

        #region Enumeration Extensions 

        /// <summary>
        /// GetAttribute by Type
        /// </summary>
        /// <typeparam name="T">type of attribute</typeparam>
        /// <returns>type of Attribute</returns>
        public static T GetAttribute<T>(this System.Enum value) where T : Attribute
        {
            return (T)value.GetType()
                            .GetMember(value.ToString())[0].GetCustomAttributes(typeof(T), false)
                            .FirstOrDefault();

        }
        /// <summary>
        ///Get Display Name Attribute
        /// </summary>
        /// <example> [Display(Name = "xy")]</example>
        /// <returns>xy</returns>
        public static string ToDisplayName(this System.Enum value) => value.GetAttribute<DisplayAttribute>()?.Name;

        #endregion

        #region DateTimeExtensions

        public static DateTime StartOfTheDay(this DateTime dateTime)
        {
            return dateTime.Date;
        }

        public static DateTime EndOfTheDay(this DateTime dateTime)
        {
            return dateTime.Date.AddDays(1).AddTicks(-1);
        }

        #endregion DateTimeExtensions
    }
}