/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
 *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _KJS_DEBUGGER_H_
#define _KJS_DEBUGGER_H_

#include <qglobal.h>

#define KJS_DEBUGGER

#ifdef KJS_DEBUGGER

#include <qwidget.h>
#include <qmultilineedit.h>
#include <qpixmap.h>
#include <qptrlist.h>
#include <qptrstack.h>
#include <qcheckbox.h>
#include <kdialogbase.h>
#include <kmainwindow.h>
#include <qscrollview.h>

#include <kjs/debugger.h>

#include "dom/dom_misc.h"

class QListBox;
class QComboBox;
class KActionCollection;
class KAction;

namespace KJS {
  class FunctionImp;
  class List;
  class Interpreter;
  class KJSDebugWin;

  class SourceFile : public DOM::DomShared
  {
   public:
    SourceFile(QString u, QString c, Interpreter *interp) 
	: url(u), code(c), interpreter(interp) {}
    QString getCode();
    QString url;
    QString code;
    Interpreter *interpreter;
  };

  /**
   * @internal
   *
   * When kjs parses some code, it generates a source code fragment (or just "source").
   * This is referenced by its source id in future calls to functions such as atLine()
   * and callEvent(). We keep a record of all source fragments parsed in order to display
   * then to the user.
   *
   * For .js files, the source fragment will be the entire file. For js code included
   * in html files, however, there may be multiple source fragments within the one file
   * (e.g. multiple SCRIPT tags or onclick="..." attributes)
   *
   * In the case where a single file has multiple source fragments, the source objects
   * for these fragments will all point to the same SourceFile for their code.
   */
  class SourceFragment
  {
  public:
    SourceFragment(int sid, int bl, int el, SourceFile *sf);
    ~SourceFragment();

    int sourceId;
    int baseLine;
    int errorLine;
    SourceFile *sourceFile;
  private:
    SourceFragment(const SourceFragment& other);
    SourceFragment& operator = (const SourceFragment& other);
  };

  class KJSErrorDialog : public KDialogBase {
    Q_OBJECT
  public:
    KJSErrorDialog(QWidget *parent, const QString& errorMessage, bool showDebug);
    virtual ~KJSErrorDialog();

    bool debugSelected() const { return m_debugSelected; }
    bool dontShowAgain() const { return m_dontShowAgainCb->isChecked(); }

  protected slots:
    virtual void slotUser1();

  private:
    QCheckBox *m_dontShowAgainCb;
    bool m_debugSelected;
  };

  class EvalMultiLineEdit : public QMultiLineEdit {
    Q_OBJECT
  public:
      EvalMultiLineEdit(QWidget *parent);
      const QString & code() const { return m_code; }
  protected:
      void keyPressEvent(QKeyEvent * e);
  private:
      QString m_code;
  };

  class SourceDisplay : public QScrollView {
    Q_OBJECT
  public:
    SourceDisplay(KJSDebugWin *debugWin, QWidget *parent, const char *name = 0);
    ~SourceDisplay();

    void setSource(SourceFile *sourceFile);
    void setCurrentLine(int lineno, bool doCenter = true);

  signals:
    void lineDoubleClicked(int lineno);

  protected:
    virtual void contentsMousePressEvent(QMouseEvent *e);
    virtual void showEvent(QShowEvent *);
    virtual void drawContents(QPainter *p, int clipx, int clipy, int clipw, int cliph);

    QString m_source;
    int m_currentLine;
    SourceFile *m_sourceFile;
    QStringList m_lines;

    KJSDebugWin *m_debugWin;
    QFont m_font;
    QPixmap m_breakpointIcon;
  };

  /**
   * @internal
   *
   * KJSDebugWin represents the debugger window that is visible to the user. It contains
   * a stack frame list, a code viewer and a source fragment selector, plus buttons
   * to control execution including next, step and continue.
   *
   * There is only one debug window per program. This can be obtained by calling #instance
   */
  class KJSDebugWin : public KMainWindow, public Debugger, public KInstance
  {
    Q_OBJECT
    friend class SourceDisplay;
  public:
    KJSDebugWin(QWidget *parent=0, const char *name=0);
    virtual ~KJSDebugWin();

