using Lucene.Net.Analysis;
using Lucene.Net.Analysis.TokenAttributes;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search.Spans;
using Lucene.Net.Store;
using Lucene.Net.Util;
using NUnit.Framework;
using System;

namespace Lucene.Net.Search.Highlight
{
    /*
     * 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 HighlighterPhraseTest : LuceneTestCase
    {
        private static readonly String FIELD = "text";

        [Test]
        public void TestConcurrentPhrase()
        {
            String TEXT = "the fox jumped";
            Directory directory = NewDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,
               NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
            try
            {
                Document document = new Document();
                FieldType customType = new FieldType(TextField.TYPE_NOT_STORED);
                customType.StoreTermVectorOffsets = (true);
                customType.StoreTermVectorPositions = (true);
                customType.StoreTermVectors = (true);
                document.Add(new Field(FIELD, new TokenStreamConcurrent(), customType));
                indexWriter.AddDocument(document);
            }
            finally
            {
                indexWriter.Dispose();
            }
            IndexReader indexReader = DirectoryReader.Open(directory);
            try
            {
                assertEquals(1, indexReader.NumDocs);
                IndexSearcher indexSearcher = NewSearcher(indexReader);
                PhraseQuery phraseQuery = new PhraseQuery();
                phraseQuery.Add(new Term(FIELD, "fox"));
                phraseQuery.Add(new Term(FIELD, "jumped"));
                phraseQuery.Slop = (0);
                TopDocs hits = indexSearcher.Search(phraseQuery, 1);
                assertEquals(1, hits.TotalHits);
                Highlighter highlighter = new Highlighter(
                         new SimpleHTMLFormatter(), new SimpleHTMLEncoder(),
                         new QueryScorer(phraseQuery));

                TokenStream tokenStream = TokenSources
                   .GetTokenStream(indexReader.GetTermVector(
                       0, FIELD), false);
                assertEquals(highlighter.GetBestFragment(new TokenStreamConcurrent(),
                    TEXT), highlighter.GetBestFragment(tokenStream, TEXT));
            }
            finally
            {
                indexReader.Dispose();
                directory.Dispose();
            }
        }
        [Test]
        public void TestConcurrentSpan()
        {
            String TEXT = "the fox jumped";
            Directory directory = NewDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,
               NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
            try
            {
                Document document = new Document();

                FieldType customType = new FieldType(TextField.TYPE_NOT_STORED);
                customType.StoreTermVectorOffsets = (true);
                customType.StoreTermVectorPositions = (true);
                customType.StoreTermVectors = (true);
                document.Add(new Field(FIELD, new TokenStreamConcurrent(), customType));
                indexWriter.AddDocument(document);
            }
            finally
            {
                indexWriter.Dispose();
            }
            IndexReader indexReader = DirectoryReader.Open(directory);
            try
            {
                assertEquals(1, indexReader.NumDocs);
                IndexSearcher indexSearcher = NewSearcher(indexReader);
                Query phraseQuery = new SpanNearQuery(new SpanQuery[] {
                    new SpanTermQuery(new Term(FIELD, "fox")),
                    new SpanTermQuery(new Term(FIELD, "jumped")) }, 0, true);
                FixedBitSet bitset = new FixedBitSet(indexReader.MaxDoc);
                indexSearcher.Search(phraseQuery, new ConcurrentSpanCollectorAnonymousHelper(this, bitset));

                assertEquals(1, bitset.Cardinality());
                int maxDoc = indexReader.MaxDoc;
                Highlighter highlighter = new Highlighter(
                         new SimpleHTMLFormatter(), new SimpleHTMLEncoder(),
                         new QueryScorer(phraseQuery));
                for (int position = bitset.NextSetBit(0); position >= 0 && position < maxDoc - 1; position = bitset
                  .NextSetBit(position + 1))
                {
                    assertEquals(0, position);
                    TokenStream tokenStream = TokenSources.GetTokenStream(
                               indexReader.GetTermVector(position,
                                   FIELD), false);
                    assertEquals(highlighter.GetBestFragment(new TokenStreamConcurrent(),
                        TEXT), highlighter.GetBestFragment(tokenStream, TEXT));
                }
            }
            finally
            {
                indexReader.Dispose();
                directory.Dispose();
            }
        }

        internal class ConcurrentSpanCollectorAnonymousHelper : ICollector
        {
            private readonly HighlighterPhraseTest outerInstance;
            private readonly FixedBitSet bitset;
            public ConcurrentSpanCollectorAnonymousHelper(HighlighterPhraseTest outerInstance, FixedBitSet bitset)
            {
                this.outerInstance = outerInstance;
                this.bitset = bitset;
            }

            private int baseDoc;

            public virtual bool AcceptsDocsOutOfOrder => true;

            public virtual void Collect(int i)
            {
                bitset.Set(this.baseDoc + i);
            }

            public virtual void SetNextReader(AtomicReaderContext context)
            {
                this.baseDoc = context.DocBase;
            }

            public virtual void SetScorer(Scorer scorer)
            {
                // Do Nothing
            }
        }

        [Test]
        public void TestSparsePhrase()
        {
            String TEXT = "the fox did not jump";
            Directory directory = NewDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,
               NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
            try
            {
                Document document = new Document();

                FieldType customType = new FieldType(TextField.TYPE_NOT_STORED);
                customType.StoreTermVectorOffsets = (true);
                customType.StoreTermVectorPositions = (true);
                customType.StoreTermVectors = (true);
                document.Add(new Field(FIELD, new TokenStreamSparse(), customType));
                indexWriter.AddDocument(document);
            }
            finally
            {
                indexWriter.Dispose();
            }
            IndexReader indexReader = DirectoryReader.Open(directory);
            try
            {
                assertEquals(1, indexReader.NumDocs);
                IndexSearcher indexSearcher = NewSearcher(indexReader);
                PhraseQuery phraseQuery = new PhraseQuery();
                phraseQuery.Add(new Term(FIELD, "did"));
                phraseQuery.Add(new Term(FIELD, "jump"));
                phraseQuery.Slop = (0);
                TopDocs hits = indexSearcher.Search(phraseQuery, 1);
                assertEquals(0, hits.TotalHits);
                Highlighter highlighter = new Highlighter(
                         new SimpleHTMLFormatter(), new SimpleHTMLEncoder(),
                         new QueryScorer(phraseQuery));
                TokenStream tokenStream = TokenSources
                   .GetTokenStream(indexReader.GetTermVector(
                       0, FIELD), false);
                assertEquals(
                    highlighter.GetBestFragment(new TokenStreamSparse(), TEXT),
                    highlighter.GetBestFragment(tokenStream, TEXT));
            }
            finally
            {
                indexReader.Dispose();
                directory.Dispose();
            }
        }

        [Test]
        public void TestSparsePhraseWithNoPositions()
        {
            String TEXT = "the fox did not jump";
            Directory directory = NewDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,
               NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
            try
            {
                Document document = new Document();

                FieldType customType = new FieldType(TextField.TYPE_STORED);
                customType.StoreTermVectorOffsets = (true);
                customType.StoreTermVectors = (true);
                document.Add(new Field(FIELD, TEXT, customType));
                indexWriter.AddDocument(document);
            }
            finally
            {
                indexWriter.Dispose();
            }
            IndexReader indexReader = DirectoryReader.Open(directory);
            try
            {
                assertEquals(1, indexReader.NumDocs);
                IndexSearcher indexSearcher = NewSearcher(indexReader);
                PhraseQuery phraseQuery = new PhraseQuery();
                phraseQuery.Add(new Term(FIELD, "did"));
                phraseQuery.Add(new Term(FIELD, "jump"));
                phraseQuery.Slop = (1);
                TopDocs hits = indexSearcher.Search(phraseQuery, 1);
                assertEquals(1, hits.TotalHits);
                Highlighter highlighter = new Highlighter(
                         new SimpleHTMLFormatter(), new SimpleHTMLEncoder(),
                         new QueryScorer(phraseQuery));
                TokenStream tokenStream = TokenSources.GetTokenStream(
                   indexReader.GetTermVector(0, FIELD), true);
                assertEquals("the fox <B>did</B> not <B>jump</B>", highlighter
                    .GetBestFragment(tokenStream, TEXT));
            }
            finally
            {
                indexReader.Dispose();
                directory.Dispose();
            }
        }

        [Test]
        public void TestSparseSpan()
        {
            String TEXT = "the fox did not jump";
            Directory directory = NewDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,
               NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
            try
            {
                Document document = new Document();
                FieldType customType = new FieldType(TextField.TYPE_NOT_STORED);
                customType.StoreTermVectorOffsets = (true);
                customType.StoreTermVectorPositions = (true);
                customType.StoreTermVectors = (true);
                document.Add(new Field(FIELD, new TokenStreamSparse(), customType));
                indexWriter.AddDocument(document);
            }
            finally
            {
                indexWriter.Dispose();
            }
            IndexReader indexReader = DirectoryReader.Open(directory);
            try
            {
                assertEquals(1, indexReader.NumDocs);
                IndexSearcher indexSearcher = NewSearcher(indexReader);
                Query phraseQuery = new SpanNearQuery(new SpanQuery[] {
                    new SpanTermQuery(new Term(FIELD, "did")),
                    new SpanTermQuery(new Term(FIELD, "jump")) }, 0, true);

                TopDocs hits = indexSearcher.Search(phraseQuery, 1);
                assertEquals(0, hits.TotalHits);
                Highlighter highlighter = new Highlighter(
                         new SimpleHTMLFormatter(), new SimpleHTMLEncoder(),
                         new QueryScorer(phraseQuery));
                TokenStream tokenStream = TokenSources
                   .GetTokenStream(indexReader.GetTermVector(
                       0, FIELD), false);
                assertEquals(
                    highlighter.GetBestFragment(new TokenStreamSparse(), TEXT),
                    highlighter.GetBestFragment(tokenStream, TEXT));
            }
            finally
            {
                indexReader.Dispose();
                directory.Dispose();
            }
        }

        private sealed class TokenStreamSparse : TokenStream
        {
            private Token[] tokens;

            private int i = -1;

            private readonly ICharTermAttribute termAttribute;
            private readonly IOffsetAttribute offsetAttribute;
            private readonly IPositionIncrementAttribute positionIncrementAttribute;

            public TokenStreamSparse()
            {
                termAttribute = AddAttribute<ICharTermAttribute>();
                offsetAttribute = AddAttribute<IOffsetAttribute>();
                positionIncrementAttribute = AddAttribute<IPositionIncrementAttribute>();
                Reset();
            }


            public override bool IncrementToken()
            {
                this.i++;
                if (this.i >= this.tokens.Length)
                {
                    return false;
                }
                ClearAttributes();
                termAttribute.SetEmpty().Append(this.tokens[i]);
                offsetAttribute.SetOffset(this.tokens[i].StartOffset, this.tokens[i]
                    .EndOffset);
                positionIncrementAttribute.PositionIncrement = (this.tokens[i]
                    .PositionIncrement);
                return true;
            }


            public override void Reset()
            {
                this.i = -1;
                this.tokens = new Token[] {
                    new Token(new char[] { 't', 'h', 'e' }, 0, 3, 0, 3),
                    new Token(new char[] { 'f', 'o', 'x' }, 0, 3, 4, 7),
                    new Token(new char[] { 'd', 'i', 'd' }, 0, 3, 8, 11),
                    new Token(new char[] { 'j', 'u', 'm', 'p' }, 0, 4, 16, 20) };
                this.tokens[3].PositionIncrement = (2);
            }
        }

        private sealed class TokenStreamConcurrent : TokenStream
        {
            private Token[] tokens;

            private int i = -1;

            private readonly ICharTermAttribute termAttribute;
            private readonly IOffsetAttribute offsetAttribute;
            private readonly IPositionIncrementAttribute positionIncrementAttribute;

            public TokenStreamConcurrent()
            {
                termAttribute = AddAttribute<ICharTermAttribute>();
                offsetAttribute = AddAttribute<IOffsetAttribute>();
                positionIncrementAttribute = AddAttribute<IPositionIncrementAttribute>();
                Reset();
            }


            public override bool IncrementToken()
            {
                this.i++;
                if (this.i >= this.tokens.Length)
                {
                    return false;
                }
                ClearAttributes();
                termAttribute.SetEmpty().Append(this.tokens[i]);
                offsetAttribute.SetOffset(this.tokens[i].StartOffset, this.tokens[i]
                    .EndOffset);
                positionIncrementAttribute.PositionIncrement = (this.tokens[i]
                    .PositionIncrement);
                return true;
            }


            public override void Reset()
            {
                this.i = -1;
                this.tokens = new Token[] {
                    new Token(new char[] { 't', 'h', 'e' }, 0, 3, 0, 3),
                    new Token(new char[] { 'f', 'o', 'x' }, 0, 3, 4, 7),
                    new Token(new char[] { 'j', 'u', 'm', 'p' }, 0, 4, 8, 14),
                    new Token(new char[] { 'j', 'u', 'm', 'p', 'e', 'd' }, 0, 6, 8, 14) };
                this.tokens[3].PositionIncrement = (0);
            }
        }
    }
}
