/***************************************************************************
                               domtreeview.cpp
                             -------------------

    copyright            : (C) 2001 - The Kafka Team/Andreas Schlapbach
    email                : kde-kafka@master.kde.org
                           schlpbch@iam.unibe.ch
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/* $Id: domtreeview.cpp,v 1.31 2003/11/03 17:18:00 nhasan Exp $ */

#include <assert.h>

#include <qapplication.h>
#include <qcheckbox.h>
#include <qlayout.h>
#include <qfont.h>
#include <qfile.h>
#include <qtextstream.h>

#include <dom/dom_element.h>
#include <dom/dom_node.h>
#include <dom/dom_exception.h>

#include <kfiledialog.h>
#include <kmessagebox.h>
#include <khtml_part.h>
#include <klocale.h>
#include <kglobalsettings.h>
#include <kdebug.h>
#include <klistview.h>
#include <kstdguiitem.h>
#include <kpushbutton.h>

#include "domtreeview.h"
#include <qpushbutton.h>

DOMTreeView::DOMTreeView(QWidget *parent, KHTMLPart *_part, const char* name, bool allowSaving)
  : KDialog(parent, name), m_expansionDepth(5), m_maxDepth(0),
    m_bPure(true), m_bShowAttributes(true), m_bHighlightHTML(true),
    m_findDialog(0)
{
  part = _part;

  setCaption( i18n( "DOM Tree for %1" ).arg(part->url().prettyURL()) );
  setMinimumHeight(600);
  setMinimumWidth(400);

  m_vLayout1 = new QVBoxLayout( this );
  m_vLayout1->setSpacing( 2 );

  m_listView = new KListView( this );
  m_listView->setFocusPolicy( QWidget::ClickFocus );
  m_vLayout1->addWidget( m_listView );

  m_vLayout2 = new QVBoxLayout;
  m_vLayout2->setMargin( 8 );

  m_hLayout1 = new QHBoxLayout;
  m_hLayout1->setSpacing( 2 );
  m_hLayout1->setMargin( 1 );

  m_pureCheckBox = new QCheckBox( this );
  m_pureCheckBox->setText( i18n( "&Pure" ) );
  m_pureCheckBox->setChecked(m_bPure);
  connect(m_pureCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPureToggled(bool)));
  m_hLayout1->addWidget( m_pureCheckBox );

  m_showAttributesCheckBox = new QCheckBox( this );
  m_showAttributesCheckBox->setText( i18n( "Show &attributes" ) );
  m_showAttributesCheckBox->setChecked(m_bShowAttributes);
  connect(m_showAttributesCheckBox, SIGNAL(toggled(bool)), this,
	  SLOT(slotShowAttributesToggled(bool)));
  m_hLayout1->addWidget( m_showAttributesCheckBox );

  m_highlightHTMLCheckBox = new QCheckBox( this );
  m_highlightHTMLCheckBox->setText( i18n( "Highlight &HTML" ) );
  m_highlightHTMLCheckBox->setChecked(m_bHighlightHTML);
  connect(m_highlightHTMLCheckBox, SIGNAL(toggled(bool)), this,
	  SLOT(slotHighlightHTMLToggled(bool)));
  m_hLayout1->addWidget( m_highlightHTMLCheckBox );

  QSpacerItem* spacer1 = new QSpacerItem( 20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
  m_hLayout1->addItem( spacer1 );

  m_decreaseButton = new KPushButton( this );
  m_decreaseButton->setText( "&<" );
  connect(m_decreaseButton, SIGNAL(clicked()), this, SLOT(slotDecrExpansionDepth()));
  m_hLayout1->addWidget( m_decreaseButton );

  m_increaseButton = new KPushButton( this );
  m_increaseButton->setText( "&>" );
  connect(m_increaseButton, SIGNAL(clicked()), this, SLOT(slotIncrExpansionDepth()));

  m_hLayout1->addWidget( m_increaseButton );

  m_vLayout2->addLayout( m_hLayout1 );

  m_hLayout2 = new QHBoxLayout;
  m_hLayout2->setSpacing( 2 );
  m_hLayout2->setMargin( 2 );

  m_findButton = new KPushButton( this );
  m_findButton->setGuiItem( KGuiItem( i18n( "&Find..." ), "find" ) );
  connect(m_findButton, SIGNAL(clicked()), this, SLOT(slotFindClicked()));
  m_hLayout2->addWidget( m_findButton );

  QSpacerItem* spacer2 = new QSpacerItem( 20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
  m_hLayout2->addItem( spacer2 );

  if (allowSaving) {
    m_saveButton = new KPushButton( this );
    m_saveButton->setText( i18n( "&Save as HTML..." ) );
    connect(m_saveButton, SIGNAL(clicked()), this, SLOT(slotSaveClicked()));
    m_hLayout2->addWidget( m_saveButton );
  }

  m_refreshButton = new KPushButton( this );
  m_refreshButton->setGuiItem( KGuiItem( i18n( "&Refresh" ), "reload" ) );
  connect(m_refreshButton, SIGNAL(clicked()), this, SLOT(slotRefreshClicked()));
  m_hLayout2->addWidget( m_refreshButton );

  m_closeButton = new KPushButton( KStdGuiItem::close(), this );
  connect(m_closeButton, SIGNAL(clicked()), this, SLOT(close()));

  m_hLayout2->addWidget( m_closeButton );

  m_vLayout2->addLayout( m_hLayout2 );
  m_vLayout1->addLayout( m_vLayout2 );

  const QFont font(KGlobalSettings::generalFont());
  m_listView->setFont( font );
  m_listView->setRootIsDecorated(true);
  m_listView->addColumn(i18n("DOM Tree"));
  m_listView->setSorting(-1);
  m_rootListView = m_listView;

  connect(m_listView, SIGNAL(clicked(QListViewItem *)), this,
	  SLOT(slotItemClicked(QListViewItem *)));

  connect(part, SIGNAL(nodeActivated(const DOM::Node &)), this,
	  SLOT(slotShowNode(const DOM::Node &)));

  m_nodedict.setAutoDelete(true);

  slotShowTree(part->document());
  updateIncrDecreaseButton();
}

DOMTreeView::~DOMTreeView()
{
  delete m_findDialog;
  disconnect(part);
}

void DOMTreeView::slotShowNode(const DOM::Node &pNode)
{
  if (m_itemdict[pNode.handle()]) {
    m_listView->setCurrentItem(m_itemdict[pNode.handle()]);
    m_listView->ensureItemVisible(m_itemdict[pNode.handle()]);
  }
}

void DOMTreeView::slotShowTree(const DOM::Node &pNode)
{
  DOM::Node child;

  m_listView->clear();
  m_itemdict.clear();
  m_nodedict.clear();

  try
  {
    child = pNode.firstChild();
  }
  catch (...)
  {
    return;
  }

  while(!child.isNull()) {
    showRecursive(0, child, 0);
    child = child.nextSibling();
  }

  m_maxDepth--;
  //kdDebug() << " Max Depth: " << m_maxDepth << endl;
}

void DOMTreeView::showRecursive(const DOM::Node &pNode, const DOM::Node &node, uint depth)
{
  DOMListViewItem *cur_item;

  if (depth > m_maxDepth) {
    m_maxDepth = depth;
  }

  if (depth == 0) {
    cur_item = new DOMListViewItem(m_listView);
    m_document = pNode.ownerDocument();
  } else {
    cur_item = new DOMListViewItem(m_itemdict[pNode.handle()]);
  }

  //kdDebug() << node.nodeName().string() << " [" << depth << "]" << endl;
  addElement (node, cur_item, false);
  cur_item->setOpen(depth < m_expansionDepth);

  if(node.handle()) {
    m_itemdict.insert(node.handle(), cur_item);
    m_nodedict.insert(cur_item, new DOM::Node(node));
  }

  DOM::Node child = node.lastChild();
  depth++;
  while(!child.isNull()) {
    showRecursive(node, child, depth);
    child = child.previousSibling();
  }

  const DOM::Element element = static_cast<const DOM::Element>(node);
  if (!m_bPure) {
    if (!(element.isNull()) && (!(element.firstChild().isNull()))) {
      if(depth == 1) {
	cur_item = new DOMListViewItem(m_listView, cur_item);
	m_document = pNode.ownerDocument();
      } else {
	cur_item = new DOMListViewItem(m_itemdict[pNode.handle()], cur_item);
      }
      //kdDebug() << "</" << node.nodeName().string() << ">" << endl;
      addElement(element, cur_item, true);
      cur_item->setOpen(depth < m_expansionDepth);
    }
  }
}

void DOMTreeView::addElement ( const DOM::Node &node,  DOMListViewItem *cur_item, bool isLast)
{
  const QString nodeName(node.nodeName().string());
  QString text;
  const DOM::Element element = static_cast<const DOM::Element> (node);
  if (!element.isNull()) {
    if (!m_bPure) {
      if (isLast) {
	text ="</";
      } else {
	text = "<";
      }
      text += nodeName;
    } else {
      text = nodeName;
    }

    if (m_bShowAttributes && !isLast) {
      QString attributes;
      DOM::Attr attr;
      DOM::NamedNodeMap attrs = element.attributes();
      unsigned long lmap = attrs.length();
      for( unsigned int j=0; j<lmap; j++ ) {
	attr = static_cast<DOM::Attr>(attrs.item(j));
	attributes += " " + attr.name().string() + "=\"" + attr.value().string() + "\"";
      }
      if (!(attributes.isEmpty())) {
	text += " ";
      }
      text += attributes.simplifyWhiteSpace();
    }

    if (!m_bPure) {
      if(element.firstChild().isNull()) {
	text += "/>";
      } else {
	text += ">";
      }
    }
    cur_item->setText(0, text);
  } else {
    text = "`" + node.nodeValue().string() + "'";

    // Hacks to deal with PRE
    QTextStream ts( text, IO_ReadOnly );
    while (!ts.eof()) {
      const QString txt(ts.readLine());
      const QFont font(KGlobalSettings::fixedFont());
      cur_item->setFont( font );
      cur_item->setNamedColor("#444444");
      cur_item->setText(0, txt);

      if(node.handle()) {
	m_itemdict.insert(node.handle(), cur_item);
	m_nodedict.insert(cur_item, new DOM::Node(node));
      }

      DOMListViewItem *parent;
      if (cur_item->parent()) {
	parent = static_cast<DOMListViewItem *>(cur_item->parent());
      } else {
	parent = cur_item;
      }
      cur_item = new DOMListViewItem(parent, cur_item);
    }
    // This is one is too much
    DOMListViewItem *notLastItem = static_cast<DOMListViewItem *>(cur_item->itemAbove());
    delete cur_item;
    cur_item = notLastItem;
  }

  if (m_bHighlightHTML) {
    highlightHTML(cur_item, nodeName);
  }
}

void DOMTreeView::highlightHTML(DOMListViewItem *cur_item, const QString &nodeName)
{
  /* This is slow. I could make it O(1) be using the tokenizer of khtml but I don't
   * think it's worth it.
   */

  QString namedColor("#000000");
  QString tagName = nodeName.upper();
  if ( tagName == "HTML" ) {
    namedColor = "#0000ff";
    cur_item->setBold(true);
  } else if ( tagName == "HEAD" ) {
    namedColor = "#0022ff";
    cur_item->setBold(true);

  } else if ( tagName == "TITLE" ) {
    namedColor = "#2200ff";
  } else if ( tagName == "SCRIPT" ) {
    namedColor = "#4400ff";
  } else if ( tagName == "NOSCRIPT" ) {
    namedColor = "#0044ff";
  } else if ( tagName == "STYLE" ) {
    namedColor = "#0066ff";
  } else if ( tagName == "LINK" ) {
    namedColor = "#6600ff";
  } else if ( tagName == "META" ) {
    namedColor = "#0088ff";

  } else if ( tagName == "BODY" ) {
    namedColor = "#ff0000";
    cur_item->setBold(true);
  } else if ( tagName == "A" || tagName == "IMG") {
    namedColor = "magenta";
    cur_item->setUnderline(true);

  } else if ( tagName == "DIV" ) {
    namedColor = "#ff0044";
  } else if ( tagName == "SPAN" ) {
    namedColor = "#ff4400";
  } else if ( tagName == "P" ) {
    namedColor = "#ff0066";

  } else if ( tagName == "DL" || tagName == "OL"|| tagName == "UL" || tagName == "TABLE" ) {
    namedColor = "#880088";
  } else if ( tagName == "LI" ) {
    namedColor = "#884488";
  } else if ( tagName == "TBODY" ){
    namedColor = "#888888";
  } else if ( tagName == "TR" ) {
    namedColor = "#882288";
  } else if ( tagName == "TD" ) {
    namedColor = "#886688";

  } else if ((tagName == "H1")||(tagName == "H2")||(tagName == "H3") ||
	     (tagName == "H4")||(tagName == "H5")||(tagName == "H6")) {
    namedColor = "#008800";
  } else if (tagName == "HR" ) {
    namedColor = "#228822";

  } else if ( tagName == "FRAME" || tagName == "IFRAME" ) {
    namedColor = "#ff22ff";
  } else if ( tagName == "FRAMESET" ) {
    namedColor = "#dd22dd";

  } else if ( tagName == "APPLET" || tagName == "OBJECT" ) {
    namedColor = "#bb22bb";

  } else if ( tagName == "BASEFONT" || tagName == "FONT" ) {
    namedColor = "#097200";

  } else if ( (tagName == "B") || (tagName == "I") ) {
    cur_item->setBold(true);
  } else if ( tagName == "U") {
    cur_item->setUnderline(true);
  }

  cur_item->setNamedColor(namedColor);
}

void DOMTreeView::slotItemClicked(QListViewItem *cur_item)
{
  DOM::Node *handle = m_nodedict[cur_item];
  if(handle) {
    emit part->setActiveNode(*handle);
  }
}

void DOMTreeView::slotFindClicked()
{
  if (m_findDialog == 0) {
    m_findDialog = new KEdFind(this);
    connect(m_findDialog, SIGNAL(search()), this, SLOT(slotSearch()));
  }
  m_findDialog->show();
}

void DOMTreeView::slotSearch()
{
  assert(m_findDialog);
  const QString& searchText = m_findDialog->getText();
  bool caseSensitive = m_findDialog->case_sensitive();

  searchRecursive(static_cast<DOMListViewItem*>(m_rootListView->firstChild()),
		  searchText, caseSensitive);

  m_findDialog->hide();
}

void DOMTreeView::searchRecursive(DOMListViewItem* cur_item, const QString& searchText,
				  bool caseSensitive)
{
  const QString text(cur_item->text(0));
  if (text.contains(searchText, caseSensitive) > 0) {
    cur_item->setUnderline(true);
    cur_item->setItalic(true);
    m_listView->setCurrentItem(cur_item);
    m_listView->ensureItemVisible(cur_item);
  } else {
    cur_item->setOpen(false);
  }

  DOMListViewItem* child = static_cast<DOMListViewItem *>(cur_item->firstChild());
  while( child ) {
    searchRecursive(child, searchText, caseSensitive);
    child = static_cast<DOMListViewItem *>(child->nextSibling());
  }
}

#if 0
void DOMTreeView::slotSaveClicked()
{
  //kdDebug() << "void KfingerCSSWidget::slotSaveAs()" << endl;
  KURL url = KFileDialog::getSaveFileName( part->url().url(), "*.html",
					   this, i18n("Save DOM Tree as HTML") );
  if (!(url.isEmpty()) && url.isValid()) {
    QFile file(url.path());

    if (file.exists()) {
      const QString title = i18n( "File Exists" );
      const QString text = i18n( "Do you really want to overwrite: \n%1?" ).arg(url.url());
      if (KMessageBox::Yes != KMessageBox::warningYesNoCancel(this, text, title ) ) {
	return;
      }
    }

    if (file.open(IO_WriteOnly) ) {
      kdDebug() << "Opened File: " << url.url() << endl;
      m_textStream = new QTextStream(&file); //(stdOut)
      saveTreeAsHTML(part->document());
      file.close();
      kdDebug() << "File closed " << endl;
      delete m_textStream;
    } else {
      const QString title = i18n( "Unable to Open File" );
      const QString text = i18n( "Unable to open \n %1 \n for writing" ).arg(url.path());
      KMessageBox::sorry( this, text, title );
    }
  } else {
    const QString title = i18n( "Invalid URL" );
    const QString text = i18n( "This URL \n %1 \n is not valid." ).arg(url.url());
    KMessageBox::sorry( this, text, title );
  }
}

void DOMTreeView::saveTreeAsHTML(const DOM::Node &pNode)
{
  assert(m_textStream);

  // Add a doctype

  (*m_textStream) <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" << endl;
  if(pNode.ownerDocument().isNull()) {
    saveRecursive(pNode, 0);
  } else {
    saveRecursive(pNode.ownerDocument(), 0);
  }
}

void DOMTreeView::saveRecursive(const DOM::Node &pNode, int indent)
{
  const QString nodeName(pNode.nodeName().string());
  QString text;
  QString strIndent;
  strIndent.fill(' ', indent);
  const DOM::Element element = static_cast<const DOM::Element>(pNode);

  text = strIndent;

  if ( !element.isNull() ) {
    if (nodeName.at(0)=='-') {
      /* Don't save khtml internal tags '-konq..'
       * Approximating it with <DIV>
       */
      text += "<DIV> <!-- -KONG_BLOCK -->";
    } else {
      text += "<" + nodeName;

      QString attributes;
      DOM::Attr attr;
      const DOM::NamedNodeMap attrs = element.attributes();
      unsigned long lmap = attrs.length();
      for( uint j=0; j<lmap; j++ ) {
	attr = static_cast<DOM::Attr>(attrs.item(j));
	attributes += " " + attr.name().string() + "=\"" + attr.value().string() + "\"";
      }
      if (!(attributes.isEmpty())){
	text += " ";
      }

      text += attributes.simplifyWhiteSpace();

      if(element.firstChild().isNull()) {
	text += "/>";
      } else {
	text += ">";
      }
    }
  } else {
    text = strIndent + pNode.nodeValue().string();
  }

  kdDebug() << text << endl;
  if (!(text.isEmpty())) {
    (*m_textStream) << text << endl;
  }

  DOM::Node child = pNode.firstChild();
  while(!child.isNull()) {
    saveRecursive(child, indent+2);
    child = child.nextSibling();
  }

  if (!(element.isNull()) && (!(element.firstChild().isNull()))) {
    if (nodeName.at(0)=='-') {
      text = strIndent + "</DIV> <!-- -KONG_BLOCK -->";
    } else {
      text = strIndent + "</" + pNode.nodeName().string() + ">";
    }
    kdDebug() << text << endl;
    (*m_textStream) << text << endl;
  }
}
#endif

void DOMTreeView::updateIncrDecreaseButton()
{
    m_decreaseButton->setEnabled((m_expansionDepth > 0));
    m_increaseButton->setEnabled((m_expansionDepth < m_maxDepth));
}

void DOMTreeView::slotRefreshClicked()
{
  slotShowTree(part->document());
}

void DOMTreeView::slotIncrExpansionDepth()
{
  if (m_expansionDepth < m_maxDepth) {
    ++m_expansionDepth;
    adjustDepth(m_rootListView->firstChild(), 0);
    updateIncrDecreaseButton();
  } else {
    QApplication::beep();
  }
}

void DOMTreeView::slotDecrExpansionDepth()
{
  if (m_expansionDepth > 0) {
    --m_expansionDepth;
    adjustDepth(m_rootListView->firstChild(), 0);
    updateIncrDecreaseButton();
  } else {
    QApplication::beep();
  }
}

void DOMTreeView::adjustDepth(QListViewItem *cur_item,  uint currDepth)
{
  if (!(cur_item == 0)) {
    while( cur_item ) {
      cur_item->setOpen( (m_expansionDepth > currDepth) );
      adjustDepth(cur_item->firstChild(), currDepth+1);
      cur_item = cur_item->nextSibling();
    }
  }
}

void DOMTreeView::slotPureToggled(bool b)
{
  m_bPure = b;
  slotRefreshClicked();
}

void DOMTreeView::slotShowAttributesToggled(bool b)
{
  m_bShowAttributes = b;
  slotRefreshClicked();
}

void DOMTreeView::slotHighlightHTMLToggled(bool b)
{
  m_bHighlightHTML = b;
  slotRefreshClicked();
}

#include "domtreeview.moc"
