// Returns true if we know at this point whether an intersection is possible between tic1 and tic2
        // The fact of whether an intersection was found is stored in the ref parameter intersectionFound
        static private bool IntersectsHelperEqualCase(ref TimeIntervalCollection tic1, ref TimeIntervalCollection tic2, ref bool intersectionFound)
        {
            // If the nodes match exactly, check if the points are both included, or if the intervals are both included
            if ((tic1.CurrentNodeIsPoint && tic2.CurrentNodeIsPoint) ||
                (tic1.CurrentNodeIsInterval && tic2.CurrentNodeIsInterval))
            {
                intersectionFound = true;
                return true;
            }
            // We did not find an intersection, but advance whichever index has a closer next node
            else if (!tic1.CurrentIsAtLastNode && (
                tic2.CurrentIsAtLastNode || (tic1.NextNodeTime < tic2.NextNodeTime)))
            {
                tic1.MoveNext();
            }
            else if (!tic2.CurrentIsAtLastNode && (
                     tic1.CurrentIsAtLastNode || (tic2.NextNodeTime < tic1.NextNodeTime)))
            {
                tic2.MoveNext();
            }
            else if (!tic1.CurrentIsAtLastNode && !tic2.CurrentIsAtLastNode)
            {
                // If both indices have room to advance, and we haven't yet advanced either one, it must be the next nodes are also exactly equal
                Debug.Assert(tic1.NextNodeTime == tic2.NextNodeTime);

                // It is necessary to advance both indices simultaneously, otherwise we break our invariant - one will be too far ahead
                tic1.MoveNext();
                tic2.MoveNext();
            }
            else  // The only way we could get here is if both indices are pointing to the last nodes
            {
                Debug.Assert(tic1.CurrentIsAtLastNode && tic2.CurrentIsAtLastNode);

                // We have exhausted all the nodes and not found an intersection; bail
                intersectionFound = false;
                return true;
            }

            // Enforce our invariant: neither index gets too far ahead of the other.
            Debug.Assert(tic2.CurrentIsAtLastNode || (tic1.CurrentNodeTime < tic2.NextNodeTime));
            Debug.Assert(tic1.CurrentIsAtLastNode || (tic2.CurrentNodeTime < tic1.NextNodeTime));

            // Tell the main algorithm to continue working
            return false;
        }
        // Make sure the indexers are starting next to each other
        static private void IntersectsHelperPrepareIndexers(ref TimeIntervalCollection tic1, ref TimeIntervalCollection tic2)
        {
            Debug.Assert(!tic1.IsEmptyOfRealPoints);  // We shouldn't reach here if either TIC is empty
            Debug.Assert(!tic2.IsEmptyOfRealPoints);

            tic1.MoveFirst();  // Point _current to the first node in both TICs
            tic2.MoveFirst();

            // First bring tic1._current and tic2._current within an interval of each other
            if (tic1.CurrentNodeTime < tic2.CurrentNodeTime)
            {
                // Keep advancing tic1._current as far as possible while keeping _nodeTime[tic1._current] < _nodeTime[tic2._current]
                while (!tic1.CurrentIsAtLastNode && (tic1.NextNodeTime <= tic2.CurrentNodeTime))
                {
                    tic1.MoveNext();
                }
            }
            else if (tic2.CurrentNodeTime < tic1.CurrentNodeTime)
            {
                // Keep advancing tic2._current as far as possible while keeping _nodeTime[tic1._current] > _nodeTime[tic2._current]
                while (!tic2.CurrentIsAtLastNode && (tic2.NextNodeTime <= tic1.CurrentNodeTime))
                {
                    tic2.MoveNext();
                }
            }
        }
        // Returns true if we know at this point whether an intersection is possible between tic1 and tic2
        // The fact of whether an intersection was found is stored in the ref parameter intersectionFound
        static private bool IntersectsHelperUnequalCase(ref TimeIntervalCollection tic1, ref TimeIntervalCollection tic2, ref bool intersectionFound)
        {
            Debug.Assert(!intersectionFound);  // If an intersection was already found, we should not reach this far

            if (tic1.CurrentNodeIsInterval)  // If we are within an interval in tic1, we immediately have an intersection
            {
                // If we have gotten into this method, tic1._current comes earlier than does tic2._current;
                // Suppose the following assert is false; then by Rule #2A, tic2's previous interval must be included;
                // If this was the case, then tic2's previous interval overlapped tic1's current interval.  Since it's
                // included, we would have encountered an intersection before even reaching this method!  Then you
                // should not even be here now.  Else suppose we are at tic2's first node, then the below Assert
                // follows directly from Rule #3.
                Debug.Assert(tic2.CurrentNodeIsPoint || tic2.CurrentNodeIsInterval);

                intersectionFound = true;
                return true;
            }
            else if (tic1.CurrentIsAtLastNode)  // // If we are already at the end of tic1, we ran out of nodes that may have an intersection
            {
                intersectionFound = false;
                return true;
            }
            else  // Else we are inside a non-included interval in tic1, no intersection is possible, but keep advancing tic2._current
            {
                while (!tic2.CurrentIsAtLastNode && (tic2.NextNodeTime <= tic1.NextNodeTime))
                {
                    tic2.MoveNext();
                }

                // If nextNodeTime1 is null, we should never get here because the IF statement would have caught it and quit
                Debug.Assert(!tic1.CurrentIsAtLastNode);  // Thus tic1._current can be safely advanced now

                // Now tic1._current can be safely advanced forward
                tic1.MoveNext();

                // If we broke out of Case I, its conditional should no longer hold true:
                Debug.Assert(tic1.CurrentNodeTime >= tic2.CurrentNodeTime);

                // Enforce our invariant: neither index gets too far ahead of the other.
                Debug.Assert(tic2.CurrentIsAtLastNode || (tic1.CurrentNodeTime < tic2.NextNodeTime));
                Debug.Assert(tic1.CurrentIsAtLastNode || (tic2.CurrentNodeTime < tic1.NextNodeTime));

                // Tell the main algorithm to continue working
                return false;
            }
        }
        /// <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;
            }
        }