/****************************************************************************
** $Id: qsinternal.cpp  beta1   edited Dec 10 14:29 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "qsinternal.h"
#include "qsclass.h"
#include "qsenv.h"
#include "qscheck.h"
#include "qsengine.h"
#include "qsobject.h"
#include "qstypes.h"
#include "qsoperations.h"
#include "qsnodes.h"
#include "qslexer.h"
#include "qsdebugger.h"
#include "qsobject_object.h"
#include "qsdate_object.h"
#include "qsmath_object.h"
#include "qsregexp_object.h"
#include "qsarray_object.h"
#include "qsfuncref.h"
#include "qserror_object.h"
#include <qregexp.h>
#include <stdio.h>
#include <assert.h>

extern int qsyyparse();

QSObject QSArgumentsClass::construct( FunctionImp * /*func*/,
		 		      const QSList * /*args*/ ) const {
    QSObject obj( createWritable() );
#if 0
    obj.put( "callee", Function( func ), DontEnum );
    if ( args ) {
	obj.put( "length", QSNumber( args->size() ), DontEnum );
	QSListIterator arg = args->begin();
	for ( int i = 0; arg != args->end(); arg++, i++ )
	    obj.put( QSString::from(i), *arg, DontEnum );
    }
#endif
    return obj;
}

ExecutionStack::ExecutionStack()
  : progNode(0L), firstNode(0L), prev(0)
{
}

ExecutionStack* ExecutionStack::push()
{
  ExecutionStack *s = new ExecutionStack();
  s->prev = this;

  return s;
}

ExecutionStack* ExecutionStack::pop()
{
  ExecutionStack *s = prev;
  delete this;

  return s;
}

QSEngineImp* QSEngineImp::curr = 0L;
QSEngineImp* QSEngineImp::hook = 0L;
int          QSEngineImp::instances = 0;
int          QSEngineImp::running = 0;

QSEngineImp::QSEngineImp( QSEngine *s )
  : scr(s),
    initialized(false),
    glob( 0 )
#ifdef QSDEBUGGER
    ,dbg(0L)
#endif
{
  instances++;
  QSEngineImp::curr = this;
  // are we the first interpreter instance ? Initialize some stuff
  if (instances == 1)
    globalInit();
  stack = new ExecutionStack();
  lex = new QSLexer();
}

QSEngineImp::~QSEngineImp()
{
  QSEngineImp::curr = this;

#ifdef QSDEBUGGER
  attachDebugger(0L);
#endif

  clear();

  delete lex;
  lex = 0L;

  delete stack;
  stack = 0L;

  QSEngineImp::curr = 0L;
  // are we the last of our kind ? Free global stuff.
  if (instances == 1)
    globalClear();
  instances--;
}

void QSEngineImp::globalInit()
{
}

void QSEngineImp::globalClear()
{
}

void QSEngineImp::mark()
{
//   assert(glob.imp());
  glob->mark();
//   exVal.mark();
  retVal.mark();
//   if (con)
//     con->mark();
}

void QSEngineImp::init()
{
    QSEngineImp::curr = this;

    errType = 0;
    errLines.clear();
    errMsgs.clear();
    retVal.invalidate();

    if (!initialized) {
	// add this interpreter to the global chain
	// add a root set for garbage collection
	if (hook) {
	    prev = hook;
	    next = hook->next;
	    hook->next->prev = this;
	    hook->next = this;
	} else {
	    hook = next = prev = this;
	}

	en = new QSEnv();
	glob = new Global( scr );
	glob->init();
	//     con = new Context();
	firstN = 0L;
	progN = 0L;
	recursion = 0;
	initialized = true;
#ifdef QSDEBUGGER
	sid = -1;
#endif
    }
}

void QSEngineImp::clear()
{
  if (initialized) {
    QSEngineImp::curr = this;

    QSNode::setFirstNode( firstNode() );
    QSNode::deleteAllNodes();
    setFirstNode(0L);
    setProgNode(0L);

    // To correctly free references.
    if( retVal.isValid() )
	retVal.objectType()->deref( &retVal );
    retVal.invalidate();

    delete glob; glob = 0;
    delete en; en = 0;
//     delete con; con = 0L;


    // ### could collect garbage now

    // remove from global chain (see init())
    next->prev = prev;
    prev->next = next;
    hook = next;
    if (hook == this)
      hook = 0L;

#ifdef QSDEBUGGER
    sid = -1;
#endif

    initialized = false;
  }
}

