/// <summary>
        /// Take a single projection segment [CurrentNodeTime, NextNodeTime], break it into parts and merge the
        /// folded parts into this collection.
        /// NOTE: the TIC is normalized so beginTime = TimeSpan.Zero and we are already clipped.
        /// NOTE: projection should have allocated arrays.
        /// </summary>
        /// <param name="projection">The output projection.</param>
        /// <param name="activeDuration">The duration of the active period.</param>
        /// <param name="periodInTicks">The length of a simple duration in ticks.</param>
        /// <param name="isAutoReversed">Whether autoreversing is enabled</param>
        /// <param name="includeMaxPoint">Whether the fill zone forces the max point to be included.</param>
        private bool ProjectionFoldInterval(ref TimeIntervalCollection projection, Nullable<TimeSpan> activeDuration,
                                            long periodInTicks, bool isAutoReversed, bool includeMaxPoint)
        {
            // Project the begin point for the segment, then look if we are autoreversing or not.
            long intervalLength = (NextNodeTime - CurrentNodeTime).Ticks;
            long timeBeforeNextPeriod, currentProjection;

            // Now see how the segment falls across periodic boundaries:
            // Case 1: segment stretches across a full period (we can exit early, since we cover the entire range of values)
            // Case 2: NON-AUTEREVERSED: segment stretches across two partial periods (we need to split into two segments and insert them into the projection)
            // Case 2: AUTOREVERSED: we need to pick the larger half of the partial period and project only that half, since it fully overlaps the other.
            // Case 3: segment is fully contained within a single period (just add the segment into the projection)
            // These cases are handled very differently for AutoReversing and non-AutoReversing timelines.

            if (isAutoReversed)  // In the autoreversed case, we "fold" the segment onto itself and eliminate the redundant parts
            {
                bool beginOnReversingArc;
                long doublePeriod = periodInTicks << 1;
                currentProjection = CurrentNodeTime.Ticks % doublePeriod;

                if (currentProjection < periodInTicks)  // We are on a forward-moving segment
                {
                    beginOnReversingArc = false;
                    timeBeforeNextPeriod = periodInTicks - currentProjection;
                }
                else  // We are on a reversing segment, adjust the values accordingly
                {
                    beginOnReversingArc = true;
                    currentProjection = doublePeriod - currentProjection;
                    timeBeforeNextPeriod = currentProjection;
                }

                Debug.Assert(timeBeforeNextPeriod > 0);

                long timeAfterNextPeriod = intervalLength - timeBeforeNextPeriod;  // How much of our interval protrudes into the next period(s); this may be negative if we don't reach it.
                // See which part of the segment -- before or after part -- "dominates" when we fold them unto each other.
                if (timeAfterNextPeriod > 0)  // Case 1 or 2: we reach into the next period but don't know if we completely cover it
                {
                    bool collectionIsSaturated;
                        
                    if (timeBeforeNextPeriod >= timeAfterNextPeriod)  // Before "dominates"
                    {
                        bool includeTime = CurrentNodeIsPoint;

                        if (timeBeforeNextPeriod == timeAfterNextPeriod)  // Corner case where before and after overlap exactly, find the IsPoint union
                        {
                            includeTime = includeTime || NextNodeIsPoint;
                        }

                        if (beginOnReversingArc)
                        {
                            projection.MergeInterval(TimeSpan.Zero,                         true,
                                                     TimeSpan.FromTicks(currentProjection), includeTime);
                            collectionIsSaturated = includeTime && (currentProjection == periodInTicks);
                        }
                        else
                        {
                            projection.MergeInterval(TimeSpan.FromTicks(currentProjection), includeTime,
                                                     TimeSpan.FromTicks(periodInTicks),     true);
                            collectionIsSaturated = includeTime && (currentProjection == 0);
                        }
                    }
                    else  // After "dominates"
                    {
                        if (beginOnReversingArc)
                        {
                            long clippedTime = timeAfterNextPeriod < periodInTicks ? timeAfterNextPeriod : periodInTicks;
                            
                            projection.MergeInterval(TimeSpan.Zero,                   true,
                                                     TimeSpan.FromTicks(clippedTime), NextNodeIsPoint);
                            collectionIsSaturated = NextNodeIsPoint && (clippedTime == periodInTicks);
                        }
                        else
                        {
                            long clippedTime = timeAfterNextPeriod < periodInTicks ? periodInTicks - timeAfterNextPeriod : 0;

                            projection.MergeInterval(TimeSpan.FromTicks(clippedTime),   NextNodeIsPoint,
                                                     TimeSpan.FromTicks(periodInTicks), true);
                            collectionIsSaturated = NextNodeIsPoint && (clippedTime == 0);
                        }
                    }
                    return collectionIsSaturated;  // See if we just saturated the collection
                }
                else  // Case 3: timeAfterNextPeriod < 0, we are fully contained in the current period
                {
                    // No need to split anything, insert the interval directly
                    if (beginOnReversingArc)  // Here the nodes are reversed
                    {
                        projection.MergeInterval(TimeSpan.FromTicks(currentProjection - intervalLength), NextNodeIsPoint,
                                                 TimeSpan.FromTicks(currentProjection),                  CurrentNodeIsPoint);
                    }
                    else
                    {
                        projection.MergeInterval(TimeSpan.FromTicks(currentProjection),                  CurrentNodeIsPoint,
                                                 TimeSpan.FromTicks(currentProjection + intervalLength), NextNodeIsPoint);
                    }
                    return false;  // Keep computing the projection
                }
            }
            else  // No AutoReversing
            {                
                currentProjection = CurrentNodeTime.Ticks % periodInTicks;
                timeBeforeNextPeriod = periodInTicks - currentProjection;

                // The only way to get 0 is if we clipped by endTime which equals CurrentNodeTime, which should not have been allowed
                Debug.Assert(intervalLength > 0);

                if (intervalLength > periodInTicks)  // Case 1. We may stretch across a whole arc, even if we start from the end and wrap back around
                {
                    // Quickly transform the collection into a saturated collection
                    projection._nodeTime[0] = TimeSpan.Zero;
                    projection._nodeIsPoint[0] = true;
                    projection._nodeIsInterval[0] = true;

                    projection._nodeTime[1] = TimeSpan.FromTicks(periodInTicks);
                    projection._nodeIsPoint[1] = includeMaxPoint;
                    projection._nodeIsInterval[1] = false;

                    _count = 2;
                    return true;  // Bail early, we have the result ready
                }
                else if (intervalLength >= timeBeforeNextPeriod)  // Case 2. We stretch until the next period begins (but not long enough to cover the length of a full period)
                {
                    // Split the segment into two projected segments by wrapping around the period boundary
                    projection.MergeInterval(TimeSpan.FromTicks(currentProjection),                     CurrentNodeIsPoint,
                                             TimeSpan.FromTicks(periodInTicks),                         false);
                    if (intervalLength > timeBeforeNextPeriod)  // See if we have a legitimate interval in the second clipped part
                    {
                        projection.MergeInterval(TimeSpan.Zero,                                             true,
                                                 TimeSpan.FromTicks(intervalLength - timeBeforeNextPeriod), NextNodeIsPoint);
                    }
                    else if (NextNodeIsPoint)  // We only seem to have a point, wrapped around at zero (or in the exceptional case, at the max)
                    {
                        if (includeMaxPoint && activeDuration.HasValue && NextNodeTime == activeDuration)  // Exceptional end case: we are exactly at the last point
                        {
                            projection.MergePoint(TimeSpan.FromTicks(periodInTicks));
                        }
                        else
                        {
                            projection.MergePoint(TimeSpan.Zero);
                        }
                    }
                    return false;  // Keep computing the projection
                }
                else  // Case 3: We fall within a single period
                {
                    // No need to split anything, insert the interval directly
                    projection.MergeInterval(TimeSpan.FromTicks(currentProjection),                    CurrentNodeIsPoint,
                                             TimeSpan.FromTicks(currentProjection + intervalLength),   NextNodeIsPoint);
                    return false;  // Keep computing the projection
                }
            }
        }
        /// <summary>
        /// Take a single projection point and insert into the output collection.
        /// NOTE: projection should have allocated arrays.
        /// </summary>
        /// <param name="projection">The output collection.</param>
        /// <param name="activeDuration">The duration of the active period.</param>
        /// <param name="periodInTicks">The length of a simple duration in ticks.</param>
        /// <param name="isAutoReversed">Whether autoreversing is enabled</param>
        /// <param name="includeMaxPoint">Whether the fill zone forces the max point to be included.</param>
        private void ProjectionFoldPoint(ref TimeIntervalCollection projection, Nullable<TimeSpan> activeDuration,
                                         long periodInTicks, bool isAutoReversed, bool includeMaxPoint)
        {
            Debug.Assert(CurrentNodeIsPoint);  // We should only call this method when we project a legitimate point
            Debug.Assert(!CurrentNodeIsInterval);

            long currentProjection;
            if (isAutoReversed)  // Take autoreversing into account
            {
                long doublePeriod = periodInTicks << 1;
                currentProjection = CurrentNodeTime.Ticks % doublePeriod;

                if (currentProjection > periodInTicks)
                {
                    currentProjection = doublePeriod - currentProjection;
                }
            }
            else  // No autoReversing
            {
                if (includeMaxPoint && activeDuration.HasValue && CurrentNodeTime == activeDuration)
                {
                    currentProjection = periodInTicks;  // Exceptional end case: we are exactly at the last point
                }
                else
                {
                    currentProjection = CurrentNodeTime.Ticks % periodInTicks;
                }
            }

            projection.MergePoint(TimeSpan.FromTicks(currentProjection));
        }