/// <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);
            }

            var spans = new OrderedSpanList();

            var index1 = 0;
            var index2 = 0;

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

            while (index1 < left.Count && index2 < right.Count)
            {
                var span1 = left[index1];
                var 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>
 /// Private constructor for use when the span list is already normalized.
 /// </summary>
 /// <param name="normalizedSpans">An already normalized span list.</param>
 private NormalizedTextSpanCollection(OrderedSpanList normalizedSpans)
     : base(normalizedSpans)
 {
 }
        /// <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);
            }

            var spans = new OrderedSpanList();

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

            do
            {
                var span1 = left[index1];
                var 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)
            {
                var span1 = left[index1++];
                spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span1.End));
            }

            return(new NormalizedTextSpanCollection(spans));
        }