// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;

namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// Set iterators (Union, Intersection, Difference) that use containment to control two nested iterators return
    /// one of the following values from MoveNext().
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public enum SetIteratorResult
    {
        NoMoreNodes,                // Iteration is complete; there are no more nodes
        InitRightIterator,          // Initialize right nested iterator
        NeedLeftNode,               // The next node needs to be fetched from the left nested iterator
        NeedRightNode,              // The next node needs to be fetched from the right nested iterator
        HaveCurrentNode,            // This iterator's Current property is set to the next node in the iteration
    };


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// Using a merge sort, this operator returns the union of these sets in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct UnionIterator
    {
        private XmlQueryRuntime _runtime;
        private XPathNavigator _navCurr, _navOther;
        private IteratorState _state;

        private enum IteratorState
        {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            LeftIsCurrent,
            RightIsCurrent,
        };

        /// <summary>
        /// Create SetIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime)
        {
            _runtime = runtime;
            _state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator)
        {
            switch (_state)
            {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    _navOther = nestedNavigator;
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    _navCurr = nestedNavigator;
                    _state = IteratorState.LeftIsCurrent;
                    break;

                case IteratorState.NeedRight:
                    _navCurr = nestedNavigator;
                    _state = IteratorState.RightIsCurrent;
                    break;

                case IteratorState.LeftIsCurrent:
                    // Just returned left node as current, so get new left
                    _state = IteratorState.NeedLeft;
                    return SetIteratorResult.NeedLeftNode;

                case IteratorState.RightIsCurrent:
                    // Just returned right node as current, so get new right
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
            }

            // Merge left and right nodes
            if (_navCurr == null)
            {
                // If both navCurr and navOther are null, then iteration is complete
                if (_navOther == null)
                    return SetIteratorResult.NoMoreNodes;

                Swap();
            }
            else if (_navOther != null)
            {
                int order = _runtime.ComparePosition(_navOther, _navCurr);

                // If navCurr is positioned to same node as navOther,
                if (order == 0)
                {
                    // Skip navCurr, since it is a duplicate
                    if (_state == IteratorState.LeftIsCurrent)
                    {
                        _state = IteratorState.NeedLeft;
                        return SetIteratorResult.NeedLeftNode;
                    }

                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
                }

                // If navOther is before navCurr in document order, then swap navCurr with navOther
                if (order < 0)
                    Swap();
            }

            // Return navCurr
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurr; }
        }

        /// <summary>
        /// Swap navCurr with navOther and invert state to reflect the change.
        /// </summary>
        private void Swap()
        {
            XPathNavigator navTemp = _navCurr;
            _navCurr = _navOther;
            _navOther = navTemp;

            if (_state == IteratorState.LeftIsCurrent)
                _state = IteratorState.RightIsCurrent;
            else
                _state = IteratorState.LeftIsCurrent;
        }
    }


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// This iterator returns the intersection of these sets in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct IntersectIterator
    {
        private XmlQueryRuntime _runtime;
        private XPathNavigator _navLeft, _navRight;
        private IteratorState _state;

        private enum IteratorState
        {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            NeedLeftAndRight,
            HaveCurrent,
        };

        /// <summary>
        /// Create IntersectIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime)
        {
            _runtime = runtime;
            _state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator)
        {
            int order;

            switch (_state)
            {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    _navLeft = nestedNavigator;
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    _navLeft = nestedNavigator;
                    break;

                case IteratorState.NeedRight:
                    _navRight = nestedNavigator;
                    break;

                case IteratorState.NeedLeftAndRight:
                    // After fetching left node, still need right node
                    _navLeft = nestedNavigator;
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;

                case IteratorState.HaveCurrent:
                    // Just returned left node as current, so fetch new left and right nodes
                    Debug.Assert(nestedNavigator == null, "null is passed to MoveNext after IteratorState.HaveCurrent has been returned.");
                    _state = IteratorState.NeedLeftAndRight;
                    return SetIteratorResult.NeedLeftNode;
            }

            if (_navLeft == null || _navRight == null)
            {
                // No more nodes from either left or right iterator (or both), so iteration is complete
                return SetIteratorResult.NoMoreNodes;
            }

            // Intersect left and right sets
            order = _runtime.ComparePosition(_navLeft, _navRight);

            if (order < 0)
            {
                // If navLeft is positioned to a node that is before navRight, skip left node
                _state = IteratorState.NeedLeft;
                return SetIteratorResult.NeedLeftNode;
            }
            else if (order > 0)
            {
                // If navLeft is positioned to a node that is after navRight, so skip right node
                _state = IteratorState.NeedRight;
                return SetIteratorResult.NeedRightNode;
            }

            // Otherwise, navLeft is positioned to the same node as navRight, so found one item in the intersection
            _state = IteratorState.HaveCurrent;
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navLeft; }
        }
    }


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// This iterator returns the difference of these sets (Left - Right) in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct DifferenceIterator
    {
        private XmlQueryRuntime _runtime;
        private XPathNavigator _navLeft, _navRight;
        private IteratorState _state;

        private enum IteratorState
        {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            NeedLeftAndRight,
            HaveCurrent,
        };

        /// <summary>
        /// Create DifferenceIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime)
        {
            _runtime = runtime;
            _state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator)
        {
            switch (_state)
            {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    _navLeft = nestedNavigator;
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    _navLeft = nestedNavigator;
                    break;

                case IteratorState.NeedRight:
                    _navRight = nestedNavigator;
                    break;

                case IteratorState.NeedLeftAndRight:
                    // After fetching left node, still need right node
                    _navLeft = nestedNavigator;
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;

                case IteratorState.HaveCurrent:
                    // Just returned left node as current, so fetch new left node
                    Debug.Assert(nestedNavigator == null, "null is passed to MoveNext after IteratorState.HaveCurrent has been returned.");
                    _state = IteratorState.NeedLeft;
                    return SetIteratorResult.NeedLeftNode;
            }

            if (_navLeft == null)
            {
                // If navLeft is null, then difference operation is complete
                return SetIteratorResult.NoMoreNodes;
            }
            else if (_navRight != null)
            {
                int order = _runtime.ComparePosition(_navLeft, _navRight);

                // If navLeft is positioned to same node as navRight,
                if (order == 0)
                {
                    // Skip navLeft and navRight
                    _state = IteratorState.NeedLeftAndRight;
                    return SetIteratorResult.NeedLeftNode;
                }

                // If navLeft is after navRight in document order, then skip navRight
                if (order > 0)
                {
                    _state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
                }
            }

            // Return navLeft
            _state = IteratorState.HaveCurrent;
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navLeft; }
        }
    }
}
