/* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
 * Use of this file is governed by the BSD 3-clause license that
 * can be found in the LICENSE.txt file in the project root.
 */
using System;
using System.Text;
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Sharpen;

namespace Antlr4.Runtime
{
    public class UnbufferedTokenStream : ITokenStream
    {
        private ITokenSource _tokenSource;

        /// <summary>A moving window buffer of the data being scanned.</summary>
        /// <remarks>
        /// A moving window buffer of the data being scanned. While there's a marker,
        /// we keep adding to buffer. Otherwise,
        /// <see cref="Consume()">consume()</see>
        /// resets so
        /// we start filling at index 0 again.
        /// </remarks>
        protected internal IToken[] tokens;

        /// <summary>
        /// The number of tokens currently in
        /// <see cref="tokens">tokens</see>
        /// .
        /// <p>This is not the buffer capacity, that's
        /// <c>tokens.length</c>
        /// .</p>
        /// </summary>
        protected internal int n;

        /// <summary>
        /// 0..n-1 index into
        /// <see cref="tokens">tokens</see>
        /// of next token.
        /// <p>The
        /// <c>LT(1)</c>
        /// token is
        /// <c>tokens[p]</c>
        /// . If
        /// <c>p == n</c>
        /// , we are
        /// out of buffered tokens.</p>
        /// </summary>
        protected internal int p = 0;

        /// <summary>
        /// Count up with
        /// <see cref="Mark()">mark()</see>
        /// and down with
        /// <see cref="Release(int)">release()</see>
        /// . When we
        /// <c>release()</c>
        /// the last mark,
        /// <c>numMarkers</c>
        /// reaches 0 and we reset the buffer. Copy
        /// <c>tokens[p]..tokens[n-1]</c>
        /// to
        /// <c>tokens[0]..tokens[(n-1)-p]</c>
        /// .
        /// </summary>
        protected internal int numMarkers = 0;

        /// <summary>
        /// This is the
        /// <c>LT(-1)</c>
        /// token for the current position.
        /// </summary>
        protected internal IToken lastToken;

        /// <summary>
        /// When
        /// <c>numMarkers &gt; 0</c>
        /// , this is the
        /// <c>LT(-1)</c>
        /// token for the
        /// first token in
        /// <see cref="tokens"/>
        /// . Otherwise, this is
        /// <see langword="null"/>
        /// .
        /// </summary>
        protected internal IToken lastTokenBufferStart;

        /// <summary>Absolute token index.</summary>
        /// <remarks>
        /// Absolute token index. It's the index of the token about to be read via
        /// <c>LT(1)</c>
        /// . Goes from 0 to the number of tokens in the entire stream,
        /// although the stream size is unknown before the end is reached.
        /// <p>This value is used to set the token indexes if the stream provides tokens
        /// that implement
        /// <see cref="IWritableToken"/>
        /// .</p>
        /// </remarks>
        protected internal int currentTokenIndex = 0;

        public UnbufferedTokenStream(ITokenSource tokenSource)
            : this(tokenSource, 256)
        {
        }

        public UnbufferedTokenStream(ITokenSource tokenSource, int bufferSize)
        {
            this.TokenSource = tokenSource;
            this.tokens = new IToken[bufferSize];
            n = 0;
            Fill(1);
        }

        // prime the pump
        public virtual IToken Get(int i)
        {
            int bufferStartIndex = GetBufferStartIndex();
            if (i < bufferStartIndex || i >= bufferStartIndex + n)
            {
                throw new ArgumentOutOfRangeException("get(" + i + ") outside buffer: " + bufferStartIndex + ".." + (bufferStartIndex + n));
            }
            return tokens[i - bufferStartIndex];
        }

        public virtual IToken LT(int i)
        {
            if (i == -1)
            {
                return lastToken;
            }
            Sync(i);
            int index = p + i - 1;
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("LT(" + i + ") gives negative index");
            }
            if (index >= n)
            {
                System.Diagnostics.Debug.Assert(n > 0 && tokens[n - 1].Type == TokenConstants.EOF);
                return tokens[n - 1];
            }
            return tokens[index];
        }

        public virtual int LA(int i)
        {
            return LT(i).Type;
        }

        public virtual ITokenSource TokenSource
        {
            get
            {
                return _tokenSource;
            }
			set
			{
				_tokenSource = value;
			}
        }

        [return: NotNull]
        public virtual string GetText()
        {
            return string.Empty;
        }

        [return: NotNull]
        public virtual string GetText(RuleContext ctx)
        {
            return GetText(ctx.SourceInterval);
        }

        [return: NotNull]
        public virtual string GetText(IToken start, IToken stop)
        {
            if (start != null && stop != null)
            {
                return GetText(Interval.Of(start.TokenIndex, stop.TokenIndex));
            }
            throw new NotSupportedException("The specified start and stop symbols are not supported.");
        }

        public virtual void Consume()
        {
            if (LA(1) == TokenConstants.EOF)
            {
                throw new InvalidOperationException("cannot consume EOF");
            }
            // buf always has at least tokens[p==0] in this method due to ctor
            lastToken = tokens[p];
            // track last token for LT(-1)
            // if we're at last token and no markers, opportunity to flush buffer
            if (p == n - 1 && numMarkers == 0)
            {
                n = 0;
                p = -1;
                // p++ will leave this at 0
                lastTokenBufferStart = lastToken;
            }
            p++;
            currentTokenIndex++;
            Sync(1);
        }

