using System;
using System.Collections.Generic;
using System.Diagnostics;

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;

    /// <summary>
    /// A <seealso cref="Rescorer"/> that re-sorts according to a provided
    /// Sort.
    /// </summary>
#if FEATURE_SERIALIZABLE
    [Serializable]
#endif
    public class SortRescorer : Rescorer
    {
        private readonly Sort sort;

        /// <summary>
        /// Sole constructor. </summary>
        public SortRescorer(Sort sort)
        {
            this.sort = sort;
        }

        public override TopDocs Rescore(IndexSearcher searcher, TopDocs firstPassTopDocs, int topN)
        {
            // Copy ScoreDoc[] and sort by ascending docID:
            ScoreDoc[] hits = (ScoreDoc[])firstPassTopDocs.ScoreDocs.Clone();
            Array.Sort(hits, new ComparerAnonymousInnerClassHelper(this));

            IList<AtomicReaderContext> leaves = searcher.IndexReader.Leaves;

            TopFieldCollector collector = TopFieldCollector.Create(sort, topN, true, true, true, false);

            // Now merge sort docIDs from hits, with reader's leaves:
            int hitUpto = 0;
            int readerUpto = -1;
            int endDoc = 0;
            int docBase = 0;

            FakeScorer fakeScorer = new FakeScorer();

            while (hitUpto < hits.Length)
            {
                ScoreDoc hit = hits[hitUpto];
                int docID = hit.Doc;
                AtomicReaderContext readerContext = null;
                while (docID >= endDoc)
                {
                    readerUpto++;
                    readerContext = leaves[readerUpto];
                    endDoc = readerContext.DocBase + readerContext.Reader.MaxDoc;
                }

                if (readerContext != null)
                {
                    // We advanced to another segment:
                    collector.SetNextReader(readerContext);
                    collector.SetScorer(fakeScorer);
                    docBase = readerContext.DocBase;
                }

                fakeScorer.score = hit.Score;
                fakeScorer.doc = docID - docBase;

                collector.Collect(fakeScorer.doc);

                hitUpto++;
            }

            return collector.GetTopDocs();
        }

#if FEATURE_SERIALIZABLE
        [Serializable]
#endif
        private class ComparerAnonymousInnerClassHelper : IComparer<ScoreDoc>
        {
            private readonly SortRescorer outerInstance;

            public ComparerAnonymousInnerClassHelper(SortRescorer outerInstance)
            {
                this.outerInstance = outerInstance;
            }

            public virtual int Compare(ScoreDoc a, ScoreDoc b)
            {
                return a.Doc - b.Doc;
            }
        }

        public override Explanation Explain(IndexSearcher searcher, Explanation firstPassExplanation, int docID)
        {
            TopDocs oneHit = new TopDocs(1, new ScoreDoc[] { new ScoreDoc(docID, firstPassExplanation.Value) });
            TopDocs hits = Rescore(searcher, oneHit, 1);
            Debug.Assert(hits.TotalHits == 1);

            // TODO: if we could ask the Sort to explain itself then
            // we wouldn't need the separate ExpressionRescorer...
            Explanation result = new Explanation(0.0f, "sort field values for sort=" + sort.ToString());

            // Add first pass:
            Explanation first = new Explanation(firstPassExplanation.Value, "first pass score");
            first.AddDetail(firstPassExplanation);
            result.AddDetail(first);

            FieldDoc fieldDoc = (FieldDoc)hits.ScoreDocs[0];

            // Add sort values:
            SortField[] sortFields = sort.GetSort();
            for (int i = 0; i < sortFields.Length; i++)
            {
                result.AddDetail(new Explanation(0.0f, "sort field " + sortFields[i].ToString() + " value=" + fieldDoc.Fields[i]));
            }

            return result;
        }
    }
}