using Lucene.Net.Analysis;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using NUnit.Framework;
using System.Collections.Generic;

namespace Lucene.Net.Sandbox.Queries
{
    /*
     * 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.
     */

    public class FuzzyLikeThisQueryTest : LuceneTestCase
    {
        private Directory directory;
        private IndexSearcher searcher;
        private IndexReader reader;
        private Analyzer analyzer;

        public override void SetUp()
        {
            base.SetUp();

            analyzer = new MockAnalyzer(Random);
            directory = NewDirectory();
            RandomIndexWriter writer = new RandomIndexWriter(Random, directory, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMergePolicy(NewLogMergePolicy()));

            //Add series of docs with misspelt names
            AddDoc(writer, "jonathon smythe", "1");
            AddDoc(writer, "jonathan smith", "2");
            AddDoc(writer, "johnathon smyth", "3");
            AddDoc(writer, "johnny smith", "4");
            AddDoc(writer, "jonny smith", "5");
            AddDoc(writer, "johnathon smythe", "6");
            reader = writer.GetReader();
            writer.Dispose();
            searcher = NewSearcher(reader);
        }

        public override void TearDown()
        {
            reader.Dispose();
            directory.Dispose();
            base.TearDown();
        }

        private void AddDoc(RandomIndexWriter writer, string name, string id)
        {
            Document doc = new Document();
            doc.Add(NewTextField("name", name, Field.Store.YES));
            doc.Add(NewTextField("id", id, Field.Store.YES));
            writer.AddDocument(doc);
        }


        //Tests that idf ranking is not favouring rare mis-spellings over a strong edit-distance match
        [Test]
        public void TestClosestEditDistanceMatchComesFirst()
        {
            FuzzyLikeThisQuery flt = new FuzzyLikeThisQuery(10, analyzer);
            flt.AddTerms("smith", "name", 0.3f, 1);
            Query q = flt.Rewrite(searcher.IndexReader);
            HashSet<Term> queryTerms = new HashSet<Term>();
            q.ExtractTerms(queryTerms);
            assertTrue("Should have variant smythe", queryTerms.contains(new Term("name", "smythe")));
            assertTrue("Should have variant smith", queryTerms.contains(new Term("name", "smith")));
            assertTrue("Should have variant smyth", queryTerms.contains(new Term("name", "smyth")));
            TopDocs topDocs = searcher.Search(flt, 1);
            ScoreDoc[] sd = topDocs.ScoreDocs;
            assertTrue("score docs must match 1 doc", (sd != null) && (sd.Length > 0));
            Document doc = searcher.Doc(sd[0].Doc);
            assertEquals("Should match most similar not most rare variant", "2", doc.Get("id"));
        }

        //Test multiple input words are having variants produced
        [Test]
        public void TestMultiWord()
        {
            FuzzyLikeThisQuery flt = new FuzzyLikeThisQuery(10, analyzer);
            flt.AddTerms("jonathin smoth", "name", 0.3f, 1);
            Query q = flt.Rewrite(searcher.IndexReader);
            HashSet<Term> queryTerms = new HashSet<Term>();
            q.ExtractTerms(queryTerms);
            assertTrue("Should have variant jonathan", queryTerms.contains(new Term("name", "jonathan")));
            assertTrue("Should have variant smith", queryTerms.contains(new Term("name", "smith")));
            TopDocs topDocs = searcher.Search(flt, 1);
            ScoreDoc[] sd = topDocs.ScoreDocs;
            assertTrue("score docs must match 1 doc", (sd != null) && (sd.Length > 0));
            Document doc = searcher.Doc(sd[0].Doc);
            assertEquals("Should match most similar when using 2 words", "2", doc.Get("id"));
        }

        // LUCENE-4809
        [Test]
        public void TestNonExistingField()
        {
            FuzzyLikeThisQuery flt = new FuzzyLikeThisQuery(10, analyzer);
            flt.AddTerms("jonathin smoth", "name", 0.3f, 1);
            flt.AddTerms("jonathin smoth", "this field does not exist", 0.3f, 1);
            // don't fail here just because the field doesn't exits
            Query q = flt.Rewrite(searcher.IndexReader);
            HashSet<Term> queryTerms = new HashSet<Term>();
            q.ExtractTerms(queryTerms);
            assertTrue("Should have variant jonathan", queryTerms.contains(new Term("name", "jonathan")));
            assertTrue("Should have variant smith", queryTerms.contains(new Term("name", "smith")));
            TopDocs topDocs = searcher.Search(flt, 1);
            ScoreDoc[] sd = topDocs.ScoreDocs;
            assertTrue("score docs must match 1 doc", (sd != null) && (sd.Length > 0));
            Document doc = searcher.Doc(sd[0].Doc);
            assertEquals("Should match most similar when using 2 words", "2", doc.Get("id"));
        }


        //Test bug found when first query word does not match anything
        [Test]
        public void TestNoMatchFirstWordBug()
        {
            FuzzyLikeThisQuery flt = new FuzzyLikeThisQuery(10, analyzer);
            flt.AddTerms("fernando smith", "name", 0.3f, 1);
            Query q = flt.Rewrite(searcher.IndexReader);
            HashSet<Term> queryTerms = new HashSet<Term>();
            q.ExtractTerms(queryTerms);
            assertTrue("Should have variant smith", queryTerms.contains(new Term("name", "smith")));
            TopDocs topDocs = searcher.Search(flt, 1);
            ScoreDoc[] sd = topDocs.ScoreDocs;
            assertTrue("score docs must match 1 doc", (sd != null) && (sd.Length > 0));
            Document doc = searcher.Doc(sd[0].Doc);
            assertEquals("Should match most similar when using 2 words", "2", doc.Get("id"));
        }

        [Test]
        public void TestFuzzyLikeThisQueryEquals()
        {
            Analyzer analyzer = new MockAnalyzer(Random);
            FuzzyLikeThisQuery fltq1 = new FuzzyLikeThisQuery(10, analyzer);
            fltq1.AddTerms("javi", "subject", 0.5f, 2);
            FuzzyLikeThisQuery fltq2 = new FuzzyLikeThisQuery(10, analyzer);
            fltq2.AddTerms("javi", "subject", 0.5f, 2);
            assertEquals("FuzzyLikeThisQuery with same attributes is not equal", fltq1,
                fltq2);
        }
    }
}
