/****************************************************************************
** $Id: quickdebuggerfrontend.cpp  beta1   edited Dec 10 13:07 $
**
** 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 "quickdebuggerfrontend.h"
#include "quickinterpreter.h"
#include "quickdebugger.h"
#include "quickinterpreterinterfaceimpl.h"
#include "quickeditorinterfaceimpl.h"
#include "quickvariableview.h"
#include "quickstackview.h"
#include "quickobjects.h"
#include <designerinterface.h>
#include <qaction.h>
#include <qpixmap.h>
#include <qapplication.h>
#include <qmainwindow.h>
#include <qdockwindow.h>
#include <qheader.h>
#include <qvariant.h>
#include <qprocess.h>
#include <qregexp.h>
#include <qtimer.h>
#if defined(Q_OS_WIN32)
#include <qt_windows.h>
#endif
#include <qinputdialog.h>
#include "qsa.h"

QUnknownInterface *ucm_instantiate();

static QIconSet createIconSet( const QString &name )
{
    QIconSet ic( QPixmap::fromMimeSource( "" + name ) );
    ic.setPixmap( QPixmap::fromMimeSource( "d_" + name ), QIconSet::Small, QIconSet::Disabled );
    return ic;
}

class EventKiller : public QObject
{
    Q_OBJECT

public:
    EventKiller() : QObject(), enabled( FALSE ) {}

    bool eventFilter( QObject *, QEvent * );

    void setEnabled( bool b ) { enabled = b; }

private:
    bool enabled;

};

bool EventKiller::eventFilter( QObject *, QEvent *e )
{
    if ( !enabled )
	return FALSE;
    if ( e->type() == QEvent::KeyPress ||
	 e->type() == QEvent::KeyRelease ||
	 e->type() == QEvent::MouseButtonPress ||
	 e->type() == QEvent::MouseButtonRelease ||
	 e->type() == QEvent::MouseButtonDblClick ||
	 e->type() == QEvent::MouseMove ||
	 e->type() == QEvent::Wheel ||
	 e->type() == QEvent::FocusIn ||
	 e->type() == QEvent::FocusOut ||
	 e->type() == QEvent::Enter ||
	 e->type() == QEvent::Leave ||
	 e->type() == QEvent::Close )
	return TRUE;
    return FALSE;
}

QuickDebuggerFrontend::QuickDebuggerFrontend( QuickEditorInterfaceImpl *i )
    : ActionInterface(), ref( 0 ), dIface( 0 ), actionRun( 0 ), actionStop( 0 ),
      actionNext( 0 ), actionStep( 0 ), modal( FALSE ), haveGUI( FALSE ), debuggerEnabled( TRUE )
{
    groupRun = 0;

    interpreterInterface = new QuickInterpreterInterfaceImpl;

    runningProject = 0;

    mw = 0;

    watchVars = 0;
    callStack = 0;
    editorIface = i;
    editorIface->addRef();
}

QuickDebuggerFrontend::~QuickDebuggerFrontend()
{
    if ( dIface )
	dIface->release();
    delete actionRun;
    delete actionStop;
    delete actionNext;
    delete actionStep;
    delete interpreterInterface;
    if ( watchVars )
	delete watchVars->parentWidget();
    if ( callStack )
	delete callStack->parentWidget();
    editorIface->release();
}

void QuickDebuggerFrontend::getMainWindow()
{
    if ( mw )
	return;
    QWidgetList *l = qApp->topLevelWidgets();
    for ( QWidget *w = l->first(); w; w = l->next() ) {
	if ( w->inherits( "QMainWindow" ) && qstrcmp( w->name(), "designer_mainwindow" ) == 0 ) {
	    mw = (QMainWindow*)w;
	    break;
	}
    }
    delete l;
}

QRESULT QuickDebuggerFrontend::queryInterface( const QUuid &uuid, QUnknownInterface **iface )
{
    *iface = 0;
    if ( uuid == IID_QUnknown )
	*iface = (QUnknownInterface*)this;
    else if ( uuid == IID_QFeatureList )
	*iface = (QFeatureListInterface*)this;
    else if ( uuid == IID_Action )
	*iface = (ActionInterface*)this;
    else
	return QE_NOINTERFACE;

    (*iface)->addRef();
    return QS_OK;
}

unsigned long QuickDebuggerFrontend::addRef()
{
    return ref++;
}

unsigned long QuickDebuggerFrontend::release()
{
    if ( !--ref ) {
	delete this;
	return 0;
    }
    return ref;
}

QStringList QuickDebuggerFrontend::featureList() const
{
    QStringList lst = editorIface->featureList();
    lst << "Qt Script Debugger Sto" // this is really run, but to get the order as we want it
	<< "Qt Script Debugger Stop"
	<< "Qt Script Debugger Next"
	<< "Qt Script Debugger Step";
    return lst;
}

QAction* QuickDebuggerFrontend::create( const QString &action, QObject* parent )
{
    haveGUI = TRUE;
    getMainWindow();
    if ( action == "Qt Script Debugger Sto" ) {
	if ( !groupRun ) {
	    groupRun = new QActionGroup( parent, 0, FALSE );
	    groupRun->setUsesDropDown( TRUE );
	    groupRun->setText( tr( "Run Function" ) );
	    groupRun->setMenuText( tr( "R&un Function..." ) );
	    groupRun->setIconSet( createIconSet( "play.png" ) );

	    actionRunFunction = new QAction( "Run Function", createIconSet( "play.png" ),
					     "R&un Function...",
					     Qt::Key_F5, groupRun, "quickscript_run_func" );
	    actionRunFunction->setEnabled( FALSE );
	    connect( actionRunFunction, SIGNAL( activated() ), this, SLOT( runFunction() ) );
	    actionRun = new QAction( "Run", createIconSet( "play.png" ),
				     "&Run", CTRL+Qt::Key_F5, groupRun, "quickscript_run" );
	    actionRun->setEnabled( FALSE );
	    connect( actionRun, SIGNAL( activated() ), this, SLOT( runProject() ) );
	}
	return groupRun;
   } else  if ( action == "Qt Script Debugger Stop" ) {
	connect( QuickInterpreter::self(), SIGNAL( stopProject() ), this, SLOT( stopProject() ) );
	connect( QuickInterpreter::self(), SIGNAL( toggleDebugger( bool ) ),
		 this, SLOT( toggleDebugger( bool ) ) );
	if ( !actionStop ) {
	    actionStop = new QAction( "Stop Execution/Debugger", createIconSet( "stop.png" ),
				     "&Stop Execution/Debugger", 0, parent, "quickscript_stop" );
	    actionStop->setEnabled( FALSE );
	    connect( actionStop, SIGNAL( activated() ), this, SLOT( stopProject() ) );
	}
	return actionStop;
   } else  if ( action == "Qt Script Debugger Next" ) {
	if ( !actionNext ) {
	    actionNext = new QAction( "Step Over", createIconSet( "stepover.png" ),
				     "Step &Over", Qt::Key_F10, parent, "quickscript_next" );
 	    actionNext->setEnabled( FALSE );
	    connect( actionNext, SIGNAL( activated() ), this, SLOT( debugNext() ) );
	}
	return actionNext;
   } else  if ( action == "Qt Script Debugger Step" ) {
	if ( !actionStep ) {
	    actionStep = new QAction( "Step Into", createIconSet( "steptonext.png" ),
				     "Step &Into", Qt::Key_F11, parent, "quickscript_step" );
 	    actionStep->setEnabled( FALSE );
	    connect( actionStep, SIGNAL( activated() ), this, SLOT( debugStep() ) );
	}
	return actionStep;
    }
    return editorIface->create( action, parent );
}

QString QuickDebuggerFrontend::group( const QString &name ) const
{
    if ( name == "Qt Script Debugger Sto" ||
	 name == "Qt Script Debugger Stop" ||
	 name == "Qt Script Debugger Next" ||
	 name == "Qt Script Debugger Step" )
	return "Qt Script Project";
    return editorIface->group( name );
}

bool QuickDebuggerFrontend::location( const QString &name, Location l ) const
{
    if ( name == "Qt Script Debugger Sto" ||
	 name == "Qt Script Debugger Stop" ||
	 name == "Qt Script Debugger Next" ||
	 name == "Qt Script Debugger Step" )
	return TRUE;
    return editorIface->location( name, l );
}

void QuickDebuggerFrontend::connectTo( QUnknownInterface *appInterface )
{
    if ( !dIface )
	connect( QuickInterpreter::self(), SIGNAL( runProject() ), this, SLOT( runProject() ) );
    DesignerInterface *oldIface = dIface;
    dIface = 0;
    appInterface->queryInterface( IID_Designer, (QUnknownInterface**)&dIface );
    if ( dIface && dIface != oldIface ) {
	dIface->onProjectChange( this, SLOT( projectChanged() ) );
	projectChanged();
    }
}

void QuickDebuggerFrontend::runFunction( const QString &function )
{
    if ( runningProject )
	return;
    dIface->currentProject()->setCustomSetting( "MAINFORM", function );
    runProject();
}

void QuickDebuggerFrontend::runFunction()
{
    if ( runningProject ) {
	debugContinue();
	return;
    }

    QStringList l = QtApplicationScript::self()->globalFunctions();
    if ( l.isEmpty() )
	return;
    bool ok = FALSE;
    QString f = QInputDialog::getItem( "Choose a function to run",
				       "Functions:", l,
				       l.findIndex( dIface->currentProject()->customSetting( "MAINFORM" ) ),
				       FALSE, &ok );
    if ( !ok )
	return;
    runFunction( f );
}

void QuickDebuggerFrontend::runProject()
{
    executeProjectEntryPoint = QuickInterpreter::self() != sender();
    if ( runningProject ) {
	debugContinue();
	return;
    }

    if ( !dIface )
	return;

    setupCallStack();
    setupWatchView();

    QuickInterpreter *interpreter = QuickInterpreter::self();
    delete interpreter;
    interpreter = QuickInterpreter::self();
    connect( QuickInterpreter::self(), SIGNAL( runProject() ), this, SLOT( runProject() ) );
    connect( QuickInterpreter::self(), SIGNAL( stopProject() ), this, SLOT( stopProject() ) );
    connect( QuickInterpreter::self(), SIGNAL( toggleDebugger( bool ) ),
	     this, SLOT( toggleDebugger( bool ) ) );

    startProject();
}

void QuickDebuggerFrontend::startProject()
{
    QuickInterpreter *interpreter = QuickInterpreter::self();
    QuickInterpreter::self()->clear();

    connect( interpreter->debuggerEngine(), SIGNAL( stopped( bool & ) ),
	     this, SLOT( debuggerStopped( bool & ) ) );
    interpreter->debuggerEngine()->setMode( Debugger::Continue );
    connect( interpreter, SIGNAL( runtimeError() ),
	     this, SLOT( runtimeError() ) );

    if ( haveGUI ) {
	actionRun->setEnabled( FALSE );
	actionRunFunction->setEnabled( FALSE );
	actionStop->setEnabled( TRUE );
	actionNext->setEnabled( FALSE );
	actionStep->setEnabled( FALSE );
    }

    eventKiller = new EventKiller;

    QObjectList *l = dIface->currentProject()->run();
    if ( !l || l->isEmpty() ) {
	if ( executeProjectEntryPoint ) {
	    projectChanged();
	    runningProject = new QObjectList;
	    stopProject();
	}
	delete l;
	return;
    }

    if ( !l )
	l = new QObjectList;

    if ( haveGUI && debuggerEnabled ) {
	QObjectList *l2 = groupRun->parent()->queryList( "QDockWindow" );
	for ( QObject *o = l2->first(); o; o = l2->next() ) {
	    if ( QString( ( (QWidget*)o )->name() ) == "Qt Script Project" ||
		 QString( ( (QWidget*)o )->name() ) == "Qt Script Editor" ) {
		( (QWidget*)o )->setEnabled( TRUE );
	    }
	}
	delete l2;
	watchVars->parentWidget()->setEnabled( TRUE );
	( (QListView*)watchVars )->header()->setEnabled( TRUE );
	callStack->parentWidget()->setEnabled( TRUE );
	mw->setAppropriate( (QDockWindow*)watchVars->parentWidget(), TRUE );
	mw->setAppropriate( (QDockWindow*)callStack->parentWidget(), TRUE );
	watchVars->parentWidget()->show();
	callStack->parentWidget()->show();
    }

    runningProject = l;

    for ( QObject *o2 = l->first(); o2; o2 = l->next() ) {
	o2->installEventFilter( eventKiller );
	connect( o2, SIGNAL( destroyed( QObject * ) ), this, SLOT( objectDestroyed( QObject * ) ) );
	QObjectList *children = o2->queryList( "QObject" );
	for ( QObject *o3 = children->first(); o3; o3 = children->next() )
	    o3->installEventFilter( eventKiller );
	delete children;
    }

    l = new QObjectList( *runningProject );
    interpreter->setTopLevelObjects( l );

    if ( executeProjectEntryPoint ) {
	QString function = dIface->currentProject()->customSetting( "MAINFORM" );
	if ( function.endsWith( "()" ) )
	    function.remove( function.length() - 2, 2 );
	QuickInterpreter::self()->call( 0, function, QSList() );
	stopProject();
    }
}

void QuickDebuggerFrontend::projectChanged()
{
    if ( !dIface || !groupRun || !actionStop || !haveGUI || !debuggerEnabled )
	return;
    if ( runningProject ) {
	actionRun->setEnabled( FALSE );
	actionRunFunction->setEnabled( FALSE );
	actionStop->setEnabled( dIface->currentProject() && dIface->currentProject()->language() == "Qt Script" );
    } else {
	actionStop->setEnabled( FALSE );
	actionRun->setEnabled( dIface->currentProject() && dIface->currentProject()->language() == "Qt Script" );
	actionRunFunction->setEnabled( dIface->currentProject() &&
				       dIface->currentProject()->language() == "Qt Script" );
    }
    actionStep->setEnabled( actionStop->isEnabled() );
    actionNext->setEnabled( actionStop->isEnabled() );
}

void QuickDebuggerFrontend::stopProject()
{
    if ( !runningProject )
	return;

    QuickInterpreter *interpreter = QuickInterpreter::self();
    interpreterInterface->showDebugStep( 0, -1 );
    interpreter->debuggerEngine()->setMode( Debugger::Stop );

    delete eventKiller;
    eventKiller = 0;

    if ( interpreter )
	disconnect( interpreter, SIGNAL( runtimeError() ),
		    this, SLOT( runtimeError() ) );
    interpreterInterface->doFinish();


    QObjectListIt it( *runningProject );
    QObject *o = 0;
    while ( ( o = it.current() ) ) {
	++it;
	if ( o->inherits( "QWidget" ) && ( (QWidget*)o )->isTopLevel() &&
	     !dIface->currentProject()->isGenericObject( o ) )
	    ( (QWidget*)o )->close();
    }

    delete runningProject;
    runningProject = 0;
    leaveSession();

    disconnect( interpreter->debuggerEngine(), SIGNAL( stopped( bool & ) ),
		this, SLOT( debuggerStopped( bool & ) ) );

    if ( haveGUI && debuggerEnabled && mw && watchVars && callStack ) {
	mw->setAppropriate( (QDockWindow*)watchVars->parentWidget(), FALSE );
	watchVars->parentWidget()->hide();
	mw->setAppropriate( (QDockWindow*)callStack->parentWidget(), FALSE );
	callStack->parentWidget()->hide();
    }

    interpreter->stop(); // to stop the timers
    interpreter->setTopLevelObjects( 0 );

    projectChanged();
}

void QuickDebuggerFrontend::debuggerStopped( bool &ret )
{
    if ( !dIface )
	return;

    QuickInterpreter *interpreter = QuickInterpreter::self();
    QObject *sidObj = QuickInterpreter::self()->objectOfSourceId( interpreter->debuggerEngine()->sourceId() );

    Q_ASSERT( sidObj );
    if ( !sidObj ) {
	ret = !!runningProject;
	return;
    }
    int line = interpreter->debuggerEngine()->lineNumber();

    eventKiller->setEnabled( TRUE );
    QuickInterpreter::enableTimers( FALSE );

    if ( watchVars )
	watchVars->evaluateAll();
    if ( callStack )
	callStack->updateStack();

    interpreterInterface->showDebugStep( (QWidget*)sidObj, line );
    enterSession();
    if ( eventKiller )
	eventKiller->setEnabled( FALSE );
    QuickInterpreter::enableTimers( TRUE );
    ret = !!runningProject;
}

void QuickDebuggerFrontend::enterSession()
{
    modal = TRUE;
    if ( haveGUI && debuggerEnabled ) {
	actionRun->setEnabled( TRUE );
	actionRunFunction->setEnabled( TRUE );
	actionStep->setEnabled( TRUE );
	actionNext->setEnabled( TRUE );
    }
    qApp->enter_loop();
}

void QuickDebuggerFrontend::leaveSession()
{
    if ( !modal )
	return;
    modal = FALSE;
    if ( haveGUI && debuggerEnabled ) {
	actionRun->setEnabled( FALSE );
	actionRunFunction->setEnabled( FALSE );
	actionStep->setEnabled( FALSE );
	actionNext->setEnabled( FALSE );
    }
    qApp->exit_loop();
}

void QuickDebuggerFrontend::debugNext()
{
    QuickInterpreter::self()->debuggerEngine()->setMode( Debugger::Next );
    leaveSession();
}

void QuickDebuggerFrontend::debugStep()
{
    QuickInterpreter::self()->debuggerEngine()->setMode( Debugger::Step );
    leaveSession();
}

void QuickDebuggerFrontend::debugContinue()
{
    interpreterInterface->showDebugStep( 0, -1 );
    QuickInterpreter::self()->debuggerEngine()->setMode( Debugger::Continue );
    leaveSession();
}

void QuickDebuggerFrontend::runtimeError()
{
    if ( haveGUI && debuggerEnabled ) {
	actionRun->setEnabled( FALSE );
	actionRunFunction->setEnabled( FALSE );
	actionStep->setEnabled( FALSE );
	actionNext->setEnabled( FALSE );
	actionStop->setEnabled( TRUE );
    }
    QuickInterpreter *interpreter = QuickInterpreter::self();
    interpreter->debuggerEngine()->setMode( Debugger::Stop );

    int l = interpreter->errorLines().first();
    QString emsg = interpreter->errorMessages().first();
    bool hadError = interpreter->hadError();

    if ( hadError ) {
	QObject *sidObj = QuickInterpreter::self()->
			  objectOfSourceId( interpreter->debuggerEngine()->sourceId() );
	if ( sidObj ) {
	    eventKiller->setEnabled( TRUE );
	    QuickInterpreter::enableTimers( FALSE );
	    if ( watchVars )
		watchVars->evaluateAll();
	    if ( callStack )
		callStack->updateStack();
	    interpreterInterface->showError( (QWidget*)sidObj, l, emsg );
	} else {
	    interpreterInterface->showError( 0, l, emsg );
	}
    }
}

void QuickDebuggerFrontend::setupWatchView()
{
    getMainWindow();
    if ( !haveGUI || !mw || watchVars || !debuggerEnabled )
	return;

    QDockWindow *dw = new QDockWindow( QDockWindow::OutsideDock, mw, 0 );
    mw->addDockWindow( dw, Qt::DockBottom );
    dw->setFixedExtentHeight( 150 );
    dw->setResizeEnabled( TRUE );
    dw->setCloseMode( QDockWindow::Always );
    watchVars = new QuickVariableView( dw );
    watchVars->setMinimumWidth( 250 );
    dw->setWidget( watchVars );
    watchVars->show();
    watchVars->addWatch( "Global Variables" );
    watchVars->addWatch( "Local Variables" );
    dw->setCaption( tr( "Watch Variables" ) );
    dw->hide();
    mw->setAppropriate( dw, FALSE );
}

void QuickDebuggerFrontend::setupCallStack()
{
    getMainWindow();
    if ( !haveGUI || !mw || callStack || !debuggerEnabled )
	return;

    QDockWindow *dw = new QDockWindow( QDockWindow::OutsideDock, mw, 0 );
    mw->addDockWindow( dw, Qt::DockBottom );
    dw->setResizeEnabled( TRUE );
    dw->setFixedExtentHeight( 150 );
    dw->setCloseMode( QDockWindow::Always );
    callStack = new QuickStackView( dw );
    dw->setWidget( callStack );
    callStack->show();
    callStack->setMinimumWidth( 150 );
    dw->setCaption( tr( "Call Stack" ) );
    dw->hide();
    mw->setAppropriate( dw, FALSE );
}

void QuickDebuggerFrontend::objectDestroyed( QObject *o )
{
    int i;
    if ( runningProject && ( i = runningProject->findRef( o ) ) != -1 )
	runningProject->take( i );
}

void QuickDebuggerFrontend::toggleDebugger( bool enable )
{
    if ( enable == debuggerEnabled )
	return;
    debuggerEnabled = enable;
    editorIface->toggleDebugger( debuggerEnabled );
    if ( !debuggerEnabled ) {
	actionRun->setEnabled( FALSE );
	actionRunFunction->setEnabled( FALSE );
	groupRun->setEnabled( FALSE );
	actionStop->setEnabled( FALSE );
	actionNext->setEnabled( FALSE );
	actionStep->setEnabled( FALSE );
    } else {
	actionStop->setEnabled( FALSE );
	actionRun->setEnabled( dIface->currentProject() &&
			       dIface->currentProject()->language() == "Qt Script" );
	actionRunFunction->setEnabled( dIface->currentProject() &&
				       dIface->currentProject()->language() == "Qt Script" );
	actionStep->setEnabled( actionStop->isEnabled() );
	actionNext->setEnabled( actionStop->isEnabled() );
    }
}

#include "quickdebuggerfrontend.moc"