bool QSEngineImp::evaluate( const QString &code, const QSObject *thisV,
			    bool onlyCheckSyntax, int checkMode, int lineZero )
{
  init();

#ifdef QSDEBUGGER
  incrSourceId();
  if (debugger())
    debugger()->setSourceId(sid);
#endif
  if (recursion > 7) {
    fprintf(stderr, "KJS: breaking out of recursion\n");
    return true;
  } else if (recursion > 0) {
#ifndef NDEBUG
    fprintf(stderr, "KJS: entering recursion level %d\n", recursion);
#endif
    stack = stack->push();
  }

  assert(QSLexer::curr());
  QSLexer::curr()->setCode( code, lineZero );
  QSNode::setFirstNode( firstNode() );
  int parseError = qsyyparse();
  setFirstNode( QSNode::firstNode() );

  if (parseError) {
    errType = 99; /* TODO */
    int l = QSLexer::curr()->lineNo();
    errLines.append( l );
    errMsgs.append( "Parse error at line " + QSString::from( l ) );
#ifndef NDEBUG
    fprintf(stderr, "JavaScript parse error at line %d.\n", l );
#endif
    /* TODO: either clear everything or keep previously
       parsed function definitions */
    //    QSNode::deleteAllNodes();
    return false;
  }

  QSCheckData sem( env(), env()->globalClass() );
  if ( thisV ) {
//       qDebug( "QSEngineImp::evaluate: entering %s",
// 	      thisV->typeName().latin1());
      sem.enterClass( (QSClass*)thisV->objectType() );
  }
  sem.setGlobalStatementsForbidden( checkMode & QSEngine::DisallowGlobal );
  progNode()->check( &sem );
  if ( sem.hasError() ) {
      errType = sem.errorCode();
      errLines = sem.errorLines();
      errMsgs = sem.errorMessages();
      return FALSE;
  }

  if (onlyCheckSyntax)
      return true;

  env()->clearException();

  QSObject oldVar;
  if ( thisV ) {
      Q_ASSERT( thisV->isValid() );
//     context()->setThisValue( *thisV );
      env()->pushScope( *thisV );
//     oldVar = context()->variableObject();
//     context()->setVariableObject( *thisV );
  }

  running++;
  recursion++;
  assert(progNode());

  QSObject res = progNode()->execute( env() );
  recursion--;
  running--;

  if ( env()->isExceptionMode( )) {
    QSObject err = env()->exception();
    errType = 99; /* TODO */
    errLines.append( err.get("line").toInt32() );
    errMsgs.append( err.get( "name" ).toString() + ". " +
		   err.get( "message" ).toString() );
#ifdef QSDEBUGGER
    if (dbg)
      dbg->setSourceId(err.get("sid").toInt32());
#endif
    env()->clearException();
  } else {
    errType = 0;
    errLines.clear();
    errMsgs.clear();

    // catch return value
    retVal = res;
  }

  if ( thisV ) {
      env()->popScope();
//     context()->setVariableObject(oldVar);
  }

//   if (progNode())
//     progNode()->deleteGlobalStatements();

  if (recursion > 0) {
    stack = stack->pop();
    assert(stack);
  }

  return !errType;
}

bool QSEngineImp::call( QSObject *scope, const QString &func,
			const QSList &args )
{
  init();
  QSObject t;
  if ( !scope || !scope->isValid() ) {
      t = env()->globalObject();
      scope = &t;
  }
  QSObject v = scope->get( func );
  if ( !v.isDefined() ) {
    if (func != "main") {
      errType = ReferenceError;
      errMsgs.append( "Unknown function: " + func );
      errLines.append( -1 );
    }
#ifndef NDEBUG
      fprintf(stderr, "couldn't resolve function name %s. call() failed\n",
	      func.ascii());
#endif
      return false;
  }

  if ( !v.isFunction() ) {
    errType = TypeError;
    errMsgs.append( func + " is not a function. Call failed." );
    errLines.append( -1 );
#ifndef NDEBUG
      fprintf(stderr, "%s is not a function. call() failed.\n", func.ascii());
#endif
      return false;
  }
  QSObject res = v.invoke( QSMember(), args );

  if ( env()->isExceptionMode() ) {
    QSObject err = env()->exception();
    errType = 99; /* TODO */
    errLines.append( err.get( "line" ).toInt32() );
    errMsgs.append( err.get( "name" ).toString() + ". " +
		    err.get( "message" ).toString() );
#ifdef QSDEBUGGER
    if (dbg)
      dbg->setSourceId(err.get("sid").toInt32());
#endif
    env()->clearException();
    return false;
  } else {
    errType = 0;
    errLines.clear();
    errMsgs.clear();

    // return value
    retVal = res;
    return true;
  }
}

#ifdef QSDEBUGGER
void QSEngineImp::attachDebugger(Debugger *d)
{
  static bool detaching = false;
  if (detaching) // break circular detaching
    return;

  if (dbg) {
    detaching = true;
    dbg->detach();
    detaching = false;
  }

  dbg = d;
}

bool QSEngineImp::setBreakpoint(int id, int line, bool set)
{
  init();
  return QSNode::setBreakpoint( firstNode(), id, line, set );
}

#endif

bool LabelStack::push(const QString &id)
{
  if (id.isEmpty() || contains(id))
    return false;

  StackElm *newtos = new StackElm;
  newtos->id = id;
  newtos->prev = tos;
  tos = newtos;
  return true;
}

bool LabelStack::contains(const QString &id) const
{
  if (id.isEmpty())
    return true;

  for (StackElm *curr = tos; curr; curr = curr->prev)
    if (curr->id == id)
      return true;

  return false;
}

void LabelStack::pop()
{
  if (tos) {
    StackElm *prev = tos->prev;
    delete tos;
    tos = prev;
  }
}

LabelStack::~LabelStack()
{
  StackElm *prev;

  while (tos) {
    prev = tos->prev;
    delete tos;
    tos = prev;
  }
}
