import java.util.*;
import java.io.*;
import antlr.CommonAST;
import antlr.collections.AST;
import antlr.RecognitionException;
import antlr.TokenStreamException;
import antlr.TokenStreamIOException;
import jamaica.*;

/** Interpreter routines that is called directly from the tree walker.
 *
 * @author Hanhua Feng - hf2048@columbia.edu
 * @version $Id: MxInterpreter.java,v 1.27 2003/05/12 21:42:43 hanhua Exp $
 */
class MxInterpreter {
    MxSymbolTable symt;

    final static int fc_none = 0;
    final static int fc_break = 1;
    final static int fc_continue = 2;
    final static int fc_return = 3;

    private int control = fc_none;
    private String label;

    private Random random = new Random();

    public MxInterpreter() {
        symt = new MxSymbolTable( null, null );
        registerInternal();
    }

    public MxDataType[] convertExprList( Vector v ) {
        /* Note: expr list can be empty */
        MxDataType[] x = new MxDataType[v.size()];
        for ( int i=0; i<x.length; i++ )
            x[i] = (MxDataType) v.elementAt( i );
        return x;
    }

    public static String[] convertVarList( Vector v ) {
        /* Note: var list can be empty */
        String[] sv = new String[ v.size() ];
        for ( int i=0; i<sv.length; i++ )
            sv[i] = (String) v.elementAt( i );
        return sv;
    }

    public static MxDataType getNumber( String s ) {
        if ( s.indexOf( '.' ) >= 0 
             || s.indexOf( 'e' ) >= 0 || s.indexOf( 'E' ) >= 0 )
            return new MxDouble( Double.parseDouble( s ) );
        return new MxInt( Integer.parseInt( s ) );
    }

    public static int getDataType( String s ) {
        if ( s.equals( "double" ) )
            return Matrix.DT_DOUBLE;
        if ( s.equals( "int" ) )
            return Matrix.DT_INT;
        if ( s.equals( "short" ) )
            return Matrix.DT_SHORT;
        if ( s.equals( "byte" ) )
            return Matrix.DT_BYTE;
        if ( s.equals( "float" ) )
            return Matrix.DT_FLOAT;
        throw new MxException( "Unknown data type" );
    }

    public MxDataType getVariable( String s ) {
        // default static scoping
        MxDataType x = symt.getValue( s, true, 0 );
        if ( null == x )
            return new MxVariable( s );
        return x;
    }


    public MxDataType rvalue( MxDataType a ) {
        if ( null == a.name )
            return a;
        return a.copy();
    }

    public MxDataType deepRvalue( MxDataType a ) {
        if ( null == a.name )
            return a;
        if ( a instanceof MxMatrix )
            return ((MxMatrix)a).deepCopy();
        return a.copy();
    }

    public MxDataType assign( MxDataType a, MxDataType b ) {
        if ( null != a.name )
        {
            MxDataType x = deepRvalue( b );
            x.setName( a.name );
            symt.setValue( x.name, x, true, 0 );  // scope?
            return x;
        }

        if ( a instanceof MxMatrix )
        {
            BitArray mask = ((MxMatrix)a).mask;
            if ( null == mask )
            {
                if ( b instanceof MxMatrix )
                    ((MxMatrix)a).mat.assign( ((MxMatrix)b).mat );
                else
                    ((MxMatrix)a).mat.assign( MxDouble.doubleValue( b ) );
            }
            else
            {
                if ( b instanceof MxMatrix )
                    ((MxMatrix)a).mat.assign( mask, ((MxMatrix)b).mat );
                else
                    ((MxMatrix)a).mat.assign( mask, MxDouble.doubleValue( b ) );
            }
            return a;
        }

        return a.error( b, "=" );
    }

    public MxDataType subMatrix( MxDataType x, MxDataType[] mexpr ) {
        if ( x instanceof MxMatrix ) 
        {
            if ( 2 == mexpr.length ) 
            {
                Range mr, nr;

                if ( mexpr[0] instanceof MxInt 
                     || mexpr[0] instanceof MxDouble )
                    mr = new Range( MxInt.intValue( mexpr[0] ), 1, 1 );
                else if ( mexpr[0] instanceof MxRange )
                    mr = ((MxRange)mexpr[0]).getRange( 
                        ((MxMatrix)x).mat.height() );
                else
                    return x.error( mexpr[0], "slicing" );

                if ( mexpr[1] instanceof MxInt  
                     || mexpr[1] instanceof MxDouble )
                    nr = new Range( MxInt.intValue( mexpr[1] ), 1, 1 );
                else if ( mexpr[1] instanceof MxRange )
                    nr = ((MxRange)mexpr[1]).getRange( 
                        ((MxMatrix)x).mat.width() );
                else
                    return x.error( mexpr[1], "slicing" );

                return new MxMatrix( ((MxMatrix)x).mat.slice( mr, nr ) );
            }
            
            if ( 1 == mexpr.length )
            {
                if ( mexpr[0] instanceof MxBitArray )
                    return new MxMatrix( ((MxMatrix)x).mat, 
                                         ((MxBitArray)mexpr[0]).ba );

                Range range;

                if ( mexpr[0] instanceof MxInt 
                     || mexpr[0] instanceof MxDouble )
                    range = new Range( MxInt.intValue( mexpr[0] ), 1, 1 );
                else if ( mexpr[0] instanceof MxRange )
                    range = ((MxRange)mexpr[0]).range;
                else
                    return x.error( mexpr[0], "slicing" );

                if ( 1 == ((MxMatrix)x).mat.width() )
                    return new MxMatrix( 
                        ((MxMatrix)x).mat.slice( range, new Range( 0, 0 ) ) );
                else if ( 1 == ((MxMatrix)x).mat.height() )
                    return new MxMatrix( 
                        ((MxMatrix)x).mat.slice( new Range( 0, 0 ), range ) );
            }
        }

        return x.error( "slicing" );
    }