        /// <summary>
        /// Make sure we have 'need' elements from current position
        /// <see cref="p">p</see>
        /// . Last valid
        /// <c>p</c>
        /// index is
        /// <c>tokens.length-1</c>
        /// .
        /// <c>p+need-1</c>
        /// is the tokens index 'need' elements
        /// ahead.  If we need 1 element,
        /// <c>(p+1-1)==p</c>
        /// must be less than
        /// <c>tokens.length</c>
        /// .
        /// </summary>
        protected internal virtual void Sync(int want)
        {
            int need = (p + want - 1) - n + 1;
            // how many more elements we need?
            if (need > 0)
            {
                Fill(need);
            }
        }

        /// <summary>
        /// Add
        /// <paramref name="n"/>
        /// elements to the buffer. Returns the number of tokens
        /// actually added to the buffer. If the return value is less than
        /// <paramref name="n"/>
        /// ,
        /// then EOF was reached before
        /// <paramref name="n"/>
        /// tokens could be added.
        /// </summary>
        protected internal virtual int Fill(int n)
        {
            for (int i = 0; i < n; i++)
            {
                if (this.n > 0 && tokens[this.n - 1].Type == TokenConstants.EOF)
                {
                    return i;
                }
                IToken t = TokenSource.NextToken();
                Add(t);
            }
            return n;
        }

        protected internal virtual void Add(IToken t)
        {
            if (n >= tokens.Length)
            {
                tokens = Arrays.CopyOf(tokens, tokens.Length * 2);
            }
            if (t is IWritableToken)
            {
                ((IWritableToken)t).TokenIndex = GetBufferStartIndex() + n;
            }
            tokens[n++] = t;
        }

        /// <summary>Return a marker that we can release later.</summary>
        /// <remarks>
        /// Return a marker that we can release later.
        /// <p>The specific marker value used for this class allows for some level of
        /// protection against misuse where
        /// <c>seek()</c>
        /// is called on a mark or
        /// <c>release()</c>
        /// is called in the wrong order.</p>
        /// </remarks>
        public virtual int Mark()
        {
            if (numMarkers == 0)
            {
                lastTokenBufferStart = lastToken;
            }
            int mark = -numMarkers - 1;
            numMarkers++;
            return mark;
        }

        public virtual void Release(int marker)
        {
            int expectedMark = -numMarkers;
            if (marker != expectedMark)
            {
                throw new InvalidOperationException("release() called with an invalid marker.");
            }
            numMarkers--;
            if (numMarkers == 0)
            {
                // can we release buffer?
                if (p > 0)
                {
                    // Copy tokens[p]..tokens[n-1] to tokens[0]..tokens[(n-1)-p], reset ptrs
                    // p is last valid token; move nothing if p==n as we have no valid char
                    System.Array.Copy(tokens, p, tokens, 0, n - p);
                    // shift n-p tokens from p to 0
                    n = n - p;
                    p = 0;
                }
                lastTokenBufferStart = lastToken;
            }
        }

        public virtual int Index
        {
            get
            {
                return currentTokenIndex;
            }
        }

        public virtual void Seek(int index)
        {
            // seek to absolute index
            if (index == currentTokenIndex)
            {
                return;
            }
            if (index > currentTokenIndex)
            {
                Sync(index - currentTokenIndex);
                index = Math.Min(index, GetBufferStartIndex() + n - 1);
            }
            int bufferStartIndex = GetBufferStartIndex();
            int i = index - bufferStartIndex;
            if (i < 0)
            {
                throw new ArgumentException("cannot seek to negative index " + index);
            }
            else
            {
                if (i >= n)
                {
                    throw new NotSupportedException("seek to index outside buffer: " + index + " not in " + bufferStartIndex + ".." + (bufferStartIndex + n));
                }
            }
            p = i;
            currentTokenIndex = index;
            if (p == 0)
            {
                lastToken = lastTokenBufferStart;
            }
            else
            {
                lastToken = tokens[p - 1];
            }
        }

        public virtual int Size
        {
            get
            {
                throw new NotSupportedException("Unbuffered stream cannot know its size");
            }
        }

        public virtual string SourceName
        {
            get
            {
                return TokenSource.SourceName;
            }
        }

        [return: NotNull]
        public virtual string GetText(Interval interval)
        {
            int bufferStartIndex = GetBufferStartIndex();
            int bufferStopIndex = bufferStartIndex + tokens.Length - 1;
            int start = interval.a;
            int stop = interval.b;
            if (start < bufferStartIndex || stop > bufferStopIndex)
            {
                throw new NotSupportedException("interval " + interval + " not in token buffer window: " + bufferStartIndex + ".." + bufferStopIndex);
            }
            int a = start - bufferStartIndex;
            int b = stop - bufferStartIndex;
            StringBuilder buf = new StringBuilder();
            for (int i = a; i <= b; i++)
            {
                IToken t = tokens[i];
                buf.Append(t.Text);
            }
            return buf.ToString();
        }

        protected internal int GetBufferStartIndex()
        {
            return currentTokenIndex - p;
        }
    }
}
