using Lucene.Net.Diagnostics;
using Lucene.Net.Util;
using System;
using System.Diagnostics;
using System.IO;

namespace Lucene.Net.Analysis.Util
{
    /*
     * 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>
    /// Acts like a forever growing <see cref="T:char[]"/> as you read
    /// characters into it from the provided reader, but
    /// internally it uses a circular buffer to only hold the
    /// characters that haven't been freed yet.  This is like a
    /// PushbackReader, except you don't have to specify
    /// up-front the max size of the buffer, but you do have to
    /// periodically call <see cref="FreeBefore"/>. 
    /// </summary>
    public sealed class RollingCharBuffer
    {
        private TextReader reader;

        private char[] buffer = new char[512];

        // Next array index to write to in buffer:
        private int nextWrite;

        // Next absolute position to read from reader:
        private int nextPos;

        // How many valid chars (wrapped) are in the buffer:
        private int count;

        // True if we hit EOF
        private bool end;

        /// <summary>
        /// Clear array and switch to new reader. </summary>
        public void Reset(TextReader reader)
        {
            this.reader = reader;
            nextPos = 0;
            nextWrite = 0;
            count = 0;
            end = false;
        }

        /// <summary>
        /// Absolute position read.  NOTE: pos must not jump
        /// ahead by more than 1!  Ie, it's OK to read arbitarily
        /// far back (just not prior to the last <see cref="FreeBefore(int)"/>, 
        /// but NOT ok to read arbitrarily far
        /// ahead.  Returns -1 if you hit EOF.
        /// </summary>
        public int Get(int pos)
        {
            //System.out.println("    Get pos=" + pos + " nextPos=" + nextPos + " count=" + count);
            if (pos == nextPos)
            {
                if (end)
                {
                    return -1;
                }
                if (count == buffer.Length)
                {
                    // Grow
                    var newBuffer = new char[ArrayUtil.Oversize(1 + count, RamUsageEstimator.NUM_BYTES_CHAR)];
                    //System.out.println(Thread.currentThread().getName() + ": cb grow " + newBuffer.length);
                    Array.Copy(buffer, nextWrite, newBuffer, 0, buffer.Length - nextWrite);
                    Array.Copy(buffer, 0, newBuffer, buffer.Length - nextWrite, nextWrite);
                    nextWrite = buffer.Length;
                    buffer = newBuffer;
                }
                if (nextWrite == buffer.Length)
                {
                    nextWrite = 0;
                }

                int toRead = buffer.Length - Math.Max(count, nextWrite);
                int readCount = reader.Read(buffer, nextWrite, toRead);
                if (readCount <= 0)
                {
                    end = true;
                    return -1;
                }
                int ch = buffer[nextWrite];
                nextWrite += readCount;
                count += readCount;
                nextPos += readCount;
                return ch;
            }
            else
            {
                if (Debugging.AssertsEnabled)
                {
                    // Cannot read from future (except by 1):
                    Debugging.Assert(pos < nextPos);

                    // Cannot read from already freed past:
                    Debugging.Assert(nextPos - pos <= count, "nextPos={0} pos={1} count={2}", nextPos, pos, count);
                }

                return buffer[GetIndex(pos)];
            }
        }

        // For assert:
        private bool InBounds(int pos)
        {
            return pos >= 0 && pos < nextPos && pos >= nextPos - count;
        }

        private int GetIndex(int pos)
        {
            int index = nextWrite - (nextPos - pos);
            if (index < 0)
            {
                // Wrap:
                index += buffer.Length;
                if (Debugging.AssertsEnabled) Debugging.Assert(index >= 0);
            }
            return index;
        }

        public char[] Get(int posStart, int length)
        {
            if (Debugging.AssertsEnabled)
            {
                Debugging.Assert(length > 0);
                Debugging.Assert(InBounds(posStart), "posStart={0} length={1}", posStart, length);
            }
            //System.out.println("    buffer.Get posStart=" + posStart + " len=" + length);

            int startIndex = GetIndex(posStart);
            int endIndex = GetIndex(posStart + length);
            //System.out.println("      startIndex=" + startIndex + " endIndex=" + endIndex);

            var result = new char[length];
            if (endIndex >= startIndex && length < buffer.Length)
            {
                Array.Copy(buffer, startIndex, result, 0, endIndex - startIndex);
            }
            else
            {
                // Wrapped:
                int part1 = buffer.Length - startIndex;
                Array.Copy(buffer, startIndex, result, 0, part1);
                Array.Copy(buffer, 0, result, buffer.Length - startIndex, length - part1);
            }
            return result;
        }

        /// <summary>
        /// Call this to notify us that no chars before this
        /// absolute position are needed anymore. 
        /// </summary>
        public void FreeBefore(int pos)
        {
            if (Debugging.AssertsEnabled)
            {
                Debugging.Assert(pos >= 0);
                Debugging.Assert(pos <= nextPos);
            }
            int newCount = nextPos - pos;
            if (Debugging.AssertsEnabled)
            {
                Debugging.Assert(newCount <= count, "newCount={0} count={1}", newCount, count);
                Debugging.Assert(newCount <= buffer.Length, "newCount={0} buf.length={1}", newCount, buffer.Length);
            }
            count = newCount;
        }
    }
}