/*
    Name:       eval.c
    Purpose:    evaluates nodes generated by parser for wxBasic
    Author:     David Cuny
    Copyright:  (c) 2001 David Cuny <dcuny@lanset.com>
    Licence:    LGPL
*/

/* stringCompare: compare two strings on the stack */
void stringCompare( int op, int popFlag )
{
    char    *string1, *string2;

    string2 = popString();
    if (popFlag) {
        string1 = popString();
    }
    else {
        string1 = stack[tos].value.string;
    }

    eMemTest( "stringCompare: string1", string1 );
    eMemTest( "stringCompare: string2", string2 );

    switch( op ) {
    case EQ:
        pushNumber( strcmp(string1,string2) == 0 );
        break;

    case NE:
        pushNumber( strcmp(string1,string2) != 0 );
        break;

    case GT:
        pushNumber( strcmp(string1,string2) == 1 );
        break;

    case LT:
        pushNumber( strcmp(string1,string2) == -1 );
        break;

    case GE:
        pushNumber( strcmp(string1,string2) > -1 );
        break;

    case LE:
        pushNumber( strcmp(string1,string2) < 1 );
        break;

    default:
        ePrintf( Runtime, "OpCmp: bad operator" );
    }

    eFree( string2 );
    if (popFlag) {
	eFree( string1 );
    }
}

/* numberCompare: compare two numbers on the stack */
void numberCompare( int op, int popFlag )
{
    Number  n1, n2;

    n2 = popNumber();
    if (popFlag) {
        n1 = popNumber();
    } else {
        n1 = stack[tos].value.number;
    }

    switch (op) {

    case EQ:
        pushNumber( abs(n1 - n2) <= ALLOWABLE_ERROR );
        break;

    case NE:
        pushNumber( n1 != n2 );
        break;

    case GT:
        pushNumber( n1 > n2 );
        break;

    case LT:
        pushNumber( n1 < n2 );
        break;

    case GE:
        pushNumber( n1 >= n2 );
        break;

    case LE:
        pushNumber( n1 <= n2 );
        break;

    default:
        ePrintf( Runtime, "numberCompare: bad operator" );
    }
}




/* stringRangeCompare: range test for case */
void stringRangeCompare()
{
    char    *stringHigh, *stringLow, *string;

    stringHigh = popString();
    stringLow = popString();
    string = stack[tos].value.string;

    pushNumber(
        strcmp(string, stringLow) > -1 &&
        strcmp(string, stringHigh) < 1 );

    eFree( stringLow );
    eFree( stringHigh );
}

/* numberRangeCompare: range test for case */
void numberRangeCompare()
{
    Number  high, low, n;

    high = popNumber();
    low = popNumber();
    n = stack[tos].value.number;

    pushNumber( n >= low && n <= high );
}


/* callFunction: call a user defined function */
void callFunction( Node *node )
{
    int     priorLocalStart, priorTos, numArgs, localCount, i;
    Symbol  *s;

    /* return value */
    pushNumber( 0 );

    /* argc */
    pushNumber( 0 );


    /* start of local variables */
    priorLocalStart = localstart;
    priorTos = tos;

    /* mark start of created objects */
    pushCreateStack( 0 );

    /* evaluate parameters */
    eval( node->left );

    /* number of args */
    numArgs = (tos-priorTos);
    stack[priorTos].value.number = (Number)numArgs;

    /* get routine */
    s = node->value.symbol;
    if (s->forward) {
        ePrintf( Runtime, "%s has not been defined yet", s->name );
    }
    /* currentScope in runtime */

    oldScope[++oldScopeP]=currentScope;
    if (oldScopeP>255)ePrintf( Runtime, "stack oldScope overflow");
    currentScope=s;

    /* allocate locals */
    localCount = s->optargs + s->locals;
    for ( i = 0; i < (localCount - numArgs); i++ ) {
        pushNumber( 0 );
    }

    /* set start of locals */
    localstart = priorTos;


    /* run code */
    eval( s->code );

    /* exit routine flag set? */
    if (exitRoutineFlag != 0) {
        exitRoutineFlag = 0;
    }

    /* resume oldScope */
    currentScope=oldScope[oldScopeP--];
    if (oldScopeP<0)ePrintf( Runtime, "stack oldScope underflow");



    /* destroy created objects */
    clearCreateStack();

    /* clear locals off stack */
    for ( i = localCount; i > 0; i-- ) {
        /* need to free record? */
        if ( i > numArgs && stack[tos].datatype == DATA_ARRAY) {
            freeArray( stack[tos].value.array );
        }
        popNumber();
    }

    /* drop argc */
    dropTos();

    /* check stack */
    if (priorTos > tos+1) {
        ePrintf( Runtime, "callFunction: %s(): stack underflow", s->name );
    } else if (priorTos < tos+1) {
        ePrintf( Runtime, "callFunction: %s(): garbage on stack", s->name );
    }

    /* drop result? */
    if (node->op == OpFunctionCall) {
        dropTos();
    }

    /* restore marker */
    localstart = priorLocalStart;
}


