/// <summary>
 /// Gets the domains that match a specified uri, into a group of domains.
 /// </summary>
 /// <param name="domains">The group of domains.</param>
 /// <param name="current">The uri, or null.</param>
 /// <returns>The domains and their normalized uris, that match the specified uri.</returns>
 internal static IEnumerable<DomainAndUri> DomainsForUri(Domain[] domains, Uri current)
 {
     var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
     return domains
         .Where(d => !d.IsWildcard)
         .Select(SanitizeForBackwardCompatibility)
         .Select(d => new DomainAndUri(d, scheme))
         .OrderByDescending(d => d.Uri.ToString());
 }
        /// <summary>
        /// Finds the domain that best matches a specified uri, into a group of domains.
        /// </summary>
        /// <param name="domains">The group of domains.</param>
        /// <param name="current">The uri, or null.</param>
        /// <param name="filter">A function to filter the list of domains, if more than one applies, or <c>null</c>.</param>
        /// <returns>The domain and its normalized uri, that best matches the specified uri.</returns>
        /// <remarks>
        /// <para>If more than one domain matches, then the <paramref name="filter"/> function is used to pick
        /// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para>
        /// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para>
        /// </remarks>
        internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null)
        {
            // sanitize the list to have proper uris for comparison (scheme, path end with /)
            // we need to end with / because example.com/foo cannot match example.com/foobar
            // we need to order so example.com/foo matches before example.com/
            var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
            var domainsAndUris = domains
                .Where(d => !d.IsWildcard)
                .Select(SanitizeForBackwardCompatibility)
                .Select(d => new DomainAndUri(d, scheme))
                .OrderByDescending(d => d.Uri.ToString())
                .ToArray();

            if (!domainsAndUris.Any())
                return null;

            DomainAndUri domainAndUri;
            if (current == null)
            {
                // take the first one by default (what else can we do?)
                domainAndUri = domainsAndUris.First(); // .First() protected by .Any() above
            }
            else
            {
                // look for the first domain that would be the base of the hint
                var hintWithSlash = current.EndPathWithSlash();
                domainAndUri = domainsAndUris
                    .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
                // if none matches, then try to run the filter to pick a domain
                if (domainAndUri == null && filter != null)
                {
                    domainAndUri = filter(domainsAndUris);
                    // if still nothing, pick the first one?
                    // no: move that constraint to the filter, but check
                    if (domainAndUri == null)
                        throw new InvalidOperationException("The filter returned null.");
                }
            }

            return domainAndUri;
        }