using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Store;
using Lucene.Net.Util;
using NUnit.Framework;
using System.Collections.Generic;
using Assert = Lucene.Net.TestFramework.Assert;
using JCG = J2N.Collections.Generic;

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 ChildScorer = Lucene.Net.Search.Scorer.ChildScorer;
    using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;

    [TestFixture]
    public class TestSubScorerFreqs : LuceneTestCase
    {
        private static Directory dir;
        private static IndexSearcher s;

        [OneTimeSetUp]
        public override void BeforeClass() // LUCENENET specific - renamed from MakeIndex() to ensure calling order
        {
            base.BeforeClass();

            dir = new RAMDirectory();
            RandomIndexWriter w = new RandomIndexWriter(Random, dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMergePolicy(NewLogMergePolicy()));
            // make sure we have more than one segment occationally
            int num = AtLeast(31);
            for (int i = 0; i < num; i++)
            {
                Documents.Document doc = new Documents.Document();
                doc.Add(NewTextField("f", "a b c d b c d c d d", Field.Store.NO));
                w.AddDocument(doc);

                doc = new Documents.Document();
                doc.Add(NewTextField("f", "a b c d", Field.Store.NO));
                w.AddDocument(doc);
            }

            s = NewSearcher(w.GetReader());
            w.Dispose();
        }

        [OneTimeTearDown]
        public override void AfterClass() // LUCENENET specific - renamed from Finish() to ensure calling order
        {
            s.IndexReader.Dispose();
            s = null;
            dir.Dispose();
            dir = null;

            base.AfterClass();
        }

        private class CountingCollector : ICollector
        {
            private readonly ICollector other;
            private int docBase;

            public IDictionary<int?, IDictionary<Query, float?>> DocCounts { get; } = new Dictionary<int?, IDictionary<Query, float?>>();

            private readonly IDictionary<Query, Scorer> subScorers = new Dictionary<Query, Scorer>();
            private readonly ISet<string> relationships;

            public CountingCollector(ICollector other)
                : this(other, new JCG.HashSet<string> { "MUST", "SHOULD", "MUST_NOT" })
            {
            }

            public CountingCollector(ICollector other, ISet<string> relationships)
            {
                this.other = other;
                this.relationships = relationships;
            }

            public virtual void SetScorer(Scorer scorer)
            {
                other.SetScorer(scorer);
                subScorers.Clear();
                SetSubScorers(scorer, "TOP");
            }

            public virtual void SetSubScorers(Scorer scorer, string relationship)
            {
                foreach (ChildScorer child in scorer.GetChildren())
                {
                    if (scorer is AssertingScorer || relationships.Contains(child.Relationship))
                    {
                        SetSubScorers(child.Child, child.Relationship);
                    }
                }
                subScorers[scorer.Weight.Query] = scorer;
            }

            public virtual void Collect(int doc)
            {
                IDictionary<Query, float?> freqs = new Dictionary<Query, float?>();
                foreach (KeyValuePair<Query, Scorer> ent in subScorers)
                {
                    Scorer value = ent.Value;
                    int matchId = value.DocID;
                    freqs[ent.Key] = matchId == doc ? value.Freq : 0.0f;
                }
                DocCounts[doc + docBase] = freqs;
                other.Collect(doc);
            }

            public virtual void SetNextReader(AtomicReaderContext context)
            {
                docBase = context.DocBase;
                other.SetNextReader(context);
            }

            public virtual bool AcceptsDocsOutOfOrder => other.AcceptsDocsOutOfOrder;
        }

        private const float FLOAT_TOLERANCE = 0.00001F;

        [Test]
        public virtual void TestTermQuery()
        {
            TermQuery q = new TermQuery(new Term("f", "d"));
            CountingCollector c = new CountingCollector(TopScoreDocCollector.Create(10, true));
            s.Search(q, null, c);
            int maxDocs = s.IndexReader.MaxDoc;
            Assert.AreEqual(maxDocs, c.DocCounts.Count);
            for (int i = 0; i < maxDocs; i++)
            {
                IDictionary<Query, float?> doc0 = c.DocCounts[i];
                Assert.AreEqual(1, doc0.Count);
                Assert.AreEqual(4.0F, doc0[q].GetValueOrDefault(), FLOAT_TOLERANCE);

                IDictionary<Query, float?> doc1 = c.DocCounts[++i];
                Assert.AreEqual(1, doc1.Count);
                Assert.AreEqual(1.0F, doc1[q].GetValueOrDefault(), FLOAT_TOLERANCE);
            }
        }

        [Test]
        public virtual void TestBooleanQuery()
        {
            TermQuery aQuery = new TermQuery(new Term("f", "a"));
            TermQuery dQuery = new TermQuery(new Term("f", "d"));
            TermQuery cQuery = new TermQuery(new Term("f", "c"));
            TermQuery yQuery = new TermQuery(new Term("f", "y"));

            BooleanQuery query = new BooleanQuery();
            BooleanQuery inner = new BooleanQuery();

            inner.Add(cQuery, Occur.SHOULD);
            inner.Add(yQuery, Occur.MUST_NOT);
            query.Add(inner, Occur.MUST);
            query.Add(aQuery, Occur.MUST);
            query.Add(dQuery, Occur.MUST);

            // Only needed in Java6; Java7+ has a @SafeVarargs annotated Arrays#asList()!
            // see http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html
            IEnumerable<ISet<string>> occurList = new ISet<string>[] { new JCG.HashSet<string> { "MUST" }, new JCG.HashSet<string> { "MUST", "SHOULD" } };

            foreach (var occur in occurList)
            {
                var c = new CountingCollector(TopScoreDocCollector.Create(10, true), occur);
                s.Search(query, null, c);
                int maxDocs = s.IndexReader.MaxDoc;
                Assert.AreEqual(maxDocs, c.DocCounts.Count);
                bool includeOptional = occur.Contains("SHOULD");
                for (int i = 0; i < maxDocs; i++)
                {
                    IDictionary<Query, float?> doc0 = c.DocCounts[i];
                    Assert.AreEqual(includeOptional ? 5 : 4, doc0.Count);
                    Assert.AreEqual(1.0F, doc0[aQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    Assert.AreEqual(4.0F, doc0[dQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    if (includeOptional)
                    {
                        Assert.AreEqual(3.0F, doc0[cQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    }

                    IDictionary<Query, float?> doc1 = c.DocCounts[++i];
                    Assert.AreEqual(includeOptional ? 5 : 4, doc1.Count);
                    Assert.AreEqual(1.0F, doc1[aQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    Assert.AreEqual(1.0F, doc1[dQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    if (includeOptional)
                    {
                        Assert.AreEqual(1.0F, doc1[cQuery].GetValueOrDefault(), FLOAT_TOLERANCE);
                    }
                }
            }
        }

        [Test]
        public virtual void TestPhraseQuery()
        {
            PhraseQuery q = new PhraseQuery();
            q.Add(new Term("f", "b"));
            q.Add(new Term("f", "c"));
            CountingCollector c = new CountingCollector(TopScoreDocCollector.Create(10, true));
            s.Search(q, null, c);
            int maxDocs = s.IndexReader.MaxDoc;
            Assert.AreEqual(maxDocs, c.DocCounts.Count);
            for (int i = 0; i < maxDocs; i++)
            {
                IDictionary<Query, float?> doc0 = c.DocCounts[i];
                Assert.AreEqual(1, doc0.Count);
                Assert.AreEqual(2.0F, doc0[q].GetValueOrDefault(), FLOAT_TOLERANCE);

                IDictionary<Query, float?> doc1 = c.DocCounts[++i];
                Assert.AreEqual(1, doc1.Count);
                Assert.AreEqual(1.0F, doc1[q].GetValueOrDefault(), FLOAT_TOLERANCE);
            }
        }
    }
}