/*
 * [The "BSD licence"]
 * Copyright (c) 2005-2008 Terence Parr
 * All rights reserved.
 *
 * Conversion to C#:
 * Copyright (c) 2008-2009 Sam Harwell, Pixel Mine, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Antlr.Runtime
{
    using System.Collections.Generic;
    using CLSCompliant = System.CLSCompliantAttribute;
    using IndexOutOfRangeException = System.IndexOutOfRangeException;
    using StringBuilder = System.Text.StringBuilder;

    /** Buffer all input tokens but do on-demand fetching of new tokens from
     *  lexer. Useful when the parser or lexer has to set context/mode info before
     *  proper lexing of future tokens. The ST template parser needs this,
     *  for example, because it has to constantly flip back and forth between
     *  inside/output templates. E.g., <names:{hi, <it>}> has to parse names
     *  as part of an expression but "hi, <it>" as a nested template.
     *
     *  You can't use this stream if you pass whitespace or other off-channel
     *  tokens to the parser. The stream can't ignore off-channel tokens.
     *  (UnbufferedTokenStream is the same way.)
     *
     *  This is not a subclass of UnbufferedTokenStream because I don't want
     *  to confuse small moving window of tokens it uses for the full buffer.
     */
    [System.Serializable]
    public class BufferedTokenStream : ITokenStream, ITokenStreamInformation
    {
        private ITokenSource _tokenSource;

        /** Record every single token pulled from the source so we can reproduce
         *  chunks of it later.  The buffer in LookaheadStream overlaps sometimes
         *  as its moving window moves through the input.  This list captures
         *  everything so we can access complete input text.
         */
        [CLSCompliant(false)]
        protected List<IToken> _tokens = new List<IToken>(100);

        /** Track the last mark() call result value for use in rewind(). */
        private int _lastMarker;

        /** The index into the tokens list of the current token (next token
         *  to consume).  tokens[p] should be LT(1).  p=-1 indicates need
         *  to initialize with first token.  The ctor doesn't get a token.
         *  First call to LT(1) or whatever gets the first token and sets p=0;
         */
        [CLSCompliant(false)]
        protected int _p = -1;

        public BufferedTokenStream()
        {
        }

        public BufferedTokenStream(ITokenSource tokenSource)
        {
            this._tokenSource = tokenSource;
        }

        public virtual ITokenSource TokenSource
        {
            get
            {
                return _tokenSource;
            }
            set
            {
                this._tokenSource = value;
                _tokens.Clear();
                _p = -1;
            }
        }

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

        /// <summary>
        /// How deep have we gone?
        /// </summary>
        public virtual int Range
        {
            get;
            protected set;
        }

        public virtual int Count
        {
            get
            {
                return _tokens.Count;
            }
        }

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

        public virtual IToken LastToken
        {
            get
            {
                return LB(1);
            }
        }

        public virtual IToken LastRealToken
        {
            get
            {
                int i = 0;
                IToken token;
                do
                {
                    i++;
                    token = LB(i);
                } while (token != null && token.Line <= 0);

                return token;
            }
        }

        public virtual int MaxLookBehind
        {
            get
            {
                return int.MaxValue;
            }
        }

        public virtual int Mark()
        {
            if (_p == -1)
                Setup();
            _lastMarker = Index;
            return _lastMarker;
        }

        public virtual void Release(int marker)
        {
            // no resources to release
        }

        public virtual void Rewind(int marker)
        {
            Seek(marker);
        }

        public virtual void Rewind()
        {
            Seek(_lastMarker);
        }

        public virtual void Reset()
        {
            _p = 0;
            _lastMarker = 0;
        }

        public virtual void Seek(int index)
        {
            _p = index;
        }

        /** Move the input pointer to the next incoming token.  The stream
         *  must become active with LT(1) available.  consume() simply
         *  moves the input pointer so that LT(1) points at the next
         *  input symbol. Consume at least one token.
         *
         *  Walk past any token not on the channel the parser is listening to.
         */
        public virtual void Consume()
        {
            if (_p == -1)
                Setup();
            _p++;
            Sync(_p);
        }

        /** Make sure index i in tokens has a token. */
        protected virtual void Sync(int i)
        {
            int n = i - _tokens.Count + 1; // how many more elements we need?
            if (n > 0)
                Fetch(n);
        }

        /** add n elements to buffer */
        protected virtual void Fetch(int n)
        {
            for (int i = 0; i < n; i++)
            {
                IToken t = TokenSource.NextToken();
                t.TokenIndex = _tokens.Count;
                _tokens.Add(t);
                if (t.Type == CharStreamConstants.EndOfFile)
                    break;
            }
        }

        public virtual IToken Get(int i)
        {
            if (i < 0 || i >= _tokens.Count)
            {
                throw new IndexOutOfRangeException("token index " + i + " out of range 0.." + (_tokens.Count - 1));
            }
            return _tokens[i];
        }

#if false // why is this different from GetTokens(start, count) ?
        /// <summary>
        /// Get all tokens from start..(start+count-1) inclusively
        /// </summary>
        public virtual List<IToken> Get(int start, int count)
        {
            if (start < 0)
                throw new ArgumentOutOfRangeException("start");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            if (start + count >= _tokens.Count)
                throw new ArgumentException();

            if (_p == -1)
                Setup();

            List<IToken> subset = new List<IToken>(count);
            for (int i = 0; i < count; i++)
            {
                IToken token = _tokens[i];
                if (token.Type == TokenTypes.EndOfFile)
                    break;

                subset.Add(token);
            }

            return subset;
        }
#endif

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

        protected virtual IToken LB(int k)
        {
            if ((_p - k) < 0)
                return null;

            return _tokens[_p - k];
        }

        public virtual IToken LT(int k)
        {
            if (_p == -1)
                Setup();
            if (k == 0)
                return null;
            if (k < 0)
                return LB(-k);

            int i = _p + k - 1;
            Sync(i);
            if (i >= _tokens.Count)
            {
                // EOF must be last token
                return _tokens[_tokens.Count - 1];
            }

            if (i > Range)
                Range = i;

            return _tokens[_p + k - 1];
        }

        protected virtual void Setup()
        {
            Sync(0);
            _p = 0;
        }

        public virtual List<IToken> GetTokens()
        {
            return _tokens;
        }

        public virtual List<IToken> GetTokens(int start, int stop)
        {
            return GetTokens(start, stop, default(BitSet));
        }

        /** Given a start and stop index, return a List of all tokens in
         *  the token type BitSet.  Return null if no tokens were found.  This
         *  method looks at both on and off channel tokens.
         */
        public virtual List<IToken> GetTokens(int start, int stop, BitSet types)
        {
            if (_p == -1)
                Setup();
            if (stop >= _tokens.Count)
                stop = _tokens.Count - 1;
            if (start < 0)
                start = 0;
            if (start > stop)
                return null;

            // list = tokens[start:stop]:{Token t, t.getType() in types}
            List<IToken> filteredTokens = new List<IToken>();
            for (int i = start; i <= stop; i++)
            {
                IToken t = _tokens[i];
                if (types == null || types.Member(t.Type))
                {
                    filteredTokens.Add(t);
                }
            }
            if (filteredTokens.Count == 0)
            {
                filteredTokens = null;
            }
            return filteredTokens;
        }

        public virtual List<IToken> GetTokens(int start, int stop, IEnumerable<int> types)
        {
            return GetTokens(start, stop, new BitSet(types));
        }

        public virtual List<IToken> GetTokens(int start, int stop, int ttype)
        {
            return GetTokens(start, stop, BitSet.Of(ttype));
        }

        public override string ToString()
        {
            if (_p == -1)
                Setup();

            Fill();
            return ToString(0, _tokens.Count - 1);
        }

        public virtual string ToString(int start, int stop)
        {
            if (start < 0 || stop < 0)
                return null;
            if (_p == -1)
                Setup();
            if (stop >= _tokens.Count)
                stop = _tokens.Count - 1;

            StringBuilder buf = new StringBuilder();
            for (int i = start; i <= stop; i++)
            {
                IToken t = _tokens[i];
                if (t.Type == CharStreamConstants.EndOfFile)
                    break;
                buf.Append(t.Text);
            }

            return buf.ToString();
        }

        public virtual string ToString(IToken start, IToken stop)
        {
            if (start != null && stop != null)
            {
                return ToString(start.TokenIndex, stop.TokenIndex);
            }
            return null;
        }

        public virtual void Fill()
        {
            if (_p == -1)
                Setup();

            if (_tokens[_p].Type == CharStreamConstants.EndOfFile)
                return;

            int i = _p + 1;
            Sync(i);
            while (_tokens[i].Type != CharStreamConstants.EndOfFile)
            {
                i++;
                Sync(i);
            }
        }
    }
}
