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

namespace EasyDevCore.Common
{
    /// <summary>
    /// String extension helper
    /// </summary>
    public static class StringExtensions
    {

        /// <summary>
        /// Ins the specified string comparison.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="comparisionType">One of the enumeration values that specifies how the strings will be compared.</param>
        /// <param name="args">The arguments.</param>
        /// <returns></returns>
        public static bool In(this string input, StringComparison comparisionType, params string[] args)
        {
            if (input == null) return false;
            foreach (string value in args)
            {
                if (!input.Equals(value))
                    continue;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the specified arguments has any.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="args">The arguments.</param>
        /// <returns>
        ///   <c>true</c> if the specified arguments has any; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAny(this string input, params string[] args)
        {
            foreach(var p in args)
            {
                if (input.IndexOf(p) > -1) return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the specified comparison has any.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="comparison">The comparison.</param>
        /// <param name="args">The arguments.</param>
        /// <returns>
        ///   <c>true</c> if the specified comparison has any; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAny(this string input, StringComparison comparison, params string[] args)
        {
            foreach (var p in args)
            {
                if (input.IndexOf(p, comparison) > -1) return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether [is unicode string] [the specified input].
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>
        ///   <c>true</c> if [is unicode string] [the specified input]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsUnicode(this string input)
        {
            return input.ToCharArray().Any(c => c > 255);
        }

        /// <summary>
        /// Removes all leading occurrences of whitespace of input string
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        public static string LTrim(this string input)
        {
            return input.TrimStart(new char[] { ' ' });
        }

        /// <summary>
        /// Removes all occurrences of whitespace of the end of the input string
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        public static string RTrim(this string input)
        {
            return input.TrimEnd(new char[] { ' ' });
        }

        /// <summary>
        /// Lefts the specified length.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="len">The length.</param>
        /// <returns></returns>
        public static string Left(this string input, int len) => (input == null || input.Length <= len) ? input : input.Substring(0, len);

        /// <summary>
        /// Rights the specified length.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="len">The length.</param>
        /// <returns></returns>
        public static string Right(this string input, int len) => (input == null || input.Length <= len) ? input : input.Substring(len - 1, input.Length - len);

        /// <summary>
        /// Replaces with case style.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="maxMatchLength">Maximum length of the match.</param>
        /// <returns></returns>
        public static string ReplaceCaseStyle(this string text, string oldValue, string newValue, int maxMatchLength = 0)
        {
            var caseReplacement = TransferCaseStyle(oldValue, newValue);
            return text.Replace(oldValue, caseReplacement);
        }

        /// <summary>
        /// Transfers the case style.
        /// </summary>
        /// <param name="match">The match.</param>
        /// <param name="replacement">The replacement.</param>
        /// <param name="maxMatchLength">Maximum length of the match.</param>
        /// <returns></returns>
        public static string TransferCaseStyle(string match, string replacement, int maxMatchLength = 0)
        {
            char[] replacementChars = replacement.ToCharArray();
            if(maxMatchLength == 0)
            {
                maxMatchLength = Math.Min(match.Length, replacement.Length);
            }    
            for (int i = 0; i < maxMatchLength; i++)
            {
                if (char.IsLower(match[i]))
                    replacementChars[i] = char.ToLower(replacementChars[i]);
                else if (char.IsUpper(match[i]))
                    replacementChars[i] = char.ToUpper(replacementChars[i]);
            }
            return new string(replacementChars);
        }


        /// <summary>
        /// Regexes the get match values.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="patterns">The patterns.</param>
        /// <returns>(Key = Match Value, Value = Group Name)</returns>
        public static Dictionary<string, string> RegexGetMatchValues(this string input, string patterns)
        {
            Dictionary<string, string> matchValues = new();
            Regex re = new Regex(patterns);
            var groupNames = re.GetGroupNames().Where(x => !(x[0] >= (byte)'0' && x[0] <= (byte)'9')).ToArray();
            var matches = re.Matches(input);
            foreach (Match m in matches.AsParallel())
            {
                foreach (var gn in groupNames)
                {
                    var mg = m.Groups[gn];
                    if (mg.Success)
                    {
                        var value = mg.Value;
                        if (!matchValues.ContainsKey(value))
                        {
                            matchValues.Add(value, gn);
                        }
                    }
                }
            }
            return matchValues;
        }

        /// <summary>
        /// Regexes the group match.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="patterns">The patterns.</param>
        /// <param name="matchHandler">The match handler is Func(Group match, string groupname, string replactedText).</param>
        public static void RegexGroupMatch(this string input, string patterns, Action<Group> matchHandler)
        {
            Regex re = new Regex(patterns);
            var groupNames = re.GetGroupNames().Where(x => !(x[0] >= (byte)'0' && x[0] <= (byte)'9')).ToArray();
            var matches = re.Matches(input);
            foreach (Match m in matches.AsParallel())
            {
                foreach (var gn in groupNames)
                {
                    var mg = m.Groups[gn];
                    if (mg.Success)
                    {
                        matchHandler(mg);
                    }
                }
            }
        }

        /// <summary>
        /// Regexes the group replace.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="patterns">The patterns.</param>
        /// <param name="matchHandler">The match handler is Func(Group match, string groupname, string replactedText).</param>
        /// <returns></returns>
        public static string RegexGroupReplace(this string input, string patterns, Func<Group, string, string> matchHandler)
        {
            Regex re = new Regex(patterns);
            var groupNames = re.GetGroupNames().Where(x => !(x[0] >= (byte)'0' && x[0] <= (byte)'9')).ToArray();
            return re.Replace(input, (MatchEvaluator)((m) =>
            {
                foreach (var gn in groupNames)
                {
                    var mg = m.Groups[gn];
                    if (mg.Success)
                    {
                        return matchHandler(mg, gn);
                    }
                }
                return m.Value;
            }));
        }

        /// <summary>
        /// Substitudes the specified input.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="index">The index.</param>
        /// <param name="length">The length.</param>
        /// <param name="replacedText">The replaced text.</param>
        /// <returns></returns>
        public static string Substitude(this string input, int index, int length, string replacedText)
        {
            return input.Substring(0, index) + replacedText + (index + length > input.Length ? "" : input.Substring(index + length));
        }

        /// <summary>
        /// Substitudes the specified input.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="index">The index.</param>
        /// <param name="replacedText">The replaced text.</param>
        /// <returns></returns>
        public static string Substitude(this string input, int index, string replacedText)
        {
            return input.Substring(0, index) + replacedText + (index + replacedText.Length > input.Length ? "" : input.Substring(index + replacedText.Length));
        }

        /// <summary>
        /// Splits the string.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="seperator">The seperator.</param>
        /// <param name="splitOptions">The split options.</param>
        /// <param name="trimSpace">if set to <c>true</c> [trim space].</param>
        /// <returns></returns>
        public static string[] SplitString(this string input, string seperator, StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries, bool trimSpace = false)
        {
            string[] result = input.Split(new string[] { seperator }, splitOptions);
            if (trimSpace)
            {
                return result.Select(s => s.Trim()).ToArray();
            }
            return result;
        }

        /// <summary>
        /// Splits the by comma or semi colon (,;).
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="splitOptions">The split options.</param>
        /// <param name="trimSpace">if set to <c>true</c> [trim space].</param>
        /// <returns>System.String[].</returns>
        public static string[] SplitByCommaOrSemiColon(this string input, StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries, bool trimSpace = false)
        {
            string[] result = input.Split( new char[] { ',', ';' }, splitOptions);
            if (trimSpace)
            {
                return result.Select(s => s.Trim()).ToArray();
            }
            return result;
        }

        /// <summary>
        /// Splits the comma string.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="splitOptions">The split options.</param>
        /// <param name="trimSpace">if set to <c>true</c> [trim space].</param>
        /// <returns></returns>
        public static string[] SplitCommaString(this string input, StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries, bool trimSpace = false)
        {
            string[] result = input.Split(new char[] { ',' }, splitOptions);
            if (trimSpace)
            {
                return result.Select(s => s.Trim()).ToArray();
            }
            return result;
        }

        /// <summary>
        /// Splits the semi colon string.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="splitOptions">The split options.</param>
        /// <param name="trimSpace">if set to <c>true</c> [trim space].</param>
        /// <returns></returns>
        public static string[] SplitSemiColonString(this string input, StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries, bool trimSpace = false)
        {
            string[] result = input.Split(new char[] { ';' }, splitOptions);
            if (trimSpace)
            {
                return result.Select(s => s.Trim()).ToArray();
            }
            return result;
        }

        /// <summary>
        /// A case insenstive replace function.
        /// </summary>
        /// <param name="value">The string to examine.</param>
        /// <param name="newValue">The value to replace.</param>
        /// <param name="oldValue">The new value to be inserted</param>
        /// <returns>A string</returns>
        public static string CaseInsenstiveReplace(this string value, string newValue, string oldValue)
        {
            Regex regEx = new Regex(oldValue, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(value, newValue);
        }

        /// <summary>
        /// Removes the new line (\n) and carriage return (\r) symbols.
        /// </summary>
        /// <param name="value">The string to search.</param>
        /// <param name="addSpace">If true, adds a space (" ") for each newline and carriage
        /// return found.</param>
        /// <returns>A string</returns>
        public static string RemoveNewLines(this string value, bool addSpace = false)
        {
            string replace = string.Empty;
            if (addSpace)
                replace = " ";

            string pattern = @"[\r|\n]";
            Regex regEx = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);

            return regEx.Replace(value, replace);
        }

        /// <summary>
        /// Reverse a string.
        /// </summary>
        /// <param name="value">The string to reverse</param>
        /// <returns>A string</returns>
        public static string Reverse(this string value)
        {
            if (value.Length <= 1)
                return value;

            char[] c = value.ToCharArray();
            StringBuilder sb = new StringBuilder(c.Length);
            for (int i = c.Length - 1; i > -1; i--)
                sb.Append(c[i]);

            return sb.ToString();
        }

        /// <summary>
        /// Converts a string to sentence case.
        /// </summary>
        /// <param name="value">The string to convert.</param>
        /// <returns>A string</returns>
        public static string SentenceCase(this string value)
        {
            if (value.Length < 1)
                return value;
            // start by converting entire string to lower case
            var lowerCase = value.ToLower();
            // matches the first sentence of a string, as well as subsequent sentences
            var r = new Regex(@"(^[a-z])|\.\s+(.)", RegexOptions.ExplicitCapture);
            // MatchEvaluator delegate defines replacement of setence starts to uppercase
            return r.Replace(lowerCase, s => s.Value.ToUpper());
        }
        
        /// <summary>
        /// Converts a string to title case.
        /// </summary>
        /// <param name="value">The string to convert.</param>
        /// <returns>A string.</returns>
        public static string TitleCase(this string value)
        {
            if (value.Length < 1)
                return value;
            // start by converting entire string to lower case
            var lowerCase = value.ToLower();
            // matches the first sentence of a string, as well as subsequent sentences
            var r = new Regex(@"\b(\w)", RegexOptions.ExplicitCapture);
            // MatchEvaluator delegate defines replacement of setence starts to uppercase
            return r.Replace(lowerCase, s => s.Value.ToUpper());
        }

        /// <summary>
        /// Determines whether the specified input is match.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="pattern">The pattern (regular expression).</param>
        /// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
        /// <returns>
        ///   <c>true</c> if the specified input is match; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsMatch(this string input, string pattern, bool ignoreCase = false)
        {
            return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        }

        
        /// <summary>
        /// string like operator.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="pattern">The pattern.</param>
        /// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
        /// <returns></returns>
        public static bool StringLike(this string input, string pattern, bool ignoreCase = false)
        {
            return SQLLike(input, pattern.Replace("?", "_").Replace("*", "%"), ignoreCase);
        }

        /// <summary>
        /// SQL like operator.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="pattern">The pattern.</param>
        /// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
        /// <returns></returns>
        public static bool SQLLike(this string input, string pattern, bool ignoreCase)
        {
            /* Turn "off" all regular expression related syntax in
             * the pattern string. */
            pattern = Regex.Escape(pattern);

            /* Replace the LIKE wildcard metacharacters with the
             * equivalent regular expression metacharacters. */
            pattern = pattern.Replace("_", ".").Replace("%", ".*");

            /* The previous call to Regex.Escape actually turned off
             * too many metacharacters, i.e. those which are recognized by
             * both the regular expression engine and the LIKE
             * statement ([...] and [^...]). Those metacharacters have
             * to be manually unescaped here. */
            pattern = pattern.Replace(@"\[", "[").Replace(@"\]", "]").Replace(@"\^", "^");

            if (ignoreCase)
            {
                return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
            }
            else
            {
                return Regex.IsMatch(input, pattern);
            }
        }

        /// <summary>
        /// SQLs the like pattern.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="pattern">The pattern.</param>
        /// <returns></returns>
        public static bool SQLLike(this string input, string pattern)
        {
            bool isMatch = true,
                isWildCardOn = false,
                isCharWildCardOn = false,
                isCharSetOn = false,
                isNotCharSetOn = false,
                endOfPattern = false;
            int lastWildCard = -1;
            int patternIndex = 0;
            List<char> set = new List<char>();
            char p = '\0';

            for (int i = 0; i < input.Length; i++)
            {
                char c = input[i];
                endOfPattern = (patternIndex >= pattern.Length);
                if (!endOfPattern)
                {
                    p = pattern[patternIndex];

                    if (!isWildCardOn && p == '%')
                    {
                        lastWildCard = patternIndex;
                        isWildCardOn = true;
                        while (patternIndex < pattern.Length &&
                            pattern[patternIndex] == '%')
                        {
                            patternIndex++;
                        }
                        if (patternIndex >= pattern.Length) p = '\0';
                        else p = pattern[patternIndex];
                    }
                    else if (p == '_')
                    {
                        isCharWildCardOn = true;
                        patternIndex++;
                    }
                    else if (p == '[')
                    {
                        if (pattern[++patternIndex] == '^')
                        {
                            isNotCharSetOn = true;
                            patternIndex++;
                        }
                        else isCharSetOn = true;

                        set.Clear();
                        if (pattern[patternIndex + 1] == '-' && pattern[patternIndex + 3] == ']')
                        {
                            char start = char.ToUpper(pattern[patternIndex]);
                            patternIndex += 2;
                            char end = char.ToUpper(pattern[patternIndex]);
                            if (start <= end)
                            {
                                for (char ci = start; ci <= end; ci++)
                                {
                                    set.Add(ci);
                                }
                            }
                            patternIndex++;
                        }

                        while (patternIndex < pattern.Length &&
                            pattern[patternIndex] != ']')
                        {
                            set.Add(pattern[patternIndex]);
                            patternIndex++;
                        }
                        patternIndex++;
                    }
                }

                if (isWildCardOn)
                {
                    if (char.ToUpper(c) == char.ToUpper(p))
                    {
                        isWildCardOn = false;
                        patternIndex++;
                    }
                }
                else if (isCharWildCardOn)
                {
                    isCharWildCardOn = false;
                }
                else if (isCharSetOn || isNotCharSetOn)
                {
                    bool charMatch = (set.Contains(char.ToUpper(c)));
                    if ((isNotCharSetOn && charMatch) || (isCharSetOn && !charMatch))
                    {
                        if (lastWildCard >= 0) patternIndex = lastWildCard;
                        else
                        {
                            isMatch = false;
                            break;
                        }
                    }
                    isNotCharSetOn = isCharSetOn = false;
                }
                else
                {
                    if (char.ToUpper(c) == char.ToUpper(p))
                    {
                        patternIndex++;
                    }
                    else
                    {
                        if (lastWildCard >= 0) patternIndex = lastWildCard;
                        else
                        {
                            isMatch = false;
                            break;
                        }
                    }
                }
            }
            endOfPattern = (patternIndex >= pattern.Length);

            if (isMatch && !endOfPattern)
            {
                bool isOnlyWildCards = true;
                for (int i = patternIndex; i < pattern.Length; i++)
                {
                    if (pattern[i] != '%')
                    {
                        isOnlyWildCards = false;
                        break;
                    }
                }
                if (isOnlyWildCards) endOfPattern = true;
            }
            return isMatch && endOfPattern;
        }    
    }

}
