/// <summary>
        /// Determines whether this span set intersects with another span set.
        /// </summary>
        /// <param name="set">Set to test.</param>
        /// <returns><c>true</c> if the span sets intersect, otherwise <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="set"/> is null.</exception>
        public bool IntersectsWith(NormalizedTextSpanCollection set)
        {
            if (set == null)
            {
                throw new ArgumentNullException(nameof(set));
            }

            for (int index1 = 0, index2 = 0; (index1 < this.Count) && (index2 < set.Count);)
            {
                TextSpan span1 = this[index1];
                TextSpan span2 = set[index2];

                if (span1.IntersectsWith(span2))
                {
                    return(true);
                }

                if (span1.End < span2.End)
                {
                    ++index1;
                }
                else
                {
                    ++index2;
                }
            }

            return(false);
        }
        /// <summary>
        /// Finds the overlap of two span sets.
        /// </summary>
        /// <param name="left">The first span set.</param>
        /// <param name="right">The second span set.</param>
        /// <returns>The new span set that corresponds to the overlap of <paramref name="left"/> and <paramref name="right"/>.</returns>
        /// <remarks>This operator runs in O(N+M) time where N = left.Count, M = right.Count.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="left"/> or <paramref name="right"/> is null.</exception>
        public static NormalizedTextSpanCollection Overlap(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right)
        {
            if (left == null)
            {
                throw new ArgumentNullException(nameof(left));
            }

            if (right == null)
            {
                throw new ArgumentNullException(nameof(right));
            }

            if (left.Count == 0)
            {
                return(left);
            }

            if (right.Count == 0)
            {
                return(right);
            }

            OrderedSpanList spans = new OrderedSpanList();

            for (int index1 = 0, index2 = 0; (index1 < left.Count) && (index2 < right.Count);)
            {
                TextSpan span1 = left[index1];
                TextSpan span2 = right[index2];

                if (span1.OverlapsWith(span2))
                {
                    spans.Add(span1.Overlap(span2).Value);
                }

                if (span1.End < span2.End)
                {
                    ++index1;
                }
                else if (span1.End == span2.End)
                {
                    ++index1;
                    ++index2;
                }
                else
                {
                    ++index2;
                }
            }

            return(new NormalizedTextSpanCollection(spans));
        }
        /// <summary>
        /// Finds the union of two span sets.
        /// </summary>
        /// <param name="left">
        /// The first span set.
        /// </param>
        /// <param name="right">
        /// The second span set.
        /// </param>
        /// <returns>
        /// The new span set that corresponds to the union of <paramref name="left"/> and <paramref name="right"/>.
        /// </returns>
        /// <remarks>This operator runs in O(N+M) time where N = left.Count, M = right.Count.</remarks>
        /// <exception cref="ArgumentNullException">Either <paramref name="left"/> or <paramref name="right"/> is null.</exception>
        public static NormalizedTextSpanCollection Union(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right)
        {
            if (left == null)
            {
                throw new ArgumentNullException(nameof(left));
            }

            if (right == null)
            {
                throw new ArgumentNullException(nameof(right));
            }

            if (left.Count == 0)
            {
                return(right);
            }

            if (right.Count == 0)
            {
                return(left);
            }

            OrderedSpanList spans = new OrderedSpanList();

            int index1 = 0;
            int index2 = 0;

            int start = -1;
            int end   = int.MaxValue;

            while ((index1 < left.Count) && (index2 < right.Count))
            {
                TextSpan span1 = left[index1];
                TextSpan span2 = right[index2];

                if (span1.Start < span2.Start)
                {
                    NormalizedTextSpanCollection.UpdateSpanUnion(span1, spans, ref start, ref end);
                    ++index1;
                }
                else
                {
                    NormalizedTextSpanCollection.UpdateSpanUnion(span2, spans, ref start, ref end);
                    ++index2;
                }
            }

            while (index1 < left.Count)
            {
                NormalizedTextSpanCollection.UpdateSpanUnion(left[index1], spans, ref start, ref end);
                ++index1;
            }

            while (index2 < right.Count)
            {
                NormalizedTextSpanCollection.UpdateSpanUnion(right[index2], spans, ref start, ref end);
                ++index2;
            }

            if (end != int.MaxValue)
            {
                spans.Add(TextSpan.FromBounds(start, end));
            }

            return(new NormalizedTextSpanCollection(spans));
        }
        /// <summary>
        /// Determines whether this span set is the same as another object.
        /// </summary>
        /// <param name="obj">The object to test.</param>
        /// <returns><c>true</c> if the two objects are equal, otherwise <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            NormalizedTextSpanCollection set = obj as NormalizedTextSpanCollection;

            return(this == set);
        }
 /// <summary>
 /// Initializes a new instance of <see cref="NormalizedTextSpanCollection"/> that contains the specified list of spans.
 /// </summary>
 /// <param name="spans">The spans to be added.</param>
 /// <remarks>
 /// <para>The list of spans will be sorted and normalized (overlapping and adjoining spans will be combined).</para>
 /// <para>This constructor runs in O(N log N) time, where N = spans.Count.</para></remarks>
 /// <exception cref="ArgumentNullException"><paramref name="spans"/> is null.</exception>
 public NormalizedTextSpanCollection(IEnumerable <TextSpan> spans)
     : base(NormalizedTextSpanCollection.NormalizeSpans(spans))
 {
     // NormalizeSpans will throw if spans == null.
 }
        /// <summary>
        /// Finds the difference between two sets. The difference is defined as everything in the first span set that is not in the second span set.
        /// </summary>
        /// <param name="left">The first span set.</param>
        /// <param name="right">The second span set.</param>
        /// <returns>The new span set that corresponds to the difference between <paramref name="left"/> and <paramref name="right"/>.</returns>
        /// <remarks>
        /// Empty spans in the second set do not affect the first set at all. This method returns empty spans in the first set that are not contained by any set in
        /// the second set.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="left"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="right"/> is null.</exception>
        public static NormalizedTextSpanCollection Difference(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right)
        {
            if (left == null)
            {
                throw new ArgumentNullException(nameof(left));
            }

            if (right == null)
            {
                throw new ArgumentNullException(nameof(right));
            }

            if (left.Count == 0)
            {
                return(left);
            }

            if (right.Count == 0)
            {
                return(left);
            }

            OrderedSpanList spans = new OrderedSpanList();

            int index1  = 0;
            int index2  = 0;
            int lastEnd = -1;

            do
            {
                TextSpan span1 = left[index1];
                TextSpan span2 = right[index2];

                if ((span2.Length == 0) || (span1.Start >= span2.End))
                {
                    ++index2;
                }
                else if (span1.End <= span2.Start)
                {
                    // lastEnd is set to the end of the previously encountered intersecting span
                    // from right when it ended before the end of span1 (so it must still be less
                    // than the end of span1).
                    Debug.Assert(lastEnd < span1.End);
                    spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span1.End));
                    ++index1;
                }
                else
                {
                    // The spans intersect, so add anything from span1 that extends to the left of span2.
                    if (span1.Start < span2.Start)
                    {
                        // lastEnd is set to the end of the previously encountered intersecting span
                        // on span2, so it must be less than the start of the current span on span2.
                        Debug.Assert(lastEnd < span2.Start);
                        spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span2.Start));
                    }

                    if (span1.End < span2.End)
                    {
                        ++index1;
                    }
                    else if (span1.End == span2.End)
                    {
                        // Both spans ended at the same place so we're done with both.
                        ++index1;
                        ++index2;
                    }
                    else
                    {
                        // span2 ends before span1, so keep track of where it ended so that we don't
                        // try to add the excluded portion the next time we add a span.
                        lastEnd = span2.End;
                        ++index2;
                    }
                }
            }while ((index1 < left.Count) && (index2 < right.Count));

            while (index1 < left.Count)
            {
                TextSpan span1 = left[index1++];
                spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span1.End));
            }

            return(new NormalizedTextSpanCollection(spans));
        }