using Lucene.Net.Codecs.Sep;
using Lucene.Net.Diagnostics;
using Lucene.Net.Store;
using System.Diagnostics;

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

    // TODO: much of this can be shared code w/ the fixed case

    /// <summary>
    /// Abstract base class that writes variable-size blocks of ints
    /// to an <see cref="IndexOutput"/>.  While this is a simple approach, a
    /// more performant approach would directly create an impl
    /// of <see cref="Int32IndexOutput"/> inside <see cref="Directory"/>.  Wrapping a generic
    /// <see cref="IndexOutput"/> will likely cost performance.
    /// <para/>
    /// NOTE: This was VariableIntBlockIndexOutput in Lucene
    /// <para/>
    /// @lucene.experimental
    /// </summary>
    /// <remarks>
    /// Naive int block API that writes vInts.  This is
    /// expected to give poor performance; it's really only for
    /// testing the pluggability.  One should typically use pfor instead. 
    /// </remarks>
    public abstract class VariableInt32BlockIndexOutput : Int32IndexOutput
    {
        protected readonly IndexOutput m_output;

        private int upto;
        private bool hitExcDuringWrite;

        // TODO what Var-Var codecs exist in practice... and what are there blocksizes like?
        // if its less than 128 we should set that as max and use byte?

        /// <summary>
        /// NOTE: <paramref name="maxBlockSize"/> must be the maximum block size 
        /// plus the max non-causal lookahead of your codec.  EG Simple9
        /// requires lookahead=1 because on seeing the Nth value
        /// it knows it must now encode the N-1 values before it. 
        /// </summary>
        protected VariableInt32BlockIndexOutput(IndexOutput output, int maxBlockSize)
        {
            this.m_output = output;
            this.m_output.WriteInt32(maxBlockSize);
        }

        /// <summary>
        /// Called one value at a time.  Return the number of
        /// buffered input values that have been written to out. 
        /// </summary>
        protected abstract int Add(int value);

        public override Int32IndexOutput.Index GetIndex()
        {
            return new Index(this);
        }

        new private class Index : Int32IndexOutput.Index
        {
            private readonly VariableInt32BlockIndexOutput outerInstance;

            public Index(VariableInt32BlockIndexOutput outerInstance)
            {
                this.outerInstance = outerInstance;
            }

            private long fp;
            private int upto;
            private long lastFP;
            private int lastUpto;

            public override void Mark()
            {
                fp = outerInstance.m_output.GetFilePointer();
                upto = outerInstance.upto;
            }

            public override void CopyFrom(Int32IndexOutput.Index other, bool copyLast)
            {
                Index idx = (Index)other;
                fp = idx.fp;
                upto = idx.upto;
                if (copyLast)
                {
                    lastFP = fp;
                    lastUpto = upto;
                }
            }

            public override void Write(DataOutput indexOut, bool absolute)
            {
                if (Debugging.AssertsEnabled) Debugging.Assert(upto >= 0);
                if (absolute)
                {
                    indexOut.WriteVInt32(upto);
                    indexOut.WriteVInt64(fp);
                }
                else if (fp == lastFP)
                {
                    // same block
                    if (Debugging.AssertsEnabled) Debugging.Assert(upto >= lastUpto);
                    int uptoDelta = upto - lastUpto;
                    indexOut.WriteVInt32(uptoDelta << 1 | 1);
                }
                else
                {
                    // new block
                    indexOut.WriteVInt32(upto << 1);
                    indexOut.WriteVInt64(fp - lastFP);
                }
                lastUpto = upto;
                lastFP = fp;
            }
        }

        public override void Write(int v)
        {
            hitExcDuringWrite = true;
            upto -= Add(v) - 1;
            hitExcDuringWrite = false;
            if (Debugging.AssertsEnabled) Debugging.Assert(upto >= 0);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    if (hitExcDuringWrite) return;

                    // stuff 0s in until the "real" data is flushed:
                    var stuffed = 0;
                    while (upto > stuffed)
                    {
                        upto -= Add(0) - 1;
                        if (Debugging.AssertsEnabled) Debugging.Assert(upto >= 0);
                        stuffed += 1;
                    }
                }
                finally
                {
                    m_output.Dispose();
                }
            }
        }
    }
}