/****************************************************************************
** $Id: quickeditorinterfaceimpl.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 "quickeditorinterfaceimpl.h"
#include "quickeditor.h"
#include "quickcompletion.h"
#include "quickdebuggerfrontend.h"
#include "quickbreakpointsettingsimpl.h"
#include "../kernel/quickinterpreter.h"
#include "../kernel/quickdebugger.h"
#include <markerwidget.h>
#include <viewmanager.h>
#include <designerinterface.h>
#include <qapplication.h>
#include <qtimer.h>
#include <qiconset.h>
#include <qaction.h>
#include <private/qrichtext_p.h>

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

static QPtrList<QuickEditorInterfaceImpl> *editorInterfaces = 0;
bool QuickEditorInterfaceImpl::debuggerEnabled = TRUE;

QuickEditorInterfaceImpl::QuickEditorInterfaceImpl()
    : EditorInterface(), viewManager( 0 ), ref( 0 ), dIface( 0 )
{
    if ( !editorInterfaces )
	editorInterfaces = new QPtrList<QuickEditorInterfaceImpl>;
    editorInterfaces->append( this );
    updateTimer = new QTimer( this );
    connect( updateTimer, SIGNAL( timeout() ),
	     this, SLOT( update() ) );
    actionToggleBreakPoint = 0;
    actionEditBreakpoints = 0;
}

QuickEditorInterfaceImpl::~QuickEditorInterfaceImpl()
{
    editorInterfaces->removeRef( this );
    updateTimer->stop();
    delete viewManager;
    if ( dIface )
	dIface->release();

    delete actionToggleBreakPoint;
    delete actionEditBreakpoints;
}

QRESULT QuickEditorInterfaceImpl::queryInterface( const QUuid &uuid, QUnknownInterface **iface )
{
    *iface = 0;
    if ( uuid == IID_QUnknown )
	*iface = (QUnknownInterface*)(QComponentInformationInterface*)this;
    else if ( uuid == IID_QComponentInformation )
	*iface = (QComponentInformationInterface*)this;
    else if ( uuid == IID_Editor )
	*iface = (EditorInterface*)this;
    else if ( uuid == IID_Action )
	*iface = (ActionInterface*)this;
    else
	return QE_NOINTERFACE;

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

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

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

QWidget *QuickEditorInterfaceImpl::editor( bool readonly,
					   QWidget *parent,
					   QUnknownInterface *iface  )
{
    if ( !viewManager ) {
	( (QuickEditorInterfaceImpl*)this )->viewManager = new ViewManager( parent, 0 );
	QuickEditor *e = new QuickEditor( QString::null, viewManager, "editor" );
	e->setEditable( !readonly );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager, SIGNAL( collapseFunction( QTextParagraph * ) ),
			  e, SLOT( collapseFunction( QTextParagraph * ) ) );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager, SIGNAL( expandFunction( QTextParagraph * ) ),
			  e, SLOT( expandFunction( QTextParagraph * ) ) );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager, SIGNAL( collapse( bool ) ),
			  e, SLOT( collapse( bool ) ) );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager, SIGNAL( expand( bool ) ),
			  e, SLOT( expand( bool ) ) );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager, SIGNAL( editBreakPoints() ),
			  this, SLOT( editBreakpoints() ) );
	QObject::connect( ( (QuickEditorInterfaceImpl*)this )->viewManager,
			  SIGNAL( isBreakpointPossible( bool&, const QString&, int ) ),
			  this, SLOT( isBreakpointPossible( bool &, const QString &, int ) ) );
	e->installEventFilter( this );
	QApplication::sendPostedEvents(); // let the workspace do its reparenting work
	if ( viewManager->parentWidget() )
	    viewManager->parentWidget()->installEventFilter( this );
	dIface = 0;
	if ( iface )
	    iface->queryInterface( IID_Designer, (QUnknownInterface**)&dIface );
	connect( e, SIGNAL( intervalChanged() ), this, SLOT( intervalChanged() ) );
	QApplication::sendPostedEvents( viewManager, QEvent::ChildInserted );
    }
    return viewManager->currentView();
}

void QuickEditorInterfaceImpl::setText( const QString &txt )
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    QuickEditor *e = (QuickEditor*)viewManager->currentView();
    disconnect( e, SIGNAL( modificationChanged( bool ) ), this, SLOT( modificationChanged( bool ) ) );
    e->setText( txt );
    e->setModified( FALSE );
    e->sync();
    e->loadLineStates();
    connect( e, SIGNAL( modificationChanged( bool ) ), this, SLOT( modificationChanged( bool ) ) );
    viewManager->marker_widget()->update();
}

QString QuickEditorInterfaceImpl::text() const
{
    if ( !viewManager || !viewManager->currentView() )
	return QString::null;
    return ( (QuickEditor*)viewManager->currentView() )->text();
}

bool QuickEditorInterfaceImpl::isUndoAvailable() const
{
    if ( !viewManager || !viewManager->currentView() )
	return FALSE;
    return ( (QuickEditor*)viewManager->currentView() )->isUndoAvailable();
}

bool QuickEditorInterfaceImpl::isRedoAvailable() const
{
    if ( !viewManager || !viewManager->currentView() )
	return FALSE;
    return ( (QuickEditor*)viewManager->currentView() )->isRedoAvailable();
}

void QuickEditorInterfaceImpl::undo()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->undo();
}

void QuickEditorInterfaceImpl::redo()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->redo();
}

void QuickEditorInterfaceImpl::cut()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->cut();
}

void QuickEditorInterfaceImpl::copy()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->copy();
}

void QuickEditorInterfaceImpl::paste()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->paste();
}

void QuickEditorInterfaceImpl::selectAll()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->selectAll();
}

bool QuickEditorInterfaceImpl::find( const QString &expr, bool cs, bool wo, bool forward, bool startAtCursor )
{
    if ( !viewManager || !viewManager->currentView() )
	return FALSE;
    QuickEditor *e = (QuickEditor*)viewManager->currentView();
    if ( startAtCursor )
	return e->find( expr, cs, wo, forward );
    int dummy = 0;
    return e->find( expr, cs, wo, forward, &dummy, &dummy );

}

bool QuickEditorInterfaceImpl::replace( const QString &find, const QString &replace, bool cs, bool wo,
				   bool forward, bool startAtCursor, bool replaceAll )
{
    if ( !viewManager || !viewManager->currentView() )
	return FALSE;
    QuickEditor *e = (QuickEditor*)viewManager->currentView();
    bool ok = FALSE;
    if ( startAtCursor ) {
	ok = e->find( find, cs, wo, forward );
    } else {
	int dummy = 0;
	ok =  e->find( find, cs, wo, forward, &dummy, &dummy );
    }

    if ( ok ) {
	e->removeSelectedText();
	e->insert( replace, FALSE, FALSE );
    }

    if ( !replaceAll || !ok ) {
	if ( ok )
	    e->setSelection( e->textCursor()->paragraph()->paragId(),
			     e->textCursor()->index() - replace.length(),
			     e->textCursor()->paragraph()->paragId(),
			     e->textCursor()->index() );
	return ok;
    }

    bool ok2 = TRUE;
    while ( ok2 ) {
	ok2 = e->find( find, cs, wo, forward );
	if ( ok2 ) {
	    e->removeSelectedText();
	    e->insert( replace, FALSE, FALSE );
	}
    }

    return TRUE;
}

void QuickEditorInterfaceImpl::gotoLine( int line )
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->setCursorPosition( line, 0 );
}

void QuickEditorInterfaceImpl::indent()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->QTextEdit::indent();
}

void QuickEditorInterfaceImpl::splitView()
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    QTextDocument *doc = ( (QuickEditor*)viewManager->currentView() )->document();
    QuickEditor *editor = new QuickEditor( QString::null, viewManager, "editor" );
    editor->setDocument( doc );
}

void QuickEditorInterfaceImpl::scrollTo( const QString &txt, const QString &first )
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    QString expr = first;
    ( (QuickEditor*)viewManager->currentView() )->sync();
    QTextDocument *doc = ( (QuickEditor*)viewManager->currentView() )->document();
    QTextParagraph *p = doc->firstParagraph();
    while ( p ) {
	if ( p->string()->toString().find( expr ) != -1 ) {
	    ( (QuickEditor*)viewManager->currentView() )->setCursorPosition( p->paragId() + 2, 0 );
	    if ( expr == txt )
		break;
	    expr = txt;
	}
	p = p->next();
    }
    ( (QuickEditor*)viewManager->currentView() )->setFocus();
}

void QuickEditorInterfaceImpl::setContext( QObject *this_ )
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->completionManager()->setContext( this_ );
}

void QuickEditorInterfaceImpl::setError( int line )
{
    if ( !viewManager )
	return;
    viewManager->setError( line );
}

void QuickEditorInterfaceImpl::setStep( int line )
{
    if ( !viewManager )
	return;
    viewManager->setStep( line );
}

void QuickEditorInterfaceImpl::clearStep()
{
    if ( !viewManager )
	return;
    viewManager->clearStep();
}

void QuickEditorInterfaceImpl::clearStackFrame()
{
    if ( !viewManager )
	return;
    viewManager->clearStackFrame();
}

void QuickEditorInterfaceImpl::setStackFrame( int line )
{
    if ( !viewManager )
	return;
    viewManager->setStackFrame( line );
}

void QuickEditorInterfaceImpl::readSettings()
{
    if ( !viewManager )
	return;
    ( (QuickEditor*)viewManager->currentView() )->configChanged();
}

void QuickEditorInterfaceImpl::modificationChanged( bool m )
{
    if ( viewManager && dIface )
	dIface->setModified( m, viewManager->currentView() );
}

void QuickEditorInterfaceImpl::setModified( bool m )
{
    if ( !viewManager )
	return;
    ( (QuickEditor*)viewManager->currentView() )->setModified( m );
}

bool QuickEditorInterfaceImpl::isModified() const
{
    if ( !viewManager )
	return FALSE;
    return ( (QuickEditor*)viewManager->currentView() )->isModified();
}

// When the editor gets closed we get a focusOut event. On focusOut we
// re-parse the contents, etc., which requires the editor to be in a
// stable state. When we get the focusOut because of a close event, we
// are not in a stable state and we might crash. So we use that
// variable to ignore focusOut events after close events of the
// editor.
static bool ignoreNextFocusOut = FALSE;

bool QuickEditorInterfaceImpl::eventFilter( QObject *o, QEvent *e )
{
    if ( viewManager && o == viewManager->currentView() ) {
	if ( e->type() == QEvent::KeyPress ) {
	    updateTimer->stop();
	} else if ( e->type() == QEvent::FocusOut && !ignoreNextFocusOut ) {
	    ignoreNextFocusOut = FALSE;
	    update();
	    for ( QuickEditorInterfaceImpl *iface = editorInterfaces->first();
		  iface; iface = editorInterfaces->next() ) {
		if ( iface->actionToggleBreakPoint ) {
		    iface->actionToggleBreakPoint->setEnabled( FALSE );
		    break;
		}
	    }
	} else if ( e->type() == QEvent::FocusIn ) {
	    for ( QuickEditorInterfaceImpl *iface = editorInterfaces->first();
		  iface; iface = editorInterfaces->next() ) {
		if ( iface->actionToggleBreakPoint ) {
		    iface->actionToggleBreakPoint->setEnabled( debuggerEnabled );
		    break;
		}
	    }
	}
    } else if ( viewManager ) {
	if ( e->type() == QEvent::Close )
	    ignoreNextFocusOut = TRUE;
    }

    return QObject::eventFilter( o, e );
}

int QuickEditorInterfaceImpl::numLines() const
{
    if ( !viewManager || !viewManager->currentView() )
	return 0;
    return ( (QuickEditor*)viewManager->currentView() )->paragraphs();
}

void QuickEditorInterfaceImpl::breakPoints( QValueList<uint> &l ) const
{
    if ( !viewManager )
	return;
    l = viewManager->breakPoints();
}

void QuickEditorInterfaceImpl::setBreakPoints( const QValueList<uint> &l )
{
    if ( !viewManager )
	return;
    viewManager->setBreakPoints( l );
}

void QuickEditorInterfaceImpl::intervalChanged()
{
    if ( !dIface )
	return;
    updateTimer->start( 2000, TRUE );
}

void QuickEditorInterfaceImpl::update()
{
    if ( !dIface || !viewManager || !viewManager->currentView() ||
	 !( (QTextEdit*)viewManager->currentView() )->isModified() )
	return;
    dIface->updateFunctionList();
}

void QuickEditorInterfaceImpl::onBreakPointChange( QObject *receiver, const char *slot )
{
    if ( !viewManager )
	return;
    connect( viewManager, SIGNAL( markersChanged() ), receiver, slot );
}

void QuickEditorInterfaceImpl::setMode( Mode m )
{
    if ( !viewManager || !viewManager->currentView() )
	return;
    ( (QuickEditor*)viewManager->currentView() )->setDebugging( m == Debugging );
}

QStringList QuickEditorInterfaceImpl::featureList() const
{
    QStringList lst;
    lst << "Qt Script Editor Toggle Breakpoint";// << "Qt Script Editor Edit Breakpoints";
    return lst;
}

QAction* QuickEditorInterfaceImpl::create( const QString &action, QObject* parent )
{
    if ( action == "Qt Script Editor Toggle Breakpoint" ) {
	if ( !actionToggleBreakPoint ) {
	    actionToggleBreakPoint = new QAction( "Toggle Breakpoint", createIconSet( "breakpoint.png" ),
		"&Toggle Breakpoint",
		Qt::Key_F9, parent, "quickscript_toggle_breakpoint" );
	    connect( actionToggleBreakPoint, SIGNAL( activated() ), this, SLOT( toggleBreakpoint() ) );
	    actionToggleBreakPoint->setEnabled( FALSE );
	}
	return actionToggleBreakPoint;
//     } else  if ( action == "Qt Script Editor Edit Breakpoints" ) {
// 	if ( !actionEditBreakpoints ) {
// 	    actionEditBreakpoints = new QAction( "Edit Breakpoints", QIconSet(),
// 		"&Edit Breakpoints...", 0, parent,
// 		"quickscript_edit_breakpoints" );
// 	    connect( actionEditBreakpoints, SIGNAL( activated() ), this, SLOT( editBreakpoints() ) );
// 	}
// 	return actionEditBreakpoints;
    }
    return 0;
}

QString QuickEditorInterfaceImpl::group( const QString & ) const
{
    return "Qt Script Editor";
}

void QuickEditorInterfaceImpl::connectTo( QUnknownInterface * )
{
}

bool QuickEditorInterfaceImpl::location( const QString &name, Location l ) const
{
    Q_UNUSED( l )
    if ( name == "Qt Script Editor Toggle Breakpoint" )
	return TRUE;
//     if ( name == "Qt Script Editor Edit Breakpoints" )
// 	return l == Menu;
    return FALSE;
}

void QuickEditorInterfaceImpl::toggleBreakpoint()
{
    QuickEditorInterfaceImpl *iface;
    QTextEdit *e = activeEditor( &iface );
    if ( !e )
	return;
    int para, index;
    e->getCursorPosition( &para, &index );
    QValueList<uint> bps;
    iface->breakPoints( bps );
    if ( bps.find( para ) != bps.end() ) {
	bps.remove( para );
    } else {
	if ( QuickInterpreter::self()->debuggerEngine()->validBreakpoint( e->text(), para ) )
	     bps << para;
    }
    iface->setBreakPoints( bps );
    if ( iface->view_manager() )
	iface->view_manager()->emitMarkersChanged();
    iface->release();
}

void QuickEditorInterfaceImpl::editBreakpoints()
{
    if ( !dIface ) {
	for ( QuickEditorInterfaceImpl *iface = editorInterfaces->first(); iface; iface = editorInterfaces->next() ) {
	    if ( iface->hasDesignerInterface() ) {
		iface->editBreakpoints();
		return;
	    }
	}
	return;
    }

    QuickEditorInterfaceImpl *iface;
    QTextEdit *e = activeEditor( &iface );
    if ( e ) {
	int para, index;
	e->getCursorPosition( &para, &index );
	QValueList<uint> bps;
	iface->breakPoints( bps );
	if ( bps.find( para ) != bps.end() ) {
	    // #### set some variable to initialize the dialog with that breakpoint
	}
	iface->release();
    }
    QMap<QString, QValueList<uint> > bps;
    dIface->currentProject()->breakPoints( bps );
    BreakPointSettings dlg( bps, dIface, 0, 0, TRUE );
    dlg.exec();
}

QTextEdit *QuickEditorInterfaceImpl::activeEditor( QuickEditorInterfaceImpl **eiface ) const
{
    if ( !editorInterfaces )
	return 0;
    for ( QuickEditorInterfaceImpl *iface = editorInterfaces->first(); iface; iface = editorInterfaces->next() ) {
	if ( iface->view_manager() && iface->view_manager()->currentView() &&
	     ( iface->view_manager()->currentView()->hasFocus() ||
	       ( (QTextEdit*)iface->view_manager()->currentView() )->viewport()->hasFocus() ) ) {
	    *eiface = iface;
	    (*eiface)->addRef();
	    return (QTextEdit*)iface->view_manager()->currentView();
	}
    }
    return 0;
}

void QuickEditorInterfaceImpl::isBreakpointPossible( bool &possible, const QString &code, int line )
{
    possible = QuickInterpreter::self()->debuggerEngine()->validBreakpoint( code, line );
}

void QuickEditorInterfaceImpl::toggleDebugger( bool enable )
{
    debuggerEnabled = enable;
//     actionEditBreakpoints->setEnabled( enable );
    actionToggleBreakPoint->setEnabled( enable );
}
