using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Support;
using System.Diagnostics;

namespace Lucene.Net.Codecs.Sep
{
    /*
     * 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.
     */

    /// <summary>
    /// Implements the skip list reader for the default posting list format
    /// that stores positions and payloads.
    /// <para/>
    /// @lucene.experimental
    /// </summary>

    // TODO: rewrite this as recursive classes?
    internal class SepSkipListReader : MultiLevelSkipListReader
    {
        private bool currentFieldStoresPayloads;
        private Int32IndexInput.Index[] freqIndex;
        private Int32IndexInput.Index[] docIndex;
        private Int32IndexInput.Index[] posIndex;
        private long[] payloadPointer;
        private int[] payloadLength;

        private readonly Int32IndexInput.Index lastFreqIndex;
        private readonly Int32IndexInput.Index lastDocIndex;
        // TODO: -- make private again
        internal readonly Int32IndexInput.Index lastPosIndex;

        private long lastPayloadPointer;
        private int lastPayloadLength;

        /// <exception cref="System.IO.IOException"/>
        internal SepSkipListReader(IndexInput skipStream,
                          Int32IndexInput freqIn,
                          Int32IndexInput docIn,
                          Int32IndexInput posIn,
                          int maxSkipLevels,
                          int skipInterval)
            : base(skipStream, maxSkipLevels, skipInterval)
        {
            if (freqIn != null)
            {
                freqIndex = new Int32IndexInput.Index[maxSkipLevels];
            }
            docIndex = new Int32IndexInput.Index[maxSkipLevels];
            if (posIn != null)
            {
                posIndex = new Int32IndexInput.Index[m_maxNumberOfSkipLevels];
            }
            for (int i = 0; i < maxSkipLevels; i++)
            {
                if (freqIn != null)
                {
                    freqIndex[i] = freqIn.GetIndex();
                }
                docIndex[i] = docIn.GetIndex();
                if (posIn != null)
                {
                    posIndex[i] = posIn.GetIndex();
                }
            }
            payloadPointer = new long[maxSkipLevels];
            payloadLength = new int[maxSkipLevels];

            if (freqIn != null)
            {
                lastFreqIndex = freqIn.GetIndex();
            }
            else
            {
                lastFreqIndex = null;
            }
            lastDocIndex = docIn.GetIndex();
            if (posIn != null)
            {
                lastPosIndex = posIn.GetIndex();
            }
            else
            {
                lastPosIndex = null;
            }
        }

        internal IndexOptions indexOptions;

        internal void SetIndexOptions(IndexOptions v)
        {
            indexOptions = v;
        }

        internal void Init(long skipPointer,
                  Int32IndexInput.Index docBaseIndex,
                  Int32IndexInput.Index freqBaseIndex,
                  Int32IndexInput.Index posBaseIndex,
                  long payloadBasePointer,
                  int df,
                  bool storesPayloads)
        {

            base.Init(skipPointer, df);
            this.currentFieldStoresPayloads = storesPayloads;

            lastPayloadPointer = payloadBasePointer;

            for (int i = 0; i < m_maxNumberOfSkipLevels; i++)
            {
                docIndex[i].CopyFrom(docBaseIndex);
                if (freqIndex != null)
                {
                    freqIndex[i].CopyFrom(freqBaseIndex);
                }
                if (posBaseIndex != null)
                {
                    posIndex[i].CopyFrom(posBaseIndex);
                }
            }
            Arrays.Fill(payloadPointer, payloadBasePointer);
            Arrays.Fill(payloadLength, 0);
        }

        internal long PayloadPointer
        {
            get { return lastPayloadPointer; }
        }

        /// <summary>
        /// Returns the payload length of the payload stored just before 
        /// the doc to which the last call of <see cref="MultiLevelSkipListReader.SkipTo(int)"/> 
        /// has skipped.
        /// </summary>
        internal int PayloadLength
        {
            get { return lastPayloadLength; }
        }

        /// <exception cref="System.IO.IOException"/>
        protected override void SeekChild(int level)
        {
            base.SeekChild(level);
            payloadPointer[level] = lastPayloadPointer;
            payloadLength[level] = lastPayloadLength;
        }

        protected override void SetLastSkipData(int level)
        {
            base.SetLastSkipData(level);

            lastPayloadPointer = payloadPointer[level];
            lastPayloadLength = payloadLength[level];
            if (freqIndex != null)
            {
                lastFreqIndex.CopyFrom(freqIndex[level]);
            }
            lastDocIndex.CopyFrom(docIndex[level]);
            if (lastPosIndex != null)
            {
                lastPosIndex.CopyFrom(posIndex[level]);
            }

            if (level > 0)
            {
                if (freqIndex != null)
                {
                    freqIndex[level - 1].CopyFrom(freqIndex[level]);
                }
                docIndex[level - 1].CopyFrom(docIndex[level]);
                if (posIndex != null)
                {
                    posIndex[level - 1].CopyFrom(posIndex[level]);
                }
            }
        }

        internal Int32IndexInput.Index FreqIndex
        {
            get { return lastFreqIndex; }
        }

        internal Int32IndexInput.Index PosIndex
        {
            get { return lastPosIndex; }
        }

        internal Int32IndexInput.Index DocIndex
        {
            get { return lastDocIndex; }
        }

        /// <exception cref="System.IO.IOException"/>
        protected override int ReadSkipData(int level, IndexInput skipStream)
        {
            int delta;
            Debug.Assert(indexOptions == IndexOptions.DOCS_AND_FREQS_AND_POSITIONS || !currentFieldStoresPayloads);
            if (currentFieldStoresPayloads)
            {
                // the current field stores payloads.
                // if the doc delta is odd then we have
                // to read the current payload length
                // because it differs from the length of the
                // previous payload
                delta = skipStream.ReadVInt32();
                if ((delta & 1) != 0)
                {
                    payloadLength[level] = skipStream.ReadVInt32();
                }
                //delta >>>= 1;
                delta = (int)((uint)delta >> 1);
            }
            else
            {
                delta = skipStream.ReadVInt32();
            }
            if (indexOptions != IndexOptions.DOCS_ONLY)
            {
                freqIndex[level].Read(skipStream, false);
            }
            docIndex[level].Read(skipStream, false);
            if (indexOptions == IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)
            {
                posIndex[level].Read(skipStream, false);
                if (currentFieldStoresPayloads)
                {
                    payloadPointer[level] += skipStream.ReadVInt32();
                }
            }

            return delta;
        }
    }
}