using System;
using System.Diagnostics;
using IBits = Lucene.Net.Util.IBits;

namespace Lucene.Net.Codecs.Lucene3x
{
    /*
     * 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 FieldInfo = Lucene.Net.Index.FieldInfo;
    using FieldInfos = Lucene.Net.Index.FieldInfos;
    using IndexInput = Lucene.Net.Store.IndexInput;
    using IndexOptions = Lucene.Net.Index.IndexOptions;
    using Term = Lucene.Net.Index.Term;

    /// <summary>
    /// @lucene.experimental
    /// </summary>
    [Obsolete("(4.0)")]
    internal class SegmentTermDocs
    {
        //protected SegmentReader parent;
        private readonly FieldInfos fieldInfos;

        private readonly TermInfosReader tis;
        protected IBits m_liveDocs;
        protected IndexInput m_freqStream;
        protected int m_count;
        protected internal int m_df;
        internal int doc = 0;
        internal int freq;

        private int skipInterval;
        private int maxSkipLevels;
        private Lucene3xSkipListReader skipListReader;

        private long freqBasePointer;
        private long proxBasePointer;

        private long skipPointer;
        private bool haveSkipped;

        protected bool m_currentFieldStoresPayloads;
        protected IndexOptions m_indexOptions;

        public SegmentTermDocs(IndexInput freqStream, TermInfosReader tis, FieldInfos fieldInfos)
        {
            this.m_freqStream = (IndexInput)freqStream.Clone();
            this.tis = tis;
            this.fieldInfos = fieldInfos;
            skipInterval = tis.SkipInterval;
            maxSkipLevels = tis.MaxSkipLevels;
        }

        public virtual void Seek(Term term)
        {
            TermInfo ti = tis.Get(term);
            Seek(ti, term);
        }

        public virtual IBits LiveDocs
        {
            get => this.m_liveDocs; // LUCENENET specific - per MSDN, a property must always have a getter
            set => this.m_liveDocs = value;
        }

        public virtual void Seek(SegmentTermEnum segmentTermEnum)
        {
            TermInfo ti;
            Term term;

            // use comparison of fieldinfos to verify that termEnum belongs to the same segment as this SegmentTermDocs
            if (segmentTermEnum.fieldInfos == fieldInfos) // optimized case
            {
                term = segmentTermEnum.Term();
                ti = segmentTermEnum.TermInfo();
            } // punt case
            else
            {
                term = segmentTermEnum.Term();
                ti = tis.Get(term);
            }

            Seek(ti, term);
        }

        internal virtual void Seek(TermInfo ti, Term term)
        {
            m_count = 0;
            FieldInfo fi = fieldInfos.FieldInfo(term.Field);
            this.m_indexOptions = (fi != null) ? fi.IndexOptions : IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;
            m_currentFieldStoresPayloads = (fi != null) && fi.HasPayloads;
            if (ti == null)
            {
                m_df = 0;
            }
            else
            {
                m_df = ti.DocFreq;
                doc = 0;
                freqBasePointer = ti.FreqPointer;
                proxBasePointer = ti.ProxPointer;
                skipPointer = freqBasePointer + ti.SkipOffset;
                m_freqStream.Seek(freqBasePointer);
                haveSkipped = false;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                m_freqStream.Dispose();
                if (skipListReader != null)
                {
                    skipListReader.Dispose();
                }
            }
        }

        public int Doc => doc;

        public int Freq => freq;

        protected internal virtual void SkippingDoc()
        {
        }

        public virtual bool Next()
        {
            while (true)
            {
                if (m_count == m_df)
                {
                    return false;
                }
                int docCode = m_freqStream.ReadVInt32();

                if (m_indexOptions == IndexOptions.DOCS_ONLY)
                {
                    doc += docCode;
                }
                else
                {
                    doc += (int)((uint)docCode >> 1); // shift off low bit
                    if ((docCode & 1) != 0) // if low bit is set
                    {
                        freq = 1; // freq is one
                    }
                    else
                    {
                        freq = m_freqStream.ReadVInt32(); // else read freq
                        Debug.Assert(freq != 1);
                    }
                }

                m_count++;

                if (m_liveDocs == null || m_liveDocs.Get(doc))
                {
                    break;
                }
                SkippingDoc();
            }
            return true;
        }

        /// <summary>
        /// Optimized implementation. </summary>
        public virtual int Read(int[] docs, int[] freqs)
        {
            int length = docs.Length;
            if (m_indexOptions == IndexOptions.DOCS_ONLY)
            {
                return ReadNoTf(docs, freqs, length);
            }
            else
            {
                int i = 0;
                while (i < length && m_count < m_df)
                {
                    // manually inlined call to next() for speed
                    int docCode = m_freqStream.ReadVInt32();
                    doc += (int)((uint)docCode >> 1); // shift off low bit
                    if ((docCode & 1) != 0) // if low bit is set
                    {
                        freq = 1; // freq is one
                    }
                    else
                    {
                        freq = m_freqStream.ReadVInt32(); // else read freq
                    }
                    m_count++;

                    if (m_liveDocs == null || m_liveDocs.Get(doc))
                    {
                        docs[i] = doc;
                        freqs[i] = freq;
                        ++i;
                    }
                }
                return i;
            }
        }

        private int ReadNoTf(int[] docs, int[] freqs, int length)
        {
            int i = 0;
            while (i < length && m_count < m_df)
            {
                // manually inlined call to next() for speed
                doc += m_freqStream.ReadVInt32();
                m_count++;

                if (m_liveDocs == null || m_liveDocs.Get(doc))
                {
                    docs[i] = doc;
                    // Hardware freq to 1 when term freqs were not
                    // stored in the index
                    freqs[i] = 1;
                    ++i;
                }
            }
            return i;
        }

        /// <summary>
        /// Overridden by <see cref="SegmentTermPositions"/> to skip in prox stream. </summary>
        protected internal virtual void SkipProx(long proxPointer, int payloadLength)
        {
        }

        /// <summary>
        /// Optimized implementation. </summary>
        public virtual bool SkipTo(int target)
        {
            // don't skip if the target is close (within skipInterval docs away)
            if ((target - skipInterval) >= doc && m_df >= skipInterval) // optimized case
            {
                if (skipListReader == null)
                {
                    skipListReader = new Lucene3xSkipListReader((IndexInput)m_freqStream.Clone(), maxSkipLevels, skipInterval); // lazily clone
                }

                if (!haveSkipped) // lazily initialize skip stream
                {
                    skipListReader.Init(skipPointer, freqBasePointer, proxBasePointer, m_df, m_currentFieldStoresPayloads);
                    haveSkipped = true;
                }

                int newCount = skipListReader.SkipTo(target);
                if (newCount > m_count)
                {
                    m_freqStream.Seek(skipListReader.FreqPointer);
                    SkipProx(skipListReader.ProxPointer, skipListReader.PayloadLength);

                    doc = skipListReader.Doc;
                    m_count = newCount;
                }
            }

            // done skipping, now just scan
            do
            {
                if (!Next())
                {
                    return false;
                }
            } while (target > doc);
            return true;
        }
    }
}