    static KJSDebugWin *createInstance();
    static void destroyInstance();
    static KJSDebugWin *debugWindow() { return kjs_html_debugger; }

    enum Mode { Disabled = 0, // No break on any statements
		Next     = 1, // Will break on next statement in current context
		Step     = 2, // Will break on next statement in current or deeper context
		Continue = 3, // Will continue until next breakpoint
		Stop     = 4  // The script will stop execution completely,
			      // as soon as possible
    };

    void setSourceLine(int sourceId, int lineno);
    void setNextSourceInfo(QString url, int baseLine);
    void sourceChanged(Interpreter *interpreter, QString url);
    bool inSession() const { return !m_execStates.isEmpty(); }
    void setMode(Mode m) { m_mode = m; }
    void clearInterpreter(Interpreter *interpreter);
    ExecState *getExecState() const { return m_execStates.top(); }

    // functions overridden from KJS:Debugger
    bool sourceParsed(ExecState *exec, int sourceId,
		      const UString &source, int errorLine);
    bool sourceUnused(ExecState * exec, int sourceId);
    bool exception(ExecState *exec, const Value &value, bool inTryCatch);
    bool atStatement(ExecState *exec);
    bool enterContext(ExecState *exec);
    bool exitContext(ExecState *exec, const Completion &completion);

  public slots:
    void slotNext();
    void slotStep();
    void slotContinue();
    void slotStop();
    void slotBreakNext();
    void slotToggleBreakpoint(int lineno);
    void slotShowFrame(int frameno);
    void slotSourceSelected(int sourceSelIndex);
    void slotEval();

  protected:

    void closeEvent(QCloseEvent *e);
    bool eventFilter(QObject *obj, QEvent *evt);
    void disableOtherWindows();
    void enableOtherWindows();

  private:

    SourceFile *getSourceFile(Interpreter *interpreter, QString url);
    void setSourceFile(Interpreter *interpreter, QString url, SourceFile *sourceFile);
    void removeSourceFile(Interpreter *interpreter, QString url);

    void checkBreak(ExecState *exec);
    void enterSession(ExecState *exec);
    void leaveSession();
    void displaySourceFile(SourceFile *sourceFile, bool forceRefresh);
    void updateContextList();

    QString contextStr(const Context &ctx);

    struct Breakpoint {
      int sourceId;
      int lineno;
    };
    Breakpoint *m_breakpoints;
    int m_breakpointCount;
    bool setBreakpoint(int sourceId, int lineno);
    bool deleteBreakpoint(int sourceId, int lineno);
    bool haveBreakpoint(SourceFile *sourceFile, int line0, int line1);
    bool haveBreakpoint(int sourceId, int line0, int line1) const {
      for (int i = 0; i < m_breakpointCount; i++) {
	if (m_breakpoints[i].sourceId == sourceId &&
	    m_breakpoints[i].lineno >= line0 &&
	    m_breakpoints[i].lineno <= line1)
	  return true;
      }
      return false;
    }

    SourceFile *m_curSourceFile;
    Mode m_mode;
    QString m_nextSourceUrl;
    int m_nextSourceBaseLine;
    QPtrStack<ExecState> m_execStates;
    ExecState **m_execs;
    int m_execsCount;
    int m_execsAlloc;
    int m_steppingDepth;

    QMap<QString,SourceFile*> m_sourceFiles; /* maps url->SourceFile */
    QMap<int,SourceFragment*> m_sourceFragments; /* maps SourceId->SourceFragment */
    QPtrList<SourceFile> m_sourceSelFiles; /* maps combobox index->SourceFile */

    KActionCollection *m_actionCollection;
    QPixmap m_stopIcon;
    QPixmap m_emptyIcon;
    SourceDisplay *m_sourceDisplay;
    QListBox *m_contextList;

    KAction *m_stepAction;
    KAction *m_nextAction;
    KAction *m_continueAction;
    KAction *m_stopAction;
    KAction *m_breakAction;

    QComboBox *m_sourceSel;
    EvalMultiLineEdit *m_evalEdit;
    int m_evalDepth;

    static KJSDebugWin *kjs_html_debugger;
  };

} // namespace

#endif // KJS_DEBUGGER

#endif // _KJS_DEBUGGER_H_
