/// <summary>
        /// Performs the NORMALIZE operation, as described in the comments to the general projection function.
        /// Clip begin and end times, normalize by beginTime, scale by speedRatio.
        /// </summary>
        /// <param name="projection">The normalized collection to create.</param>
        /// <param name="beginTime">Begin time of the active period for clipping.</param>
        /// <param name="endTime">End time of the active period for clipping.</param>
        /// <param name="speedRatio">The ratio by which to scale begin and end time.</param>
        /// <param name="includeFillPeriod">Whether a non-zero fill period exists.</param>
        private void ProjectionNormalize(ref TimeIntervalCollection projection,
                                         TimeSpan beginTime, Nullable<TimeSpan> endTime, bool includeFillPeriod, double speedRatio)
        {
            Debug.Assert(!IsEmptyOfRealPoints);
            Debug.Assert(projection.IsEmpty);
            
            projection.EnsureAllocatedCapacity(this._nodeTime.Length);

            this.MoveFirst();
            projection.MoveFirst();

            // Get to the non-clipped zone; we must overlap the active zone, so we should terminate at some point.
            while (!CurrentIsAtLastNode && NextNodeTime <= beginTime)
            {
                MoveNext();
            }

            if (CurrentNodeTime < beginTime)  // This means we have an interval clipped by beginTime
            {
                if (CurrentNodeIsInterval)
                {
                    projection._count++;
                    projection.CurrentNodeTime = TimeSpan.Zero;
                    projection.CurrentNodeIsPoint = true;
                    projection.CurrentNodeIsInterval = true;
                    projection.MoveNext();
                }
                this.MoveNext();
            }

            while(_current < _count && (!endTime.HasValue || CurrentNodeTime < endTime))  // Copy the main set of segments, transforming them
            {
                double timeOffset = (double)((this.CurrentNodeTime - beginTime).Ticks);
                
                projection._count++;
                projection.CurrentNodeTime = TimeSpan.FromTicks((long)(speedRatio * timeOffset));
                projection.CurrentNodeIsPoint = this.CurrentNodeIsPoint;
                projection.CurrentNodeIsInterval = this.CurrentNodeIsInterval;

                projection.MoveNext();
                this.MoveNext();
            }

            Debug.Assert(_current > 0);  // The only way _current could stay at zero is if the collection begins at (or past) the end of active period
            if (_current < _count  // We have an interval reaching beyond the active zone, clip that interval
             && (_nodeIsInterval[_current - 1]
              || (CurrentNodeTime == endTime.Value && CurrentNodeIsPoint && includeFillPeriod)))
            {
                Debug.Assert(endTime.HasValue && CurrentNodeTime >= endTime.Value);

                double timeOffset = (double)((endTime.Value - beginTime).Ticks);

                projection._count++;
                projection.CurrentNodeTime = TimeSpan.FromTicks((long)(speedRatio * timeOffset));
                projection.CurrentNodeIsPoint = includeFillPeriod && (CurrentNodeTime > endTime.Value || CurrentNodeIsPoint);
                projection.CurrentNodeIsInterval = false;
            }
        }
        /// <returns>
        /// Returns a collection which is the projection of this collection onto the defined periodic function.
        /// </returns>
        /// <remarks>
        /// The object on which this method is called is a timeline's parent's collection of intervals.
        /// The periodic collection passed via parameters describes the active/fill periods of the timeline.
        /// The output is the projection of (this) object using the parameter function of the timeline.
        /// 
        /// We assume this function is ONLY called when this collection overlaps the active zone.
        /// 
        /// The periodic function maps values from domain to range within its activation period of [beginTime, endTime);
        /// in the fill period [endTime, endTime+fillDuration) everything maps to a constant post-fill value, and outside of
        /// those periods every value maps to null.
        /// 
        /// The projection process can be described as three major steps:
        /// 
        /// (1) NORMALIZE this collection: offset the TIC's coordinates by BeginTime and scale by SpeedRatio.
        /// 
        /// (2) FOLD this collection.  This means we convert from parent-time coordinate space into the space of
        ///     a single simpleDuration for the child.  This is equivalent to "cutting up" the parent TIC into
        ///     equal-length segments (of length Period) and overlapping them -- taking their union.  This lets us
        ///     know exactly which values inside the simpleDuration we have reached on the child.  In the case of
        ///     autoreversed timelines, we do the folding similiar to folding a strip of paper -- alternating direction.
        /// 
        /// (3) WARP the resulting collection.  We now convert from simpleDuration domain coordinates into
        ///     coordinates in the range of the timeline function.  We do this by applying the "warping" effects of
        ///     acceleration, and deceleration.
        /// 
        /// In the special case of infinite simple duration, we essentially are done after performing NORMALIZE,
        /// because no periodicity or acceleration is present.
        /// 
        /// In the ultimate degenerate case of zero duration, we terminate early and project the zero point.
        /// 
        /// </remarks>
        /// <param name="projection">An empty output projection, passed by reference to allow TIC reuse.</param>
        /// <param name="beginTime">Begin time of the periodic function.</param>
        /// <param name="endTime">The end (expiration) time of the periodic function.  Null indicates positive infinity.</param>
        /// <param name="fillDuration">The fill time appended at the end of the periodic function.  Zero indicates no fill period.  Forever indicates infinite fill period.</param>
        /// <param name="period">Length of a single iteration in the periodic collection.</param>
        /// <param name="appliedSpeedRatio">Ratio by which to scale down the periodic collection.</param>
        /// <param name="accelRatio">Ratio of the length of the accelerating portion of the iteration.</param>
        /// <param name="decelRatio">Ratio of the length of the decelerating portion of the iteration.</param>
        /// <param name="isAutoReversed">Indicates whether reversed arcs should follow after forward arcs.</param>
        internal void ProjectOntoPeriodicFunction(ref TimeIntervalCollection projection,
                                                  TimeSpan beginTime, Nullable<TimeSpan> endTime,
                                                  Duration fillDuration, Duration period,
                                                  double appliedSpeedRatio, double accelRatio, double decelRatio,
                                                  bool isAutoReversed)
        {
            Debug.Assert(projection.IsEmpty);
            Debug.Assert(!_invertCollection);  // Make sure we never leave inverted mode enabled
            Debug.Assert(!endTime.HasValue || beginTime <= endTime);     // Ensure legitimate begin/end clipping parameters

            Debug.Assert(!IsEmptyOfRealPoints);  // We assume this function is ONLY called when this collection overlaps the active zone.  So we cannot be empty.
            Debug.Assert(!endTime.HasValue || endTime >= _nodeTime[0]);  // EndTime must come at or after our first node (it can be infinite)
            Debug.Assert(_nodeTime[_count - 1] >= beginTime);  // Our last node must come at least at begin time (since we must intersect the active period)
            Debug.Assert(endTime.HasValue || fillDuration == TimeSpan.Zero);   // Either endTime is finite, or it's infinite hence we cannot have any fill zone
            Debug.Assert(!period.HasTimeSpan || period.TimeSpan > TimeSpan.Zero || (endTime.HasValue && beginTime == endTime));  // Check the consistency of degenerate case where simple duration is zero; expiration time should equal beginTime
            Debug.Assert(!_nodeIsInterval[_count - 1]);  // We should not have an infinite domain set

            // We initially project all intervals into a single period of the timeline, creating a union of the projected segments.
            // Then we warp the time coordinates of the resulting TIC from domain to range, applying the effects of speed/accel/decel

            bool nullPoint = _containsNullPoint  // Start by projecting the null point directly, then check whether we fall anywhere outside of the active and fill period

             || _nodeTime[0] < beginTime  // If we intersect space before beginTime, or...

              || (endTime.HasValue && fillDuration.HasTimeSpan  // ...the active and fill periods don't stretch forever, and...
               && (_nodeTime[_count - 1] > endTime.Value + fillDuration.TimeSpan  // ...we intersect space after endTime+fill, or...
                || (_nodeTime[_count - 1] == endTime.Value + fillDuration.TimeSpan  // ...as we fall right onto the end of fill zone...
                 && _nodeIsPoint[_count - 1] && (endTime > beginTime || fillDuration.TimeSpan > TimeSpan.Zero))));  // ...we may have a point intersection with the stopped zone

            // Now consider the main scenarios:

            if (endTime.HasValue && beginTime == endTime)  // Degenerate case when our active period is a single point; project only the point
            {
                projection.InitializePoint(TimeSpan.Zero);
            }
            else  // The case of non-zero active duration
            {
                bool includeFillPeriod = !fillDuration.HasTimeSpan || fillDuration.TimeSpan > TimeSpan.Zero;  // This variable represents whether we have a non-zero fill zone

                if (period.HasTimeSpan)  // We have a finite TimeSpan period and non-zero activation duration
                {
                    TimeIntervalCollection tempCollection = new TimeIntervalCollection();

                    ProjectionNormalize(ref tempCollection, beginTime, endTime, includeFillPeriod, appliedSpeedRatio);

                    long periodInTicks = period.TimeSpan.Ticks;
                    Nullable<TimeSpan> activeDuration;
                    bool includeMaxPoint;

                    if (endTime.HasValue)
                    {
                        activeDuration = endTime.Value - beginTime;
                        includeMaxPoint = includeFillPeriod && (activeDuration.Value.Ticks % periodInTicks == 0);  // Fill starts at a boundary
                    }
                    else
                    {
                        activeDuration = null;
                        includeMaxPoint = false;
                    }

                    projection.EnsureAllocatedCapacity(_minimumCapacity);
                    tempCollection.ProjectionFold(ref projection, activeDuration, periodInTicks, isAutoReversed, includeMaxPoint);

                    if (accelRatio + decelRatio > 0)
                    {
                        projection.ProjectionWarp(periodInTicks, accelRatio, decelRatio);
                    }
                }
                else  // Infinite period degenerate case; we perform straight 1-1 linear mapping, offset by begin time and clipped
                {
                    ProjectionNormalize(ref projection, beginTime, endTime, includeFillPeriod, appliedSpeedRatio);
                }
            }

            projection._containsNullPoint = nullPoint;  // Ensure we have the null point properly set
        }