using Lucene.Net.Support;
using Lucene.Net.Util;
using System;
using System.IO;

namespace Lucene.Net.Search
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
    using Entry = Lucene.Net.Search.FieldValueHitQueue.Entry;

    /// <summary>
    /// A <see cref="ICollector"/> that sorts by <see cref="SortField"/> using
    /// <see cref="FieldComparer"/>s.
    /// <para/>
    /// See the <see cref="Create(Lucene.Net.Search.Sort, int, bool, bool, bool, bool)"/> method
    /// for instantiating a <see cref="TopFieldCollector"/>.
    /// <para/>
    /// @lucene.experimental
    /// </summary>
    public abstract class TopFieldCollector : TopDocsCollector<Entry>
    {
        // TODO: one optimization we could do is to pre-fill
        // the queue with sentinel value that guaranteed to
        // always compare lower than a real hit; this would
        // save having to check queueFull on each insert

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, without
        /// tracking document scores and maxScore.
        /// </summary>
        private class OneComparerNonScoringCollector : TopFieldCollector
        {
            internal FieldComparer comparer;
            internal readonly int reverseMul;
            internal readonly FieldValueHitQueue<Entry> queue;

            public OneComparerNonScoringCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
                this.queue = queue;
                comparer = queue.Comparers[0];
                reverseMul = queue.ReverseMul[0];
            }

            internal void UpdateBottom(int doc)
            {
                // bottom.score is already set to Float.NaN in add().
                bottom.Doc = docBase + doc;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    if ((reverseMul * comparer.CompareBottom(doc)) <= 0)
                    {
                        // since docs are visited in doc Id order, if compare is 0, it means
                        // this document is larger than anything else in the queue, and
                        // therefore not competitive.
                        return;
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, float.NaN);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override void SetNextReader(AtomicReaderContext context)
            {
                this.docBase = context.DocBase;
                queue.SetComparer(0, comparer.SetNextReader(context));
                comparer = queue.FirstComparer;
            }
            
            public override void SetScorer(Scorer scorer)
            {
                comparer.SetScorer(scorer);
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, without
        /// tracking document scores and maxScore, and assumes out of orderness in doc
        /// Ids collection.
        /// </summary>
        private class OutOfOrderOneComparerNonScoringCollector : OneComparerNonScoringCollector
        {
            public OutOfOrderOneComparerNonScoringCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    int cmp = reverseMul * comparer.CompareBottom(doc);
                    if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.Doc))
                    {
                        return;
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, float.NaN);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, while tracking
        /// document scores but no maxScore.
        /// </summary>
        private class OneComparerScoringNoMaxScoreCollector : OneComparerNonScoringCollector
        {
            internal Scorer scorer;

            public OneComparerScoringNoMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            internal void UpdateBottom(int doc, float score)
            {
                bottom.Doc = docBase + doc;
                bottom.Score = score;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    if ((reverseMul * comparer.CompareBottom(doc)) <= 0)
                    {
                        // since docs are visited in doc Id order, if compare is 0, it means
                        // this document is largest than anything else in the queue, and
                        // therefore not competitive.
                        return;
                    }

                    // Compute the score only if the hit is competitive.
                    float score = scorer.GetScore();

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc, score);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Compute the score only if the hit is competitive.
                    float score = scorer.GetScore();

                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                comparer.SetScorer(scorer);
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, while tracking
        /// document scores but no maxScore, and assumes out of orderness in doc Ids
        /// collection.
        /// </summary>
        private class OutOfOrderOneComparerScoringNoMaxScoreCollector : OneComparerScoringNoMaxScoreCollector
        {
            public OutOfOrderOneComparerScoringNoMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    int cmp = reverseMul * comparer.CompareBottom(doc);
                    if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.Doc))
                    {
                        return;
                    }

                    // Compute the score only if the hit is competitive.
                    float score = scorer.GetScore();

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc, score);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Compute the score only if the hit is competitive.
                    float score = scorer.GetScore();

                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, with tracking
        /// document scores and maxScore.
        /// </summary>
        private class OneComparerScoringMaxScoreCollector : OneComparerNonScoringCollector
        {
            internal Scorer scorer;

            public OneComparerScoringMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
                // Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
                maxScore = float.NegativeInfinity;
            }

            internal void UpdateBottom(int doc, float score)
            {
                bottom.Doc = docBase + doc;
                bottom.Score = score;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                float score = scorer.GetScore();
                if (score > maxScore)
                {
                    maxScore = score;
                }
                ++m_totalHits;
                if (queueFull)
                {
                    if ((reverseMul * comparer.CompareBottom(doc)) <= 0)
                    {
                        // since docs are visited in doc Id order, if compare is 0, it means
                        // this document is largest than anything else in the queue, and
                        // therefore not competitive.
                        return;
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc, score);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                base.SetScorer(scorer);
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over one <see cref="SortField"/> criteria, with tracking
        /// document scores and maxScore, and assumes out of orderness in doc Ids
        /// collection.
        /// </summary>
        private class OutOfOrderOneComparerScoringMaxScoreCollector : OneComparerScoringMaxScoreCollector
        {
            public OutOfOrderOneComparerScoringMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                float score = scorer.GetScore();
                if (score > maxScore)
                {
                    maxScore = score;
                }
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    int cmp = reverseMul * comparer.CompareBottom(doc);
                    if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.Doc))
                    {
                        return;
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    comparer.Copy(bottom.Slot, doc);
                    UpdateBottom(doc, score);
                    comparer.SetBottom(bottom.Slot);
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    comparer.Copy(slot, doc);
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        comparer.SetBottom(bottom.Slot);
                    }
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, without
        /// tracking document scores and maxScore.
        /// </summary>
        private class MultiComparerNonScoringCollector : TopFieldCollector
        {
            internal readonly FieldComparer[] comparers;
            internal readonly int[] reverseMul;
            internal readonly FieldValueHitQueue<Entry> queue;

            public MultiComparerNonScoringCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
                this.queue = queue;
                comparers = queue.Comparers;
                reverseMul = queue.ReverseMul;
            }

            internal void UpdateBottom(int doc)
            {
                // bottom.score is already set to Float.NaN in add().
                bottom.Doc = docBase + doc;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // Here c=0. If we're at the last comparer, this doc is not
                            // competitive, since docs are visited in doc Id order, which means
                            // this doc cannot compete with any other document in the queue.
                            return;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    UpdateBottom(doc);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }
                    Add(slot, doc, float.NaN);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override void SetNextReader(AtomicReaderContext context)
            {
                docBase = context.DocBase;
                for (int i = 0; i < comparers.Length; i++)
                {
                    queue.SetComparer(i, comparers[i].SetNextReader(context));
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                // set the value on all comparers
                for (int i = 0; i < comparers.Length; i++)
                {
                    comparers[i].SetScorer(scorer);
                }
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, without
        /// tracking document scores and maxScore, and assumes out of orderness in doc
        /// Ids collection.
        /// </summary>
        private class OutOfOrderMultiComparerNonScoringCollector : MultiComparerNonScoringCollector
        {
            public OutOfOrderMultiComparerNonScoringCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // this is the equals case.
                            if (doc + docBase > bottom.Doc)
                            {
                                // Definitely not competitive
                                return;
                            }
                            break;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    UpdateBottom(doc);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }
                    Add(slot, doc, float.NaN);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, with
        /// tracking document scores and maxScore.
        /// </summary>
        private class MultiComparerScoringMaxScoreCollector : MultiComparerNonScoringCollector
        {
            internal Scorer scorer;

            public MultiComparerScoringMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
                // Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
                maxScore = float.NegativeInfinity;
            }

            internal void UpdateBottom(int doc, float score)
            {
                bottom.Doc = docBase + doc;
                bottom.Score = score;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                float score = scorer.GetScore();
                if (score > maxScore)
                {
                    maxScore = score;
                }
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // Here c=0. If we're at the last comparer, this doc is not
                            // competitive, since docs are visited in doc Id order, which means
                            // this doc cannot compete with any other document in the queue.
                            return;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    UpdateBottom(doc, score);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                base.SetScorer(scorer);
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, with
        /// tracking document scores and maxScore, and assumes out of orderness in doc
        /// Ids collection.
        /// </summary>
        private sealed class OutOfOrderMultiComparerScoringMaxScoreCollector : MultiComparerScoringMaxScoreCollector
        {
            public OutOfOrderMultiComparerScoringMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                float score = scorer.GetScore();
                if (score > maxScore)
                {
                    maxScore = score;
                }
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // this is the equals case.
                            if (doc + docBase > bottom.Doc)
                            {
                                // Definitely not competitive
                                return;
                            }
                            break;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    UpdateBottom(doc, score);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, with
        /// tracking document scores and maxScore.
        /// </summary>
        private class MultiComparerScoringNoMaxScoreCollector : MultiComparerNonScoringCollector
        {
            internal Scorer scorer;

            public MultiComparerScoringNoMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            internal void UpdateBottom(int doc, float score)
            {
                bottom.Doc = docBase + doc;
                bottom.Score = score;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // Here c=0. If we're at the last comparer, this doc is not
                            // competitive, since docs are visited in doc Id order, which means
                            // this doc cannot compete with any other document in the queue.
                            return;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    // Compute score only if it is competitive.
                    float score = scorer.GetScore();
                    UpdateBottom(doc, score);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }

                    // Compute score only if it is competitive.
                    float score = scorer.GetScore();
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                base.SetScorer(scorer);
            }
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> over multiple <see cref="SortField"/> criteria, with
        /// tracking document scores and maxScore, and assumes out of orderness in doc
        /// Ids collection.
        /// </summary>
        private sealed class OutOfOrderMultiComparerScoringNoMaxScoreCollector : MultiComparerScoringNoMaxScoreCollector
        {
            public OutOfOrderMultiComparerScoringNoMaxScoreCollector(FieldValueHitQueue<Entry> queue, int numHits, bool fillFields)
                : base(queue, numHits, fillFields)
            {
            }

            public override void Collect(int doc)
            {
                ++m_totalHits;
                if (queueFull)
                {
                    // Fastmatch: return if this hit is not competitive
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // this is the equals case.
                            if (doc + docBase > bottom.Doc)
                            {
                                // Definitely not competitive
                                return;
                            }
                            break;
                        }
                    }

                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    // Compute score only if it is competitive.
                    float score = scorer.GetScore();
                    UpdateBottom(doc, score);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = m_totalHits - 1;
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }

                    // Compute score only if it is competitive.
                    float score = scorer.GetScore();
                    Add(slot, doc, score);
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                base.SetScorer(scorer);
            }

            public override bool AcceptsDocsOutOfOrder => true;
        }

        /// <summary>
        /// Implements a <see cref="TopFieldCollector"/> when after != null.
        /// </summary>
        private sealed class PagingFieldCollector : TopFieldCollector
        {
            internal Scorer scorer;
            internal int collectedHits;
            internal readonly FieldComparer[] comparers;
            internal readonly int[] reverseMul;
            internal readonly FieldValueHitQueue<Entry> queue;
            internal readonly bool trackDocScores;
            internal readonly bool trackMaxScore;
            internal readonly FieldDoc after;
            internal int afterDoc;

            public PagingFieldCollector(FieldValueHitQueue<Entry> queue, FieldDoc after, int numHits, bool fillFields, bool trackDocScores, bool trackMaxScore)
                : base(queue, numHits, fillFields)
            {
                this.queue = queue;
                this.trackDocScores = trackDocScores;
                this.trackMaxScore = trackMaxScore;
                this.after = after;
                comparers = queue.Comparers;
                reverseMul = queue.ReverseMul;

                // Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
                maxScore = float.NegativeInfinity;

                // Tell all comparers their top value:
                for (int i = 0; i < comparers.Length; i++)
                {
                    FieldComparer comparer = comparers[i];
                    comparer.SetTopValue(after.Fields[i]);
                }
            }

            internal void UpdateBottom(int doc, float score)
            {
                bottom.Doc = docBase + doc;
                bottom.Score = score;
                bottom = m_pq.UpdateTop();
            }

            public override void Collect(int doc)
            {
                //System.out.println("  collect doc=" + doc);

                m_totalHits++;

                float score = float.NaN;
                if (trackMaxScore)
                {
                    score = scorer.GetScore();
                    if (score > maxScore)
                    {
                        maxScore = score;
                    }
                }

                if (queueFull)
                {
                    // Fastmatch: return if this hit is no better than
                    // the worst hit currently in the queue:
                    for (int i = 0; ; i++)
                    {
                        int c = reverseMul[i] * comparers[i].CompareBottom(doc);
                        if (c < 0)
                        {
                            // Definitely not competitive.
                            return;
                        }
                        else if (c > 0)
                        {
                            // Definitely competitive.
                            break;
                        }
                        else if (i == comparers.Length - 1)
                        {
                            // this is the equals case.
                            if (doc + docBase > bottom.Doc)
                            {
                                // Definitely not competitive
                                return;
                            }
                            break;
                        }
                    }
                }

                // Check if this hit was already collected on a
                // previous page:
                bool sameValues = true;
                for (int compIDX = 0; compIDX < comparers.Length; compIDX++)
                {
                    FieldComparer comp = comparers[compIDX];

                    int cmp = reverseMul[compIDX] * comp.CompareTop(doc);
                    if (cmp > 0)
                    {
                        // Already collected on a previous page
                        //System.out.println("    skip: before");
                        return;
                    }
                    else if (cmp < 0)
                    {
                        // Not yet collected
                        sameValues = false;
                        //System.out.println("    keep: after; reverseMul=" + reverseMul[compIDX]);
                        break;
                    }
                }

                // Tie-break by docID:
                if (sameValues && doc <= afterDoc)
                {
                    // Already collected on a previous page
                    //System.out.println("    skip: tie-break");
                    return;
                }

                if (queueFull)
                {
                    // this hit is competitive - replace bottom element in queue & adjustTop
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(bottom.Slot, doc);
                    }

                    // Compute score only if it is competitive.
                    if (trackDocScores && !trackMaxScore)
                    {
                        score = scorer.GetScore();
                    }
                    UpdateBottom(doc, score);

                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].SetBottom(bottom.Slot);
                    }
                }
                else
                {
                    collectedHits++;

                    // Startup transient: queue hasn't gathered numHits yet
                    int slot = collectedHits - 1;
                    //System.out.println("    slot=" + slot);
                    // Copy hit into queue
                    for (int i = 0; i < comparers.Length; i++)
                    {
                        comparers[i].Copy(slot, doc);
                    }

                    // Compute score only if it is competitive.
                    if (trackDocScores && !trackMaxScore)
                    {
                        score = scorer.GetScore();
                    }
                    bottom = m_pq.Add(new Entry(slot, docBase + doc, score));
                    queueFull = collectedHits == numHits;
                    if (queueFull)
                    {
                        for (int i = 0; i < comparers.Length; i++)
                        {
                            comparers[i].SetBottom(bottom.Slot);
                        }
                    }
                }
            }

            public override void SetScorer(Scorer scorer)
            {
                this.scorer = scorer;
                for (int i = 0; i < comparers.Length; i++)
                {
                    comparers[i].SetScorer(scorer);
                }
            }

            public override bool AcceptsDocsOutOfOrder => true;

            public override void SetNextReader(AtomicReaderContext context)
            {
                docBase = context.DocBase;
                afterDoc = after.Doc - docBase;
                for (int i = 0; i < comparers.Length; i++)
                {
                    queue.SetComparer(i, comparers[i].SetNextReader(context));
                }
            }
        }

        private static readonly ScoreDoc[] EMPTY_SCOREDOCS = Arrays.Empty<ScoreDoc>();

        private readonly bool fillFields;

        /// <summary>
        /// Stores the maximum score value encountered, needed for normalizing. If
        /// document scores are not tracked, this value is initialized to NaN.
        /// </summary>
        internal float maxScore = float.NaN;

        internal readonly int numHits;
        internal FieldValueHitQueue.Entry bottom = null;
        internal bool queueFull;
        internal int docBase;

        // Declaring the constructor private prevents extending this class by anyone
        // else. Note that the class cannot be final since it's extended by the
        // internal versions. If someone will define a constructor with any other
        // visibility, then anyone will be able to extend the class, which is not what
        // we want.
        private TopFieldCollector(PriorityQueue<Entry> pq, int numHits, bool fillFields)
            : base(pq)
        {
            this.numHits = numHits;
            this.fillFields = fillFields;
        }

        /// <summary>
        /// Creates a new <see cref="TopFieldCollector"/> from the given
        /// arguments.
        ///
        /// <para/><b>NOTE</b>: The instances returned by this method
        /// pre-allocate a full array of length
        /// <paramref name="numHits"/>.
        /// </summary>
        /// <param name="sort">
        ///          The sort criteria (<see cref="SortField"/>s). </param>
        /// <param name="numHits">
        ///          The number of results to collect. </param>
        /// <param name="fillFields">
        ///          Specifies whether the actual field values should be returned on
        ///          the results (<see cref="FieldDoc"/>). </param>
        /// <param name="trackDocScores">
        ///          Specifies whether document scores should be tracked and set on the
        ///          results. Note that if set to <c>false</c>, then the results' scores will
        ///          be set to <see cref="float.NaN"/>. Setting this to <c>true</c> affects performance, as
        ///          it incurs the score computation on each competitive result.
        ///          Therefore if document scores are not required by the application,
        ///          it is recommended to set it to <c>false</c>. </param>
        /// <param name="trackMaxScore">
        ///          Specifies whether the query's <see cref="maxScore"/> should be tracked and set
        ///          on the resulting <see cref="TopDocs"/>. Note that if set to <c>false</c>,
        ///          <see cref="TopDocs.MaxScore"/> returns <see cref="float.NaN"/>. Setting this to
        ///          <c>true</c> affects performance as it incurs the score computation on
        ///          each result. Also, setting this <c>true</c> automatically sets
        ///          <paramref name="trackDocScores"/> to <c>true</c> as well. </param>
        /// <param name="docsScoredInOrder">
        ///          Specifies whether documents are scored in doc Id order or not by
        ///          the given <see cref="Scorer"/> in <see cref="ICollector.SetScorer(Scorer)"/>. </param>
        /// <returns> A <see cref="TopFieldCollector"/> instance which will sort the results by
        ///         the sort criteria. </returns>
        /// <exception cref="IOException"> If there is a low-level I/O error </exception>
        public static TopFieldCollector Create(Sort sort, int numHits, bool fillFields, bool trackDocScores, bool trackMaxScore, bool docsScoredInOrder)
        {
            return Create(sort, numHits, null, fillFields, trackDocScores, trackMaxScore, docsScoredInOrder);
        }

        /// <summary>
        /// Creates a new <see cref="TopFieldCollector"/> from the given
        /// arguments.
        ///
        /// <para/><b>NOTE</b>: The instances returned by this method
        /// pre-allocate a full array of length
        /// <paramref name="numHits"/>.
        /// </summary>
        /// <param name="sort">
        ///          The sort criteria (<see cref="SortField"/>s). </param>
        /// <param name="numHits">
        ///          The number of results to collect. </param>
        /// <param name="after">
        ///          Only hits after this <see cref="FieldDoc"/> will be collected </param>
        /// <param name="fillFields">
        ///          Specifies whether the actual field values should be returned on
        ///          the results (<see cref="FieldDoc"/>). </param>
        /// <param name="trackDocScores">
        ///          Specifies whether document scores should be tracked and set on the
        ///          results. Note that if set to <c>false</c>, then the results' scores will
        ///          be set to <see cref="float.NaN"/>. Setting this to <c>true</c> affects performance, as
        ///          it incurs the score computation on each competitive result.
        ///          Therefore if document scores are not required by the application,
        ///          it is recommended to set it to <c>false</c>. </param>
        /// <param name="trackMaxScore">
        ///          Specifies whether the query's maxScore should be tracked and set
        ///          on the resulting <see cref="TopDocs"/>. Note that if set to <c>false</c>,
        ///          <see cref="TopDocs.MaxScore"/> returns <see cref="float.NaN"/>. Setting this to
        ///          <c>true</c> affects performance as it incurs the score computation on
        ///          each result. Also, setting this <c>true</c> automatically sets
        ///          <paramref name="trackDocScores"/> to <c>true</c> as well. </param>
        /// <param name="docsScoredInOrder">
        ///          Specifies whether documents are scored in doc Id order or not by
        ///          the given <see cref="Scorer"/> in <see cref="ICollector.SetScorer(Scorer)"/>. </param>
        /// <returns> A <see cref="TopFieldCollector"/> instance which will sort the results by
        ///         the sort criteria. </returns>
        /// <exception cref="IOException"> If there is a low-level I/O error </exception>
        public static TopFieldCollector Create(Sort sort, int numHits, FieldDoc after, bool fillFields, bool trackDocScores, bool trackMaxScore, bool docsScoredInOrder)
        {
            if (sort.fields.Length == 0)
            {
                throw new ArgumentException("Sort must contain at least one field");
            }

            if (numHits <= 0)
            {
                throw new ArgumentException("numHits must be > 0; please use TotalHitCountCollector if you just need the total hit count");
            }

            FieldValueHitQueue<Entry> queue = FieldValueHitQueue.Create<Entry>(sort.fields, numHits);

            if (after == null)
            {
                if (queue.Comparers.Length == 1)
                {
                    if (docsScoredInOrder)
                    {
                        if (trackMaxScore)
                        {
                            return new OneComparerScoringMaxScoreCollector(queue, numHits, fillFields);
                        }
                        else if (trackDocScores)
                        {
                            return new OneComparerScoringNoMaxScoreCollector(queue, numHits, fillFields);
                        }
                        else
                        {
                            return new OneComparerNonScoringCollector(queue, numHits, fillFields);
                        }
                    }
                    else
                    {
                        if (trackMaxScore)
                        {
                            return new OutOfOrderOneComparerScoringMaxScoreCollector(queue, numHits, fillFields);
                        }
                        else if (trackDocScores)
                        {
                            return new OutOfOrderOneComparerScoringNoMaxScoreCollector(queue, numHits, fillFields);
                        }
                        else
                        {
                            return new OutOfOrderOneComparerNonScoringCollector(queue, numHits, fillFields);
                        }
                    }
                }

                // multiple comparers.
                if (docsScoredInOrder)
                {
                    if (trackMaxScore)
                    {
                        return new MultiComparerScoringMaxScoreCollector(queue, numHits, fillFields);
                    }
                    else if (trackDocScores)
                    {
                        return new MultiComparerScoringNoMaxScoreCollector(queue, numHits, fillFields);
                    }
                    else
                    {
                        return new MultiComparerNonScoringCollector(queue, numHits, fillFields);
                    }
                }
                else
                {
                    if (trackMaxScore)
                    {
                        return new OutOfOrderMultiComparerScoringMaxScoreCollector(queue, numHits, fillFields);
                    }
                    else if (trackDocScores)
                    {
                        return new OutOfOrderMultiComparerScoringNoMaxScoreCollector(queue, numHits, fillFields);
                    }
                    else
                    {
                        return new OutOfOrderMultiComparerNonScoringCollector(queue, numHits, fillFields);
                    }
                }
            }
            else
            {
                if (after.Fields == null)
                {
                    throw new ArgumentException("after.fields wasn't set; you must pass fillFields=true for the previous search");
                }

                if (after.Fields.Length != sort.GetSort().Length)
                {
                    throw new ArgumentException("after.fields has " + after.Fields.Length + " values but sort has " + sort.GetSort().Length);
                }

                return new PagingFieldCollector(queue, after, numHits, fillFields, trackDocScores, trackMaxScore);
            }
        }

        internal void Add(int slot, int doc, float score)
        {
            bottom = m_pq.Add(new Entry(slot, docBase + doc, score));
            queueFull = m_totalHits == numHits;
        }

        /*
         * Only the following callback methods need to be overridden since
         * topDocs(int, int) calls them to return the results.
         */

        protected override void PopulateResults(ScoreDoc[] results, int howMany)
        {
            if (fillFields)
            {
                // avoid casting if unnecessary.
                FieldValueHitQueue<Entry> queue = (FieldValueHitQueue<Entry>)m_pq;
                for (int i = howMany - 1; i >= 0; i--)
                {
                    results[i] = queue.FillFields(queue.Pop());
                }
            }
            else
            {
                for (int i = howMany - 1; i >= 0; i--)
                {
                    Entry entry = m_pq.Pop();
                    results[i] = new FieldDoc(entry.Doc, entry.Score);
                }
            }
        }

        protected override TopDocs NewTopDocs(ScoreDoc[] results, int start)
        {
            if (results == null)
            {
                results = EMPTY_SCOREDOCS;
                // Set maxScore to NaN, in case this is a maxScore tracking collector.
                maxScore = float.NaN;
            }

            // If this is a maxScoring tracking collector and there were no results,
            return new TopFieldDocs(m_totalHits, results, ((FieldValueHitQueue<Entry>)m_pq).Fields, maxScore);
        }

        public override bool AcceptsDocsOutOfOrder => false;
    }
}