    public MxDataType funcInvoke( 
        MxAntlrWalker walker,  MxDataType func, 
        MxDataType[] params ) throws antlr.RecognitionException {

        // func must be an existing function
        if ( !( func instanceof MxFunction ) )
            return func.error( "not a function" );

        // Is this function an internal function? 
        // Names of formal args are not necessary for internal functions.
        if ( ((MxFunction)func).isInternal() )
            return execInternal( ((MxFunction)func).getInternalId(),
                                 params );

        // otherwise check numbers of actual and formal arguments 
        String[] args = ((MxFunction)func).getArgs();
        if ( args.length != params.length ) 
            return func.error( "unmatched length of parameters" );

        // create a new symbol table
        symt = new MxSymbolTable( ((MxFunction)func).getParentSymbolTable(), 
                                  symt );
        
        // assign actual parameters to formal arguments
        for ( int i=0; i<args.length; i++ )
        {
            MxDataType d = rvalue( params[i] );
            d.setName( args[i] );
            symt.setValue( args[i], d, false, 0 );
        }

        // call the function body
        MxDataType r = walker.expr( ((MxFunction)func).getBody() );

        // no break or continue can go through the function
        if ( control == fc_break || control == fc_continue )
            throw new MxException( "nowhere to break or continue" );

        // if a return was called
        if ( control == fc_return )
            tryResetFlowControl( ((MxFunction)func).name );

        // remove this symbol table and return
        symt = symt.dynamicParent();

        return r;
    }

    public void funcRegister( String name, String[] args, AST body ) {
        symt.put( name, new MxFunction( name, args, body, symt ) );
    }

    public void setBreak( String label ) {
        this.label = label;
        control = fc_break;
    }

    public void setContinue( String label ) {
        this.label = label;
        control = fc_continue;
    }

    public void setReturn( String label ) {
        this.label = label;
        control = fc_return;
    }

    public void tryResetFlowControl( String loop_label ) {
        if ( null == label || label.equals( loop_label ) )
            control = fc_none;
    }

    public void loopNext( String loop_label ) {
        if ( control == fc_continue )
            tryResetFlowControl( loop_label );
    }

    public void loopEnd( String loop_label ) {
        if ( control == fc_break )
            tryResetFlowControl( loop_label );
    }

    public boolean canProceed() {
        return control == fc_none;
    }

    public MxInt[] forInit( MxDataType[] mexpr ) {
        // very much like function call
        for ( int i=0; i<mexpr.length; i++ ) 
            if ( ! ( mexpr[i] instanceof MxRange )  )
            {
                mexpr[i].error( "for: [range expected]" );
                return null;
            }

        // create a new symbol table
        symt = new MxSymbolTable( symt, symt );

        MxInt[] x = new MxInt[mexpr.length];
        for ( int i=0; i<mexpr.length; i++ )
        {
            x[i] = new MxInt( ((MxRange)mexpr[i]).range.first() );
            x[i].setName( mexpr[i].name );
            symt.setValue( mexpr[i].name, x[i], false, 0 );
        }

        symt.setReadOnly();
        return x;
    }

    public boolean forCanProceed( MxDataType[] mexpr, MxInt[] values ) {
        if ( control != fc_none )
            return false;

        for ( int i=mexpr.length-1; ; i-- ) 
        {
            if ( ((MxRange)mexpr[i]).range.contain( values[i].var ) )
                return true;
            if ( 0 == i )
                return false;

            values[i].var = ((MxRange)mexpr[i]).range.first();
            values[i-1].var = 
                ((MxRange)mexpr[i-1]).range.next( values[i-1].var );
        }
    }

    public void forNext( MxDataType[] mexpr, MxInt[] values ) {

        values[ values.length-1 ].var++;
        if ( control == fc_continue )
            tryResetFlowControl( mexpr[0].name );
    }

    public void forEnd( MxDataType[] mexpr ) {
        if ( control == fc_break )
            tryResetFlowControl( mexpr[0].name );

        // remove this symbol table
        symt = symt.dynamicParent();
    }

    public MxDataType execInternal( int id, MxDataType[] params ) {
        return MxInternalFunction.run( symt, id, params );
    }

    public void registerInternal() {
        MxInternalFunction.register( symt );
    }

    public MxDataType include( MxDataType file ) {
        if ( file instanceof MxString ) 
        {
            try 
            {
                InputStream input = 
                    (InputStream) new FileInputStream( ((MxString)file).var );
                
                MxAntlrLexer lexer = new MxAntlrLexer( input );
                MxAntlrParser parser = new MxAntlrParser( lexer );
                
                parser.program();
                if ( lexer.nr_error > 0 || parser.nr_error > 0 )
                    throw new MxException( "parsing erros" );
                CommonAST tree = (CommonAST)parser.getAST();
                MxAntlrWalker walker = new MxAntlrWalker();
                // share the symbol table
                walker.ipt.symt = this.symt;
                return walker.expr( tree );
            }
            catch ( Exception e ) 
            {
                throw new MxException( "include failed" );
            }
        }
        
        throw new MxException( "why parser let another data type go through?" );
    }
}
