// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Net.Http.Headers
{
    /// <summary>
    /// Represents the <c>Cache-Control</c> HTTP header.
    /// </summary>
    public class CacheControlHeaderValue
    {
        /// <summary>
        /// A constant for the <c>public</c> cache-control directive.
        /// </summary>
        public static readonly string PublicString = "public";

        /// <summary>
        /// A constant for the <c>private</c> cache-control directive.
        /// </summary>
        public static readonly string PrivateString = "private";

        /// <summary>
        /// A constant for the <c>max-age</c> cache-control directive.
        /// </summary>
        public static readonly string MaxAgeString = "max-age";

        /// <summary>
        /// A constant for the <c>s-maxage</c> cache-control directive.
        /// </summary>
        public static readonly string SharedMaxAgeString = "s-maxage";

        /// <summary>
        /// A constant for the <c>no-cache</c> cache-control directive.
        /// </summary>
        public static readonly string NoCacheString = "no-cache";

        /// <summary>
        /// A constant for the <c>no-store</c> cache-control directive.
        /// </summary>
        public static readonly string NoStoreString = "no-store";

        /// <summary>
        /// A constant for the <c>max-stale</c> cache-control directive.
        /// </summary>
        public static readonly string MaxStaleString = "max-stale";

        /// <summary>
        /// A constant for the <c>min-fresh</c> cache-control directive.
        /// </summary>
        public static readonly string MinFreshString = "min-fresh";

        /// <summary>
        /// A constant for the <c>no-transform</c> cache-control directive.
        /// </summary>
        public static readonly string NoTransformString = "no-transform";

        /// <summary>
        /// A constant for the <c>only-if-cached</c> cache-control directive.
        /// </summary>
        public static readonly string OnlyIfCachedString = "only-if-cached";

        /// <summary>
        /// A constant for the <c>must-revalidate</c> cache-control directive.
        /// </summary>
        public static readonly string MustRevalidateString = "must-revalidate";

        /// <summary>
        /// A constant for the <c>proxy-revalidate</c> cache-control directive.
        /// </summary>
        public static readonly string ProxyRevalidateString = "proxy-revalidate";

        // The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
        // as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
        // OK to have multiple Cache-Control headers in a request/response message. However, after parsing all
        // Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
        // values, otherwise we may have multiple strings containing the invalid values).
        private static readonly HttpHeaderParser<CacheControlHeaderValue> Parser
            = new GenericHeaderParser<CacheControlHeaderValue>(true, GetCacheControlLength);

        private static readonly Action<StringSegment> CheckIsValidTokenAction = CheckIsValidToken;

        private bool _noCache;
        private ICollection<StringSegment>? _noCacheHeaders;
        private bool _noStore;
        private TimeSpan? _maxAge;
        private TimeSpan? _sharedMaxAge;
        private bool _maxStale;
        private TimeSpan? _maxStaleLimit;
        private TimeSpan? _minFresh;
        private bool _noTransform;
        private bool _onlyIfCached;
        private bool _public;
        private bool _private;
        private ICollection<StringSegment>? _privateHeaders;
        private bool _mustRevalidate;
        private bool _proxyRevalidate;
        private IList<NameValueHeaderValue>? _extensions;

        /// <summary>
        /// Initializes a new instance of <see cref="CacheControlHeaderValue"/>.
        /// </summary>
        public CacheControlHeaderValue()
        {
            // This type is unique in that there is no single required parameter.
        }

        /// <summary>
        /// Gets or sets a value for the <c>no-cache</c> directive.
        /// <para>
        /// Configuring no-cache indicates that the client must re-validate cached responses with the original server
        /// before using it.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.4</remarks>
        public bool NoCache
        {
            get { return _noCache; }
            set { _noCache = value; }
        }

        /// <summary>
        /// Gets a collection of field names in the "no-cache" directive in a cache-control header field on an HTTP response.
        /// </summary>
        public ICollection<StringSegment> NoCacheHeaders
        {
            get
            {
                if (_noCacheHeaders == null)
                {
                    _noCacheHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
                }
                return _noCacheHeaders;
            }
        }

        /// <summary>
        /// Gets or sets a value for the <c>no-store</c> directive.
        /// <para>
        /// Configuring no-store indicates that the response may not be stored in any cache.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.5</remarks>
        public bool NoStore
        {
            get { return _noStore; }
            set { _noStore = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>max-age</c> directive.
        /// <para>
        /// max-age specifies the maximum amount of time the response is considered fresh.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.1</remarks>
        public TimeSpan? MaxAge
        {
            get { return _maxAge; }
            set { _maxAge = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>s-maxage</c> directive.
        /// <para>
        /// Overrides <see cref="MaxAge">max-age</see>, but only for shared caches (such as proxies).
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.9</remarks>
        public TimeSpan? SharedMaxAge
        {
            get { return _sharedMaxAge; }
            set { _sharedMaxAge = value; }
        }

        /// <summary>
        /// Gets or sets a value that determines if the <c>max-stale</c> is included.
        /// <para>
        /// <c>max-stale</c> that the client will accept stale responses. The maximum tolerance for staleness
        /// is specified by <see cref="MaxStaleLimit"/>.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
        public bool MaxStale
        {
            get { return _maxStale; }
            set { _maxStale = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>max-stale</c> directive.
        /// <para>
        /// Indicates the maximum duration an HTTP client is willing to accept a response that has exceeded its expiration time.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
        public TimeSpan? MaxStaleLimit
        {
            get { return _maxStaleLimit; }
            set { _maxStaleLimit = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>min-fresh</c> directive.
        /// <para>
        /// Indicates the freshness lifetime that an HTTP client is willing to accept a response.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.3</remarks>
        public TimeSpan? MinFresh
        {
            get { return _minFresh; }
            set { _minFresh = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>no-transform</c> request directive.
        /// <para>
        /// Forbids intermediate caches or proxies from editing the response payload.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.6</remarks>
        public bool NoTransform
        {
            get { return _noTransform; }
            set { _noTransform = value; }
        }

        /// <summary>
        /// Gets or sets a value for the <c>only-if-cached</c> request directive.
        /// <para>
        /// Indicates that the client only wishes to obtain a stored response
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.7</remarks>
        public bool OnlyIfCached
        {
            get { return _onlyIfCached; }
            set { _onlyIfCached = value; }
        }

        /// <summary>
        /// Gets or sets a value that determines if the <c>public</c> response directive is included.
        /// <para>
        /// Indicates that the response may be stored by any cache.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.5</remarks>
        public bool Public
        {
            get { return _public; }
            set { _public = value; }
        }

        /// <summary>
        /// Gets or sets a value that determines if the <c>private</c> response directive is included.
        /// <para>
        /// Indicates that the response may not be stored by a shared cache.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.6</remarks>
        public bool Private
        {
            get { return _private; }
            set { _private = value; }
        }

        /// <summary>
        /// Gets a collection of field names in the "private" directive in a cache-control header field on an HTTP response.
        /// </summary>
        public ICollection<StringSegment> PrivateHeaders
        {
            get
            {
                if (_privateHeaders == null)
                {
                    _privateHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
                }
                return _privateHeaders;
            }
        }

        /// <summary>
        /// Gets or sets a value that determines if the <c>must-revalidate</c> response directive is included.
        /// <para>
        /// Indicates that caches must revalidate the use of stale caches with the origin server before their use.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
        public bool MustRevalidate
        {
            get { return _mustRevalidate; }
            set { _mustRevalidate = value; }
        }

        /// <summary>
        /// Gets or sets a value that determines if the <c>proxy-validate</c> response directive is included.
        /// <para>
        /// Indicates that shared caches must revalidate the use of stale caches with the origin server before their use.
        /// </para>
        /// </summary>
        /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
        public bool ProxyRevalidate
        {
            get { return _proxyRevalidate; }
            set { _proxyRevalidate = value; }
        }

        /// <summary>
        /// Gets cache-extension tokens, each with an optional assigned value.
        /// </summary>
        public IList<NameValueHeaderValue> Extensions
        {
            get
            {
                if (_extensions == null)
                {
                    _extensions = new ObjectCollection<NameValueHeaderValue>();
                }
                return _extensions;
            }
        }

        /// <inheritdoc />
        public override string ToString()
        {
            var sb = new StringBuilder();

            AppendValueIfRequired(sb, _noStore, NoStoreString);
            AppendValueIfRequired(sb, _noTransform, NoTransformString);
            AppendValueIfRequired(sb, _onlyIfCached, OnlyIfCachedString);
            AppendValueIfRequired(sb, _public, PublicString);
            AppendValueIfRequired(sb, _mustRevalidate, MustRevalidateString);
            AppendValueIfRequired(sb, _proxyRevalidate, ProxyRevalidateString);

            if (_noCache)
            {
                AppendValueWithSeparatorIfRequired(sb, NoCacheString);
                if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
                {
                    sb.Append("=\"");
                    AppendValues(sb, _noCacheHeaders);
                    sb.Append('\"');
                }
            }

            if (_maxAge.HasValue)
            {
                AppendValueWithSeparatorIfRequired(sb, MaxAgeString);
                sb.Append('=');
                sb.Append(((int)_maxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
            }

            if (_sharedMaxAge.HasValue)
            {
                AppendValueWithSeparatorIfRequired(sb, SharedMaxAgeString);
                sb.Append('=');
                sb.Append(((int)_sharedMaxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
            }

            if (_maxStale)
            {
                AppendValueWithSeparatorIfRequired(sb, MaxStaleString);
                if (_maxStaleLimit.HasValue)
                {
                    sb.Append('=');
                    sb.Append(((int)_maxStaleLimit.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
                }
            }

            if (_minFresh.HasValue)
            {
                AppendValueWithSeparatorIfRequired(sb, MinFreshString);
                sb.Append('=');
                sb.Append(((int)_minFresh.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
            }

            if (_private)
            {
                AppendValueWithSeparatorIfRequired(sb, PrivateString);
                if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
                {
                    sb.Append("=\"");
                    AppendValues(sb, _privateHeaders);
                    sb.Append('\"');
                }
            }

            NameValueHeaderValue.ToString(_extensions, ',', false, sb);

            return sb.ToString();
        }

        /// <inheritdoc />
        public override bool Equals(object? obj)
        {
            var other = obj as CacheControlHeaderValue;

            if (other == null)
            {
                return false;
            }

            if ((_noCache != other._noCache) || (_noStore != other._noStore) || (_maxAge != other._maxAge) ||
                (_sharedMaxAge != other._sharedMaxAge) || (_maxStale != other._maxStale) ||
                (_maxStaleLimit != other._maxStaleLimit) || (_minFresh != other._minFresh) ||
                (_noTransform != other._noTransform) || (_onlyIfCached != other._onlyIfCached) ||
                (_public != other._public) || (_private != other._private) ||
                (_mustRevalidate != other._mustRevalidate) || (_proxyRevalidate != other._proxyRevalidate))
            {
                return false;
            }

            if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
                StringSegmentComparer.OrdinalIgnoreCase))
            {
                return false;
            }

            if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
                StringSegmentComparer.OrdinalIgnoreCase))
            {
                return false;
            }

            if (!HeaderUtilities.AreEqualCollections(_extensions, other._extensions))
            {
                return false;
            }

            return true;
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            // Use a different bit for bool fields: bool.GetHashCode() will return 0 (false) or 1 (true). So we would
            // end up having the same hash code for e.g. two instances where one has only noCache set and the other
            // only noStore.
            int result = _noCache.GetHashCode() ^ (_noStore.GetHashCode() << 1) ^ (_maxStale.GetHashCode() << 2) ^
                (_noTransform.GetHashCode() << 3) ^ (_onlyIfCached.GetHashCode() << 4) ^
                (_public.GetHashCode() << 5) ^ (_private.GetHashCode() << 6) ^
                (_mustRevalidate.GetHashCode() << 7) ^ (_proxyRevalidate.GetHashCode() << 8);

            // XOR the hashcode of timespan values with different numbers to make sure two instances with the same
            // timespan set on different fields result in different hashcodes.
            result = result ^ (_maxAge.HasValue ? _maxAge.GetValueOrDefault().GetHashCode() ^ 1 : 0) ^
                (_sharedMaxAge.HasValue ? _sharedMaxAge.GetValueOrDefault().GetHashCode() ^ 2 : 0) ^
                (_maxStaleLimit.HasValue ? _maxStaleLimit.GetValueOrDefault().GetHashCode() ^ 4 : 0) ^
                (_minFresh.HasValue ? _minFresh.GetValueOrDefault().GetHashCode() ^ 8 : 0);

            if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
            {
                foreach (var noCacheHeader in _noCacheHeaders)
                {
                    result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
                }
            }

            if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
            {
                foreach (var privateHeader in _privateHeaders)
                {
                    result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
                }
            }

            if ((_extensions != null) && (_extensions.Count > 0))
            {
                foreach (var extension in _extensions)
                {
                    result = result ^ extension.GetHashCode();
                }
            }

            return result;
        }

        /// <summary>
        /// Parses <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/> value.
        /// </summary>
        /// <param name="input">The values to parse.</param>
        /// <returns>The parsed values.</returns>
        public static CacheControlHeaderValue Parse(StringSegment input)
        {
            var index = 0;
            // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
            var result = Parser.ParseValue(input, ref index);
            if (result == null)
            {
                throw new FormatException("No cache directives found.");
            }
            return result;
        }

        /// <summary>
        /// Attempts to parse the specified <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/>.
        /// </summary>
        /// <param name="input">The value to parse.</param>
        /// <param name="parsedValue">The parsed value.</param>
        /// <returns><see langword="true"/> if input is a valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
        public static bool TryParse(StringSegment input, [NotNullWhen(true)] out CacheControlHeaderValue? parsedValue)
        {
            var index = 0;
            // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
            if (Parser.TryParseValue(input, ref index, out parsedValue) && parsedValue != null)
            {
                return true;
            }
            parsedValue = null;
            return false;
        }

        private static int GetCacheControlLength(StringSegment input, int startIndex, out CacheControlHeaderValue? parsedValue)
        {
            Contract.Requires(startIndex >= 0);

            parsedValue = null;

            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return 0;
            }

            // Cache-Control header consists of a list of name/value pairs, where the value is optional. So use an
            // instance of NameValueHeaderParser to parse the string.
            var current = startIndex;
            var nameValueList = new List<NameValueHeaderValue>();
            while (current < input.Length)
            {
                if (!NameValueHeaderValue.MultipleValueParser.TryParseValue(input, ref current, out var nameValue))
                {
                    return 0;
                }

                if (nameValue != null)
                {
                    nameValueList.Add(nameValue);
                }
            }

            // If we get here, we were able to successfully parse the string as list of name/value pairs. Now analyze
            // the name/value pairs.

            // Cache-Control is a header supporting lists of values. However, expose the header as an instance of
            // CacheControlHeaderValue.
            var result = new CacheControlHeaderValue();

            if (!TrySetCacheControlValues(result, nameValueList))
            {
                return 0;
            }

            parsedValue = result;

            // If we get here we successfully parsed the whole string.
            return input.Length - startIndex;
        }

        private static bool TrySetCacheControlValues(
            CacheControlHeaderValue cc,
            List<NameValueHeaderValue> nameValueList)
        {
            for (var i = 0; i < nameValueList.Count; i++)
            {
                var nameValue = nameValueList[i];
                var name = nameValue.Name;
                var success = true;

                switch (name.Length)
                {
                    case 6:
                        if (StringSegment.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._public);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 7:
                        if (StringSegment.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTimeSpan(nameValue, ref cc._maxAge);
                        }
                        else if(StringSegment.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 8:
                        if (StringSegment.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
                        }
                        else if (StringSegment.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
                        }
                        else if (StringSegment.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 9:
                        if (StringSegment.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
                            if (success)
                            {
                                cc._maxStale = true;
                            }
                        }
                        else if (StringSegment.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTimeSpan(nameValue, ref cc._minFresh);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 12:
                        if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 14:
                        if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 15:
                        if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    case 16:
                        if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
                        {
                            success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
                        }
                        else
                        {
                            goto default;
                        }
                        break;

                    default:
                        cc.Extensions.Add(nameValue); // success is always true
                        break;
                }

                if (!success)
                {
                    return false;
                }
            }

            return true;
        }

        private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
        {
            if (nameValue.Value != null)
            {
                return false;
            }

            boolField = true;
            return true;
        }

        private static bool TrySetOptionalTokenList(
            NameValueHeaderValue nameValue,
            ref bool boolField,
            ref ICollection<StringSegment>? destination)
        {
            if (nameValue.Value == null)
            {
                boolField = true;
                return true;
            }

            // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
            // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespaces.
            var valueString = nameValue.Value;
            if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
            {
                return false;
            }

            // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
            var current = 1; // skip the initial '"' character.
            var maxLength = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
            var separatorFound = false;
            var originalValueCount = destination == null ? 0 : destination.Count;
            while (current < maxLength)
            {
                current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
                    out separatorFound);

                if (current == maxLength)
                {
                    break;
                }

                var tokenLength = HttpRuleParser.GetTokenLength(valueString, current);

                if (tokenLength == 0)
                {
                    // We already skipped whitespaces and separators. If we don't have a token it must be an invalid
                    // character.
                    return false;
                }

                if (destination == null)
                {
                    destination = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
                }

                destination.Add(valueString.Subsegment(current, tokenLength));

                current = current + tokenLength;
            }

            // After parsing a valid token list, we expect to have at least one value
            if ((destination != null) && (destination.Count > originalValueCount))
            {
                boolField = true;
                return true;
            }

            return false;
        }

        private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
        {
            if (nameValue.Value == null)
            {
                return false;
            }

            int seconds;
            if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
            {
                return false;
            }

            timeSpan = new TimeSpan(0, 0, seconds);

            return true;
        }

        private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
        {
            if (appendValue)
            {
                AppendValueWithSeparatorIfRequired(sb, value);
            }
        }

        private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
        {
            if (sb.Length > 0)
            {
                sb.Append(", ");
            }
            sb.Append(value);
        }

        private static void AppendValues(StringBuilder sb, IEnumerable<StringSegment> values)
        {
            var first = true;
            foreach (var value in values)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    sb.Append(", ");
                }

                sb.Append(value.AsSpan());
            }
        }

        private static void CheckIsValidToken(StringSegment item)
        {
            HeaderUtilities.CheckValidToken(item, nameof(item));
        }
    }
}
