/// <summary>
        /// Get suitable tags
        /// </summary>
        /// <param name="languageTag"></param>
        /// <returns></returns>
        public IEnumerable <LanguageTag> GetSuitableTags(LanguageTag languageTag)
        {
            if (_cachedResults.TryGetValue(languageTag.GetHashCode(), out var results))
            {
                return(results.Select(s => s.Value));
            }

            if (!_items.Any())
            {
                return(Enumerable.Empty <LanguageTag>());
            }

            results = new List <FallbackItem>();

            var orderedItems  = _items.OrderByDescending(o => o.Priority).ToList();
            var skipHashCodes = new List <int>();

            //step.1 filter all results by this order:  ExactMatch, DefaultRegion, ScriptMatch and LanguageMatch
            foreach (var level in _matchLevels)
            {
                results.AddRange(Filter(orderedItems, languageTag, level, skipHashCodes));
            }

            //step.2 add all results which priority less than the min of the result that got from step.1
            results.AddRange(Filter(orderedItems, results.Min(m => m.Priority), skipHashCodes));

            //step.3 add default language tag (in this case, the most less priority) if need.
            AddMostLessPriorityIfNeed(results, skipHashCodes);

            //step.4 clear skipped hashcode cache
            skipHashCodes.Clear();

            return(Remember(languageTag, results));
        }
        /// <summary>
        /// Register language tag
        /// </summary>
        /// <param name="languageTag"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public void RegisterTag(LanguageTag languageTag)
        {
            if (languageTag == null)
            {
                throw new ArgumentNullException(nameof(languageTag));
            }

            var hashcode = languageTag.GetHashCode();

            // ReSharper disable once InconsistentlySynchronizedField
            if (_items.Any(x => x.HashCodeCached == hashcode))
            {
                return;
            }

            lock (_cacheLockObj) {
                if (_items.Any(x => x.HashCodeCached == hashcode))
                {
                    return;
                }

                //this will update all item's priority
                _items.Add(new FallbackItem(languageTag));
            }
        }
 /// <summary>
 /// Has cached result
 /// </summary>
 /// <param name="languageTag"></param>
 /// <returns></returns>
 public bool HashCachedResult(LanguageTag languageTag)
 {
     if (languageTag == null)
     {
         return(false);
     }
     return(_cachedResults.ContainsKey(languageTag.GetHashCode()));
 }
        private IEnumerable <LanguageTag> Remember(LanguageTag left, List <FallbackItem> filteredItems)
        {
            lock (_cacheLockObj) {
                var hashcode = left.GetHashCode();
                if (_cachedResults.ContainsKey(hashcode))
                {
                    _cachedResults[hashcode] = filteredItems;
                }
                else
                {
                    _cachedResults.Add(hashcode, filteredItems);
                }
            }

            return(filteredItems.Select(s => s.Value));
        }