/*
 * [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 ConditionalAttribute = System.Diagnostics.ConditionalAttribute;
    using Console = System.Console;
    using IDebugEventListener = Antlr.Runtime.Debug.IDebugEventListener;

    public delegate int SpecialStateTransitionHandler( DFA dfa, int s, IIntStream input );

    /** <summary>A DFA implemented as a set of transition tables.</summary>
     *
     *  <remarks>
     *  Any state that has a semantic predicate edge is special; those states
     *  are generated with if-then-else structures in a specialStateTransition()
     *  which is generated by cyclicDFA template.
     *
     *  There are at most 32767 states (16-bit signed short).
     *  Could get away with byte sometimes but would have to generate different
     *  types and the simulation code too.  For a point of reference, the Java
     *  lexer's Tokens rule DFA has 326 states roughly.
     *  </remarks>
     */
    public class DFA
    {
        protected short[] eot;
        protected short[] eof;
        protected char[] min;
        protected char[] max;
        protected short[] accept;
        protected short[] special;
        protected short[][] transition;

        protected int decisionNumber;

        /** <summary>Which recognizer encloses this DFA?  Needed to check backtracking</summary> */
        protected BaseRecognizer recognizer;

        public readonly bool debug = false;

        public DFA()
            : this( new SpecialStateTransitionHandler( SpecialStateTransitionDefault ) )
        {
        }

        public DFA( SpecialStateTransitionHandler specialStateTransition )
        {
            this.SpecialStateTransition = specialStateTransition ?? new SpecialStateTransitionHandler( SpecialStateTransitionDefault );
        }

        public virtual string Description
        {
            get
            {
                return "n/a";
            }
        }

        /** <summary>
         *  From the input stream, predict what alternative will succeed
         *  using this DFA (representing the covering regular approximation
         *  to the underlying CFL).  Return an alternative number 1..n.  Throw
         *  an exception upon error.
         *  </summary>
         */
        public virtual int Predict( IIntStream input )
        {
            if ( debug )
            {
                Console.Error.WriteLine( "Enter DFA.predict for decision " + decisionNumber );
            }
            int mark = input.Mark(); // remember where decision started in input
            int s = 0; // we always start at s0
            try
            {
                for ( ; ; )
                {
                    if ( debug )
                        Console.Error.WriteLine( "DFA " + decisionNumber + " state " + s + " LA(1)=" + (char)input.LA( 1 ) + "(" + input.LA( 1 ) +
                                           "), index=" + input.Index );
                    int specialState = special[s];
                    if ( specialState >= 0 )
                    {
                        if ( debug )
                        {
                            Console.Error.WriteLine( "DFA " + decisionNumber +
                                " state " + s + " is special state " + specialState );
                        }
                        s = SpecialStateTransition( this, specialState, input );
                        if ( debug )
                        {
                            Console.Error.WriteLine( "DFA " + decisionNumber +
                                " returns from special state " + specialState + " to " + s );
                        }
                        if ( s == -1 )
                        {
                            NoViableAlt( s, input );
                            return 0;
                        }
                        input.Consume();
                        continue;
                    }
                    if ( accept[s] >= 1 )
                    {
                        if ( debug )
                            Console.Error.WriteLine( "accept; predict " + accept[s] + " from state " + s );
                        return accept[s];
                    }
                    // look for a normal char transition
                    char c = (char)input.LA( 1 ); // -1 == \uFFFF, all tokens fit in 65000 space
                    if ( c >= min[s] && c <= max[s] )
                    {
                        int snext = transition[s][c - min[s]]; // move to next state
                        if ( snext < 0 )
                        {
                            // was in range but not a normal transition
                            // must check EOT, which is like the else clause.
                            // eot[s]>=0 indicates that an EOT edge goes to another
                            // state.
                            if ( eot[s] >= 0 )
                            {  // EOT Transition to accept state?
                                if ( debug )
                                    Console.Error.WriteLine( "EOT transition" );
                                s = eot[s];
                                input.Consume();
                                // TODO: I had this as return accept[eot[s]]
                                // which assumed here that the EOT edge always
                                // went to an accept...faster to do this, but
                                // what about predicated edges coming from EOT
                                // target?
                                continue;
                            }
                            NoViableAlt( s, input );
                            return 0;
                        }
                        s = snext;
                        input.Consume();
                        continue;
                    }
                    if ( eot[s] >= 0 )
                    {  // EOT Transition?
                        if ( debug )
                            Console.Error.WriteLine( "EOT transition" );
                        s = eot[s];
                        input.Consume();
                        continue;
                    }
                    if ( c == unchecked( (char)TokenTypes.EndOfFile ) && eof[s] >= 0 )
                    {  // EOF Transition to accept state?
                        if ( debug )
                            Console.Error.WriteLine( "accept via EOF; predict " + accept[eof[s]] + " from " + eof[s] );
                        return accept[eof[s]];
                    }
                    // not in range and not EOF/EOT, must be invalid symbol
                    if ( debug )
                    {
                        Console.Error.WriteLine( "min[" + s + "]=" + min[s] );
                        Console.Error.WriteLine( "max[" + s + "]=" + max[s] );
                        Console.Error.WriteLine( "eot[" + s + "]=" + eot[s] );
                        Console.Error.WriteLine( "eof[" + s + "]=" + eof[s] );
                        for ( int p = 0; p < transition[s].Length; p++ )
                        {
                            Console.Error.Write( transition[s][p] + " " );
                        }
                        Console.Error.WriteLine();
                    }
                    NoViableAlt( s, input );
                    return 0;
                }
            }
            finally
            {
                input.Rewind( mark );
            }
        }

        protected virtual void NoViableAlt( int s, IIntStream input )
        {
            if ( recognizer.state.backtracking > 0 )
            {
                recognizer.state.failed = true;
                return;
            }
            NoViableAltException nvae =
                new NoViableAltException( Description,
                                         decisionNumber,
                                         s,
                                         input );
            Error( nvae );
            throw nvae;
        }

        /** <summary>A hook for debugging interface</summary> */
        public virtual void Error( NoViableAltException nvae )
        {
        }

        public SpecialStateTransitionHandler SpecialStateTransition
        {
            get;
            private set;
        }
        //public virtual int specialStateTransition( int s, IntStream input )
        //{
        //    return -1;
        //}

        static int SpecialStateTransitionDefault( DFA dfa, int s, IIntStream input )
        {
            return -1;
        }

        /** <summary>
         *  Given a String that has a run-length-encoding of some unsigned shorts
         *  like "\1\2\3\9", convert to short[] {2,9,9,9}.  We do this to avoid
         *  static short[] which generates so much init code that the class won't
         *  compile. :(
         *  </summary>
         */
        public static short[] UnpackEncodedString( string encodedString )
        {
            // walk first to find how big it is.
            int size = 0;
            for ( int i = 0; i < encodedString.Length; i += 2 )
            {
                size += encodedString[i];
            }
            short[] data = new short[size];
            int di = 0;
            for ( int i = 0; i < encodedString.Length; i += 2 )
            {
                char n = encodedString[i];
                char v = encodedString[i + 1];
                // add v n times to data
                for ( int j = 1; j <= n; j++ )
                {
                    data[di++] = (short)v;
                }
            }
            return data;
        }

        /** <summary>Hideous duplication of code, but I need different typed arrays out :(</summary> */
        public static char[] UnpackEncodedStringToUnsignedChars( string encodedString )
        {
            // walk first to find how big it is.
            int size = 0;
            for ( int i = 0; i < encodedString.Length; i += 2 )
            {
                size += encodedString[i];
            }
            char[] data = new char[size];
            int di = 0;
            for ( int i = 0; i < encodedString.Length; i += 2 )
            {
                char n = encodedString[i];
                char v = encodedString[i + 1];
                // add v n times to data
                for ( int j = 1; j <= n; j++ )
                {
                    data[di++] = v;
                }
            }
            return data;
        }

        [Conditional("ANTLR_DEBUG")]
        protected virtual void DebugRecognitionException(RecognitionException ex)
        {
            IDebugEventListener dbg = recognizer.DebugListener;
            if (dbg != null)
                dbg.RecognitionException(ex);
        }
    }
}