/// <summary>
        /// Retrieve all rules that match this string
        /// </summary>
        /// <param name="str">input string</param>
        /// <returns>list of rule items</returns>
        public List <MatchingRuleOutput> Retrieve(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentNullException(nameof(str));
            }

            if (Perfect.TryGetValue(str, out var ret))
            {
                return(ret.ToRuleOutputs());
            }

            // Fetch rule candidates
            var candidates = new List <MatchingRuleItem>();

            for (var i = 0; i < str.Length; i++)
            {
                for (var j = 0; j < Math.Min(MaxIndexLen, str.Length - i); j++)
                {
                    var key = str.Substring(i, j + 1);
                    if (RuleItems.TryGetValue(key, out var rules))
                    {
                        candidates.AddRange(rules);
                    }
                }
            }

            // Further check if rule matches or not
            var matches = new List <MatchingRuleItem>();

            foreach (var candidate in candidates)
            {
                if (candidate.KeyWords.All(str.Contains))
                {
                    matches.Add(candidate);
                }
            }

            return(matches.ToRuleOutputs());
        }
        /// <summary>
        /// Add matching rule
        /// </summary>
        /// <param name="matchingRuleItem"></param>
        public void Add(MatchingRuleItem matchingRuleItem)
        {
            switch (matchingRuleItem.Type)
            {
            case MatchingRuleType.Perfect:
                var key = matchingRuleItem.KeyWords[0];
                if (!Perfect.TryGetValue(key, out var list))
                {
                    list         = new List <MatchingRuleItem>();
                    Perfect[key] = list;
                }
                list.Add(matchingRuleItem);
                break;

            case MatchingRuleType.Contain:
                var indexKeyWord = "";
                foreach (var keyword in matchingRuleItem.KeyWords)
                {
                    if (indexKeyWord.Length < keyword.Length)
                    {
                        indexKeyWord = keyword;
                    }
                }
                var indexKey = indexKeyWord.Substring(0, Math.Min(indexKeyWord.Length, MaxIndexLen));
                if (!RuleItems.TryGetValue(indexKey, out var containRuleItems))
                {
                    containRuleItems    = new List <MatchingRuleItem>();
                    RuleItems[indexKey] = containRuleItems;
                }
                containRuleItems.Add(matchingRuleItem);
                break;

            default:
                break;
            }
        }
        /// <summary>
        /// Add matching rule
        /// </summary>
        /// <param name="matchingRuleItem"></param>
        public void Add(MatchingRuleItem matchingRuleItem)
        {
            var keywords = matchingRuleItem.KeyWords.SplitPatterns().ToList();

            switch (matchingRuleItem.Type)
            {
            case MatchingRuleType.Perfect:
                var key = keywords.First();

                if (!key.IsPattern())
                {
                    if (!Perfect.TryGetValue(key, out var list))
                    {
                        list         = new List <MatchingRuleItem>();
                        Perfect[key] = list;
                    }
                    list.Add(matchingRuleItem);
                }
                else if (PatternToPhrases.TryGetValue(key, out var phrases))
                {
                    foreach (var phrase in phrases)
                    {
                        if (!Perfect.TryGetValue(phrase, out var list))
                        {
                            list            = new List <MatchingRuleItem>();
                            Perfect[phrase] = list;
                        }
                        list.Add(matchingRuleItem);
                    }
                }
                else
                {
                    throw new KeyNotFoundException($"Pattern={key} is not found");
                }
                break;

            case MatchingRuleType.Pattern:
            case MatchingRuleType.Contain:
                var indexKeyWord = keywords.First();
                foreach (var keyword in keywords.Skip(1))
                {
                    if (!keyword.IsPattern())
                    {
                        if (indexKeyWord.IsPattern())
                        {
                            indexKeyWord = keyword;
                        }
                        else
                        {
                            if (indexKeyWord.Length < keyword.Length)
                            {
                                indexKeyWord = keyword;
                            }
                        }
                    }
                }

                if (!indexKeyWord.IsPattern())
                {
                    var indexKey = indexKeyWord.Substring(0, Math.Min(indexKeyWord.Length, MaxIndexLen));
                    if (!RuleItems.TryGetValue(indexKey, out var containRuleItems))
                    {
                        containRuleItems    = new List <MatchingRuleItem>();
                        RuleItems[indexKey] = containRuleItems;
                    }
                    containRuleItems.Add(matchingRuleItem);
                }
                else if (PatternToPhrases.TryGetValue(indexKeyWord, out var phrases))
                {
                    foreach (var phrase in phrases)
                    {
                        var indexKey = phrase.Substring(0, Math.Min(phrase.Length, MaxIndexLen));
                        if (!RuleItems.TryGetValue(indexKey, out var list))
                        {
                            list = new List <MatchingRuleItem>();
                            RuleItems[indexKey] = list;
                        }
                        list.Add(matchingRuleItem);
                    }
                }
                else
                {
                    throw new KeyNotFoundException($"Pattern={indexKeyWord} is not found");
                }
                break;
            }
        }
        /// <summary>
        /// Retrieve all rules that match this string
        /// </summary>
        /// <param name="str">input string</param>
        /// <returns>list of rule items</returns>
        public List <MatchingRuleOutput> Retrieve(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentNullException(nameof(str));
            }

            if (Perfect.TryGetValue(str, out var ret))
            {
                return(ret.ToRuleOutputs());
            }

            // Fetch rule candidates
            var candidates = new List <MatchingRuleItem>();

            for (var i = 0; i < str.Length; i++)
            {
                for (var j = 0; j < Math.Min(MaxIndexLen, str.Length - i); j++)
                {
                    var key = str.Substring(i, j + 1);
                    if (RuleItems.TryGetValue(key, out var rules))
                    {
                        candidates.AddRange(rules);
                    }
                }
            }

            candidates = candidates.OrderBy(x => { return(x.Type == MatchingRuleType.Pattern ? 0 : 1); }).ToList();

            // Further check if rule matches or not
            var matches = new List <MatchingRuleOutput>();

            foreach (var candidate in candidates)
            {
                var keywords = candidate.KeyWords.SplitPatterns().ToList();
                switch (candidate.Type)
                {
                case MatchingRuleType.Contain:
                    var patternMatches1 = keywords.Select(keyword =>
                    {
                        if (!keyword.IsPattern())
                        {
                            return(str.Contains(keyword) ? keyword : null);
                        }
                        else if (PatternToPhrases.TryGetValue(keyword, out var phrases))
                        {
                            return(phrases.FirstOrDefault(x => str.Contains(x)));
                        }
                        return(null);
                    });
                    if (!patternMatches1.Any(x => x == null))
                    {
                        matches.Add(candidate.ToRuleOutput(patternMatches1));
                    }

                    break;

                case MatchingRuleType.Pattern:
                    var patternMatches2 = PatternMatches(str, keywords);
                    if (patternMatches2.Any())
                    {
                        matches.Add(candidate.ToRuleOutput(patternMatches2));
                    }
                    break;

                default:
                    break;
                }
            }
            return(matches);
        }