void eval( Node *node )
{
    int             i, j;
    Number          n1, n2, n3, n4;
    char            *string1;
    Symbol          *s;
    Node            *n;
    Array           *a;

/* debugging */
#ifdef __DEBUG_TRACE__
    SourceCode *sc;
#endif


/* init vars */
    n1=0;
    n2=0;
    n3=0;
    n4=0;


    if (node == NULL) {
        return;
    }

    /* test node */
    eMemTest( "eval: node is corrupt", node );
    eMemTest( "eval: node->left is corrupt", node->left );
    eMemTest( "eval: node->right is corrupt", node->right );

    /* tracing */
    if (node->trace != -1) {
        runLineId = node->trace;
#ifdef __DEBUG_TRACE__
        sc = eFindSource( runLineId );
        eConsole("%d: %s\n", sc->lineNum, sc->text );
#endif
	/* sdlBasic_debugging */
	if (debug==1){
	    screendebug(); ////////////////////////////////////
	}
    }

/* debugging */
#ifdef __DEBUG_TRACE__
    eConsole( "Op:%s\n", opcodeName[node->op] );
#endif

    switch( node->op ) {

    //case NULL:
    //    break;

    case OpAdd:
        eval( node->left );
        eval( node->right );

        /* add or concat? */
        if (getStackType( tos ) == DATA_STRING) {
            basConcat();
        } else {
            n2 = popNumber();
            n1 = popNumber();
            pushNumber( n1 + n2 );
        }
        break;

    case OpAnd:
        /* short circuit */
        eval( node->left );
        if (!popNumber()) {
            pushNumber( (Number)0 );
        } else {
            eval( node->right );
            if (popNumber()) {
                pushNumber( (Number)1 );
            } else {
                pushNumber( (Number)0 );
            }
        }
        break;

    case OpArgList:
        eval( node->left );
        eval( node->right );
        break;

    case OpArrayGet:
        eval( node->left );     /* indices and index */
        getArrayElement( node->value.symbol );
        break;

    case OpArrayPtr:
        pushArray( node->value.symbol );
        break;

    case OpArraySet:
        eval( node->right );    /* value to store */
        eval( node->left );     /* indices and index */
        setArrayElement( node->value.symbol );
        break;

    case OpAssign:

        /* value to be assigned. note the *right* node is used */
        eval( node->right );

        /* variable to assign to */
        setVar( node->value.symbol );
        break;

    case OpBuiltin:
    case OpBuiltinCall:

        /* for argcount */
        n1 = tos;

        /* mark start of created objects */
        pushCreateStack( 0 );

        /* the args */
        eval( node->left );

        /* builtin symbol */
        s = node->value.symbol;

        if (s == NULL) {
            ePrintf( Runtime, "builtin pointer is null");
        }

        /* set args */
        argCount = (int)(tos - n1);

        /* call the c function */
        (*s->builtin)();

        /* destroy created objects */
        clearCreateStack();

        /* drop result? */
        if (node->op == OpBuiltinCall) {
            dropTos();
        }

        break;

    case OpCaseSelect:
        /* top level of a case statement */

        /* value to compare */
        eval( node->left );

        /* resolve into real data */
        switch (getStackType( tos )) {

        case DATA_NUMBER:
            pushNumber( popNumber() );
            break;

        case DATA_STRING:
            pushString( popString() );
            break;

        default:
            ePrintf( Runtime, "OpCaseSelect: can't resolve type %s",
                datumName[getStackType( tos )] );
        }

        /* get first test */
        if (node == NULL) {
            /* no tests */
            break;
        } else {
            node = node->right;
        }

        /* walk the chain of cases */
        while (1) {
            /* get a test/action pair */
            n = node->left;

            /* perform the tests */
            eval( n->left );

            /* true? */
            if (popNumber()) {
                /* perform action and leave loop */
                eval( n->right );
                break;
            }

            /* move to next test */
            node = node->right;
            if (node == NULL) {
                break;
            }
        }

        /* drop the test value from the stack */
        dropTos();
        break;

    case OpCaseCmp:
    case OpCaseRange:
    case OpCaseElse:
        /* perform chain of tests until true or end */
        while (1) {
            switch (node->op) {
            case OpCaseCmp:
                /* value to compare */
                eval(node->left);

                /* what type of test? */
                switch (getStackType( tos-1 )) {
                case DATA_NUMBER:
                    numberCompare( node->value.iValue, 0 );
                    break;

                case DATA_STRING:
                    stringCompare( node->value.iValue, 0 );
                    break;

                default:
                    ePrintf( Runtime, "OpCaseCmp: bad data type" );
                }
                break;

            case OpCaseRange:
                /* values to compare */
                n = node->left;
                eval( n->left );
                eval( n->right );

                /* what type of comparison? */
                switch (getStackType( tos-2 )) {
                case DATA_NUMBER:
                    numberRangeCompare();
                    break;

                case DATA_STRING:
                    stringRangeCompare();
                    break;

                default:
                    ePrintf( Runtime, "OpCaseRange: bad data type" );
                    break;
                }
                break;

            case OpCaseElse:
                /* put true on stack */
                pushNumber( 1 );
                break;

            default:
                ePrintf( Runtime, "opcode %s found in Case test chain",
                    opcodeName[node->op] );
            }

            /* was result true? */
            if (stack[tos].value.number) {
                /* leave true flag on stack and exit */
                break;
            }

            /* move to next test */
            node = node->right;
            if (node == NULL) {
                /* exit with false flag */
                break;
            }

            /* drop test result flag */
            dropTos();
        }
        break;

    case OpClassMethod:
    case OpClassMethodCall:

        /* the args */
        n1 = tos;

        /* mark start of created objects */
        pushCreateStack( 0 );

        /* the args */
        eval( node->right );

        argCount = (int)(tos - n1);
        me = 0;
        runMethod( node->left->value.symbol->klass, node->value.string );

        /* destroy created objects */
        clearCreateStack();

        /* drop result? */
        if (node->op == OpClassMethodCall) {
            dropTos();
        }

        break;

    case OpClose:
        if (node->left == NULL) {
            fileCloseAll();
        } else {
            eval( node->left );
            i = (int)popNumber();
            fileClose( i );
        }
        break;

    case OpCmp:
        eval( node->left );
        eval( node->right );

        switch(getStackType(tos)) {
        case DATA_NUMBER:
            numberCompare( node->value.iValue, 1 );
            break;

        case DATA_STRING:
            stringCompare( node->value.iValue, 1 );
            break;

        default:
            ePrintf( Runtime, "opCmp: can't handle datatype" );

        }
        break;

    case OpComma:

        /* optimized for linked lists */
        while (1) {
            /* exit flag set? */
            if (exitForFlag != 0 ||
                exitRoutineFlag != 0 ||
                exitDoFlag != 0 ||
                exitWhileFlag != 0 ||
                continueFlag ) {
                break;
            }

            if (node->left != NULL ) {
                eval( node->left );
            }

            /* end of list? */
            if (node->right == NULL ) {
                break;

            /* linked list? */
            } else if (node->right->op == OpComma) {
                node = node->right;

            /* not a list */
            } else {
                eval( node->right );
                break;
            }
        }
        break;

    case OpConcat:
        eval( node->left );
        eval( node->right );
        basConcat();
        break;

    case OpConstGet:
        s = node->value.symbol;
        i = s->stackPos;
        /* this better not be indirected! */
        switch (stack[i].datatype) {
        case DATA_STRING:
            pushString( stack[i].value.string );
            break;

        case DATA_NUMBER:
            pushNumber( stack[i].value.number );
            break;

        default:
            ePrintf( Runtime, "Can't fetch Const type %s",
                datumName[stack[i].datatype] );
            break;
        }
        break;

    case OpConstSet:
        s = node->value.symbol;
        eval( node->left );
        switch (stack[tos].datatype) {
        case DATA_STRING:
            stack[s->stackPos].datatype = DATA_STRING;
            stack[s->stackPos].value.string = stack[tos].value.string;
            stack[tos].datatype = DATA_NUMBER;
            tos--;
            break;

        case DATA_NUMBER:
            stack[s->stackPos].datatype = DATA_NUMBER;
            stack[s->stackPos].value.number = stack[tos].value.number;
            tos--;
            break;

        default:
            ePrintf( Runtime, "Can't set Const to %s", datumName[stack[tos].datatype] );
            break;
        }
        break;

    case OpDelete:
        eval( node->left );
        runDestructor( (int)popNumber(), OpDelete );
        break;

    case OpDestroy:
        eval( node->left );
        runDestructor( (int)popNumber(), OpDestroy );
        break;

    case OpContinue:
        continueFlag = 1;
        break;

    case OpDiv:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        if (n2 == 0.0) {
            ePrintf( Runtime, "Division by zero" );
        }

        pushNumber( n1 / n2 );
        break;

    case OpDo:
        while (1) {
            /* test */
            eval( node->left );
            /*if (!popNumber()){
                break;
            }*/

            /* code */
            eval( node->right );
	    //printf("exitDoFlag:%d\n",exitDoFlag);
            if (exitDoFlag != 0) {
                exitDoFlag = 0;
                break;
            }
	    else if (continueFlag) {
                continueFlag = 0;
            }
        }
        break;

    case OpEnd:
        eShutdown(0);
        break;

    case OpErase:
        if (node->left == NULL) {
            /* erase entire array */
            eraseArray( node->value.symbol );
        } else {
            /* evaluate the indexes */
            eval( node->left );

            /* erase single element from array */
            eraseArrayElement( node->value.symbol );
        }
        break;

    case OpExitDo:
        exitDoFlag = 1;
        break;

    case OpExitFor:
        exitForFlag = 1;
        break;

    case OpExitRoutine:
	currentScope=oldScope[oldScopeP--];
        exitRoutineFlag = 1;
        break;

    case OpExitWhile:
        exitWhileFlag = 1;
        break;

    case OpFloat:
        pushNumber( node->value.fValue );
        break;

    case OpFor:
        s = node->value.symbol;
        eval( node->left );
        n2 = popNumber();
        n1 = popNumber();

        /* initial value */
        pushNumber( n3 );
        setVar( s );

        for( n3=n1; n3 <= n2; n3++ ) {
            /* set loop variable */
            pushNumber( n3 );
            setVar( s );

            /* run code */
            eval( node->right );

            /* special exit condition? */
            if (exitForFlag) {
                exitForFlag = 0;
                break;
            } else if (continueFlag) {
                continueFlag = 0;
            }

            /* get loop value (in case it changed) */
            getVar( s );
            n3 = popNumber();

        }
        break;

    case OpForEach:
        /* variable to assign */
        s = node->value.symbol;

        /* array to read from */
        a = getArray( node->left->value.symbol );

        /* iterate through keys */
        i = 0;
        /* put key on stack, or exit */
        while (getDynamicKey( a, i )) {

            /* assign to variable */
            setVar( s );

            /* run block */
            eval( node->right );

            /* next */
            i += 1;
        }
        break;


    case OpForStep:
        s = node->value.symbol;
        eval( node->left );
        n3 = popNumber();       /* step  */
        n2 = popNumber();       /* end   */
        n1 = popNumber();       /* start */

        /* which direction? */
        if (n3 > 0) {
            n2 += ALLOWABLE_ERROR;
            for( n4=n1; n2 >= n4; n4 += n3 ) {
                /* set loop variable */
                pushNumber( n4 );
                setVar( s );

                /* run code */
                eval( node->right );

                /* special exit condition? */
                if (exitForFlag) {
                    exitForFlag = 0;
                    break;
                } else if (continueFlag) {
                    continueFlag = 0;
                }

                /* get loop value (in case it changed) */
                getVar( s );
                n4 = popNumber();

            }
        } else {
            n2 -= ALLOWABLE_ERROR;
            for( n4=n1; n2 <= n4; n4 += n3 ) {
                /* set loop variable */
                pushNumber( n4 );
                setVar( s );

                /* run code */
                eval( node->right );

                /* special exit condition? */
                if (exitForFlag) {
                    exitForFlag = 0;
                    break;
                } else if (continueFlag) {
                    continueFlag = 0;
                }

                /* get loop value (in case it changed) */
                getVar( s );
                n4 = popNumber();

            }
        }

        break;

    case OpFunction:
    case OpFunctionCall:
        callFunction( node );
        break;

    case OpInitArray:
        if (node->left == NULL) {
            createDynamicArray( node->value.symbol );
        } else {
            eval( node->left );
            createStaticArray( node->value.symbol );
        }
        break;

    case OpInput:
        if (node->value.iValue == 1) {
            eval( node->left );
            i = (int)popNumber();

            string1 = fileLineInput( i );
            pushString( string1  );

        } else {
            /* command line */
            ePrintf( Runtime, "Input statement is not currently supported" );

        }
        break;

    case OpInt:
        pushNumber( node->value.iValue );
        break;

    case OpMethod:
    case OpMethodCall:

        /* the index */
        eval( node->left );
        n1 = popNumber();

        /* method name */
        string1 = node->value.string;

        /* mark start of created objects */
        pushCreateStack( 0 );

        /* args */
        n2 = tos;
        eval( node->right );
        argCount = (int)(tos - n2);

        /* resolve and run method */
        resolveMethod( (int)n1, string1 );

        /* drop result? */
        if (node->op == OpMethodCall) {
            dropTos();
        }

        /* destroy created objects */
        clearCreateStack();

        break;

    case OpMod:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( (long)n1 % (long)n2 );
        break;

    case OpMul:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( n1 * n2 );
        break;

    case OpOpen:
        /* file name */
        eval( node->left );
        string1 = popString();

        /* mode */
        i = node->value.iValue;

        /* file number */
        eval( node->right );
        n1 = popNumber();

        fileOpen( string1, i, (int)n1 );
        free( string1 );

        break;

    case OpOr:
        /* short circuit */
        eval( node->left );
        if (popNumber()) {
            pushNumber( (Number)1 );
        } else {
            eval( node->right );
            if (popNumber()) {
                pushNumber( (Number)1 );
            } else {
                pushNumber( (Number)0 );
            }
        }
        break;

    case OpOrBits:
        eval( node->left );
        eval( node->right);
        i = popNumber();
        j = popNumber();
        pushNumber( i | j );
        break;

    case OpIDiv:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        /* check for division by zero */
        if (n2 == 0.0) {
            ePrintf( Runtime, "Division by zero" );
        }

        pushNumber( floor(n1 / n2) );
        break;

    case OpIf:
        n = node->left;

        /* test */
        eval( n->left );
        n1 = popNumber();
        if (n1 != 0) {
            /* true portion */
            eval( n->right );
        } else {
            /* false portion */
            eval( node->right );
        }
        break;

    case OpIfTrue:
        ePrintf( Runtime, "OpIfTrue: internal error" );
        break;

    case OpIn:
        /* evaluate key */
        eval( node->left );

        /* look for it in array */
        pushNumber( inArray( node->value.symbol ) );

        break;

    case OpInv:
        ePrintf( Runtime, "Inv is not implemented yet" );
        break;

    case OpNegate:
        eval( node->left );
        n1 = popNumber();
        pushNumber( -n1 );
        break;

    case OpNew:
    case OpNewTmp:

        /* mark start of created objects */
        pushCreateStack( 0 );

        /* the args */
        n1 = tos;
        eval( node->left );
        argCount = (int)(tos - n1);
        runMethod( node->value.symbol->klass, "new" );

        /* destroy created objects *before* adding new object to stack */
        clearCreateStack();

        /* add new object to create stack? */
        if (node->op == OpNewTmp) {
            /* track on stack */
            copyStackItem( tos );
            pushCreateStack( (int)popNumber() );
        }

        break;

    case OpNoOp:
        break;

    case OpNot:
        eval( node->left );
        n1 = popNumber();
        pushNumber( !n1 );
        break;

    case OpPower:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( pow( n1, n2 ) );
        break;

    case OpPrint:
        if (node->left == NULL) {
            i = 0;
        } else {
            eval( node->left );
            i = (int)popNumber();
        }

        node = node->right;


        /* empty print statement */
        if (node == NULL) {
            if (i==0) {
                eConsole("\n");
            } else {
                filePrintString( i, "\n" );
            }
        }

        /* process nodes */
        while(node != NULL) {

            /* data value */
            if (node->left != NULL) {
                eval( node->left );
                string1 = popString();
                if (i==0) {
                    eConsole("%s", string1 );
                } else {
                    filePrintString( i, string1 );
                }
                eFree( string1 );
            }

            /* field delimiter */
            switch (node->value.iValue) {
            case PRINT_TAB:
                if (i==0) {
                    eConsole("\t");
                } else {
                    filePrintString( i, "\t" );
                }
                break;
            case PRINT_NEWLINE:
                if (i==0) {
                    eConsole("\n");
                } else {
                    filePrintString( i, "\n" );
                }
                break;
            default:
                /* no action */
                break;
            }

            /* link */
            node = node->right;

        }
        break;


    case OpReturnValue:
        eval( node->left );
        setReturn();
        exitRoutineFlag = 1;
        break;


    case OpReturnSetValue:
        eval( node->left );
        setReturn();
        break;

    case OpShl:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( (long)n1 << (long)n2 );
        break;

    case OpShr:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( (long)n1 >> (long)n2 );
        break;

    case OpString:
        pushStringCopy( node->value.string );
        break;

    case OpSub:

        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( n1 - n2 );
        break;

    case OpUndefined:
        ePrintf( Runtime, "Opcode is undefined" );
        break;

    case OpVar:
        /* check type */
        getVar( node->value.symbol );
        if (getStackType(tos) == DATA_UNDEFINED) {
            ePrintf( Runtime, "the value of %s is undefined",
                node->value.symbol->name );
        }
        break;

    case OpWhile:
        while (1) {
            /* test */
            eval( node->left );
            if (!popNumber()){
                break;
            }

            /* code */
            eval( node->right );

            if (exitWhileFlag != 0) {
                exitWhileFlag = 0;
                break;

            } else if (continueFlag) {
                continueFlag = 0;
            }
        }
        break;


    case OpXor:
        eval( node->left );
        eval( node->right );

        n2 = popNumber();
        n1 = popNumber();

        pushNumber( (long)n1 ^ (long)n2 );
        break;

    default:
        ePrintf( Runtime, "Unknown Opcode: %d", node->op );

    }
}

