// 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.Collections.Generic;
using System.Xml.XPath;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;

namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// Merges several doc-order-distinct sequences into a single doc-order-distinct sequence.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct DodSequenceMerge
    {
        private IList<XPathNavigator> _firstSequence;
        private List<IEnumerator<XPathNavigator>> _sequencesToMerge;
        private int _nodeCount;
        private XmlQueryRuntime _runtime;

        /// <summary>
        /// Initialize this instance of DodSequenceMerge.
        /// </summary>
        public void Create(XmlQueryRuntime runtime)
        {
            _firstSequence = null;
            _sequencesToMerge = null;
            _nodeCount = 0;
            _runtime = runtime;
        }

        /// <summary>
        /// Add a new sequence to the list of sequences to merge.
        /// </summary>
        public void AddSequence(IList<XPathNavigator> sequence)
        {
            // Ignore empty sequences
            if (sequence.Count == 0)
                return;

            if (_firstSequence == null)
            {
                _firstSequence = sequence;
            }
            else
            {
                if (_sequencesToMerge == null)
                {
                    _sequencesToMerge = new List<IEnumerator<XPathNavigator>>();
                    MoveAndInsertSequence(_firstSequence.GetEnumerator());
                    _nodeCount = _firstSequence.Count;
                }

                MoveAndInsertSequence(sequence.GetEnumerator());
                _nodeCount += sequence.Count;
            }
        }

        /// <summary>
        /// Return the fully merged sequence.
        /// </summary>
        public IList<XPathNavigator> MergeSequences()
        {
            XmlQueryNodeSequence newSequence;

            // Zero sequences to merge
            if (_firstSequence == null)
                return XmlQueryNodeSequence.Empty;

            // One sequence to merge
            if (_sequencesToMerge == null || _sequencesToMerge.Count <= 1)
                return _firstSequence;

            // Two or more sequences to merge
            newSequence = new XmlQueryNodeSequence(_nodeCount);

            while (_sequencesToMerge.Count != 1)
            {
                // Save last item in list in temp variable, and remove it from list
                IEnumerator<XPathNavigator> sequence = _sequencesToMerge[_sequencesToMerge.Count - 1];
                _sequencesToMerge.RemoveAt(_sequencesToMerge.Count - 1);

                // Add current node to merged sequence
                newSequence.Add(sequence.Current);

                // Now move to the next node, and re-insert it into the list in reverse document order
                MoveAndInsertSequence(sequence);
            }

            // Add nodes in remaining sequence to end of list
            Debug.Assert(_sequencesToMerge.Count == 1, "While loop should terminate when count == 1");
            do
            {
                newSequence.Add(_sequencesToMerge[0].Current);
            }
            while (_sequencesToMerge[0].MoveNext());

            return newSequence;
        }

        /// <summary>
        /// Move to the next item in the sequence.  If there is no next item, then do not
        /// insert the sequence.  Otherwise, call InsertSequence.
        /// </summary>
        private void MoveAndInsertSequence(IEnumerator<XPathNavigator> sequence)
        {
            if (sequence.MoveNext())
                InsertSequence(sequence);
        }

        /// <summary>
        /// Insert the specified sequence into the list of sequences to be merged.
        /// Insert it in reverse document order with respect to the current nodes in other sequences.
        /// </summary>
        private void InsertSequence(IEnumerator<XPathNavigator> sequence)
        {
            for (int i = _sequencesToMerge.Count - 1; i >= 0; i--)
            {
                int cmp = _runtime.ComparePosition(sequence.Current, _sequencesToMerge[i].Current);

                if (cmp == -1)
                {
                    // Insert after current item
                    _sequencesToMerge.Insert(i + 1, sequence);
                    return;
                }
                else if (cmp == 0)
                {
                    // Found duplicate, so skip the duplicate
                    if (!sequence.MoveNext())
                    {
                        // No more nodes, so don't insert anything
                        return;
                    }

                    // Next node must be after current node in document order, so don't need to reset loop
                }
            }

            // Insert at beginning of list
            _sequencesToMerge.Insert(0, sequence);
        }
    }
}

