/*
 * [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 Antlr.Runtime.Tree;

    using ArgumentException = System.ArgumentException;
    using ArgumentNullException = System.ArgumentNullException;
    using Exception = System.Exception;
    using SerializationInfo = System.Runtime.Serialization.SerializationInfo;
    using StreamingContext = System.Runtime.Serialization.StreamingContext;

    /** <summary>The root of the ANTLR exception hierarchy.</summary>
     *
     *  <remarks>
     *  To avoid English-only error messages and to generally make things
     *  as flexible as possible, these exceptions are not created with strings,
     *  but rather the information necessary to generate an error.  Then
     *  the various reporting methods in Parser and Lexer can be overridden
     *  to generate a localized error message.  For example, MismatchedToken
     *  exceptions are built with the expected token type.
     *  So, don't expect getMessage() to return anything.
     *
     *  Note that as of Java 1.4, you can access the stack trace, which means
     *  that you can compute the complete trace of rules from the start symbol.
     *  This gives you considerable context information with which to generate
     *  useful error messages.
     *
     *  ANTLR generates code that throws exceptions upon recognition error and
     *  also generates code to catch these exceptions in each rule.  If you
     *  want to quit upon first error, you can turn off the automatic error
     *  handling mechanism using rulecatch action, but you still need to
     *  override methods mismatch and recoverFromMismatchSet.
     *
     *  In general, the recognition exceptions can track where in a grammar a
     *  problem occurred and/or what was the expected input.  While the parser
     *  knows its state (such as current input symbol and line info) that
     *  state can change before the exception is reported so current token index
     *  is computed and stored at exception time.  From this info, you can
     *  perhaps print an entire line of input not just a single token, for example.
     *  Better to just say the recognizer had a problem and then let the parser
     *  figure out a fancy report.
     *  </remarks>
     */
    [System.Serializable]
    public class RecognitionException : Exception
    {
        /** <summary>What input stream did the error occur in?</summary> */
        private IIntStream _input;

        /** <summary>What is index of token/char were we looking at when the error occurred?</summary> */
        private int _index;

        /** <summary>
         *  The current Token when an error occurred.  Since not all streams
         *  can retrieve the ith Token, we have to track the Token object.
         *  For parsers.  Even when it's a tree parser, token might be set.
         *  </summary>
         */
        private IToken _token;

        /** <summary>
         *  If this is a tree parser exception, node is set to the node with
         *  the problem.
         *  </summary>
         */
        private object _node;

        /** <summary>The current char when an error occurred. For lexers.</summary> */
        private int _c;

        /** <summary>
         *  Track the line (1-based) at which the error occurred in case this is
         *  generated from a lexer.  We need to track this since the
         *  unexpected char doesn't carry the line info.
         *  </summary>
         */
        private int _line;

        /// <summary>
        /// The 0-based index into the line where the error occurred.
        /// </summary>
        private int _charPositionInLine;

        /** <summary>
         *  If you are parsing a tree node stream, you will encounter som
         *  imaginary nodes w/o line/col info.  We now search backwards looking
         *  for most recent token with line/col info, but notify getErrorHeader()
         *  that info is approximate.
         *  </summary>
         */
        private bool _approximateLineInfo;

        /** <summary>Used for remote debugger deserialization</summary> */
        public RecognitionException()
            : this("A recognition error occurred.", null, null)
        {
        }

        public RecognitionException( IIntStream input )
            : this("A recognition error occurred.", input, null)
        {
        }

        public RecognitionException(string message)
            : this(message, null, null)
        {
        }

        public RecognitionException(string message, IIntStream input)
            : this(message, input, null)
        {
        }

        public RecognitionException(string message, Exception innerException)
            : this(message, null, innerException)
        {
        }

        public RecognitionException(string message, IIntStream input, Exception innerException)
            : base(message, innerException)
        {
            this._input = input;
            if (input != null)
            {
                this._index = input.Index;
                if (input is ITokenStream)
                {
                    this._token = ((ITokenStream)input).LT(1);
                    this._line = _token.Line;
                    this._charPositionInLine = _token.CharPositionInLine;
                }

                ITreeNodeStream tns = input as ITreeNodeStream;
                if (tns != null)
                {
                    ExtractInformationFromTreeNodeStream(tns);
                }
                else
                {
                    ICharStream charStream = input as ICharStream;
                    if (charStream != null)
                    {
                        this._c = input.LA(1);
                        this._line = ((ICharStream)input).Line;
                        this._charPositionInLine = ((ICharStream)input).CharPositionInLine;
                    }
                    else
                    {
                        this._c = input.LA(1);
                    }
                }
            }
        }

        protected RecognitionException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            _index = info.GetInt32("Index");
            _c = info.GetInt32("C");
            _line = info.GetInt32("Line");
            _charPositionInLine = info.GetInt32("CharPositionInLine");
            _approximateLineInfo = info.GetBoolean("ApproximateLineInfo");
        }

        /** <summary>Return the token type or char of the unexpected input element</summary> */
        public virtual int UnexpectedType
        {
            get
            {
                if ( _input is ITokenStream )
                {
                    return _token.Type;
                }

                ITreeNodeStream treeNodeStream = _input as ITreeNodeStream;
                if ( treeNodeStream != null )
                {
                    ITreeAdaptor adaptor = treeNodeStream.TreeAdaptor;
                    return adaptor.GetType( _node );
                }

                return _c;
            }
        }

        public bool ApproximateLineInfo
        {
            get
            {
                return _approximateLineInfo;
            }
            protected set
            {
                _approximateLineInfo = value;
            }
        }

        public IIntStream Input
        {
            get
            {
                return _input;
            }
            protected set
            {
                _input = value;
            }
        }

        public IToken Token
        {
            get
            {
                return _token;
            }
            set
            {
                _token = value;
            }
        }

        public object Node
        {
            get
            {
                return _node;
            }
            protected set
            {
                _node = value;
            }
        }

        public int Character
        {
            get
            {
                return _c;
            }
            protected set
            {
                _c = value;
            }
        }

        public int Index
        {
            get
            {
                return _index;
            }
            protected set
            {
                _index = value;
            }
        }

        public int Line
        {
            get
            {
                return _line;
            }
            set
            {
                _line = value;
            }
        }

        public int CharPositionInLine
        {
            get
            {
                return _charPositionInLine;
            }
            set
            {
                _charPositionInLine = value;
            }
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            base.GetObjectData(info, context);
            info.AddValue("Index", _index);
            info.AddValue("C", _c);
            info.AddValue("Line", _line);
            info.AddValue("CharPositionInLine", _charPositionInLine);
            info.AddValue("ApproximateLineInfo", _approximateLineInfo);
        }

        protected virtual void ExtractInformationFromTreeNodeStream(ITreeNodeStream input)
        {
            this._node = input.LT(1);
            ITokenStreamInformation streamInformation = input as ITokenStreamInformation;
            if (streamInformation != null)
            {
                IToken lastToken = streamInformation.LastToken;
                IToken lastRealToken = streamInformation.LastRealToken;
                if (lastRealToken != null)
                {
                    this._token = lastRealToken;
                    this._line = lastRealToken.Line;
                    this._charPositionInLine = lastRealToken.CharPositionInLine;
                    this._approximateLineInfo = lastRealToken.Equals(lastToken);
                }
            }
            else
            {
                ITreeAdaptor adaptor = input.TreeAdaptor;
                IToken payload = adaptor.GetToken(_node);
                if (payload != null)
                {
                    this._token = payload;
                    if (payload.Line <= 0)
                    {
                        // imaginary node; no line/pos info; scan backwards
                        int i = -1;
                        object priorNode = input.LT(i);
                        while (priorNode != null)
                        {
                            IToken priorPayload = adaptor.GetToken(priorNode);
                            if (priorPayload != null && priorPayload.Line > 0)
                            {
                                // we found the most recent real line / pos info
                                this._line = priorPayload.Line;
                                this._charPositionInLine = priorPayload.CharPositionInLine;
                                this._approximateLineInfo = true;
                                break;
                            }
                            --i;
                            try
                            {
                                priorNode = input.LT(i);
                            }
                            catch (ArgumentException)
                            {
                                priorNode = null;
                            }
                        }
                    }
                    else
                    {
                        // node created from real token
                        this._line = payload.Line;
                        this._charPositionInLine = payload.CharPositionInLine;
                    }
                }
                else if (this._node is Tree.ITree)
                {
                    this._line = ((Tree.ITree)this._node).Line;
                    this._charPositionInLine = ((Tree.ITree)this._node).CharPositionInLine;
                    if (this._node is CommonTree)
                    {
                        this._token = ((CommonTree)this._node).Token;
                    }
                }
                else
                {
                    int type = adaptor.GetType(this._node);
                    string text = adaptor.GetText(this._node);
                    this._token = new CommonToken(type, text);
                }
            }
        }
    }
}