/*********
*
* This file is part of BibleTime's source code, http://www.bibletime.info/.
*
* Copyright 1999-2006 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License version 2.0.
*
**********/



#include "chtmlreaddisplay.h"

#include "frontend/displaywindow/cdisplaywindow.h"
#include "frontend/displaywindow/creadwindow.h"
#include "backend/creferencemanager.h"
#include "backend/cswordkey.h"

#include "frontend/cbtconfig.h"
#include "frontend/cdragdropmgr.h"
#include "frontend/cinfodisplay.h"

#include "util/ctoolclass.h"
#include "util/cpointers.h"
#include "util/scoped_resource.h"

//We will need to reference this in the TQt includes
#include <tdeversion.h>

//TQt includes
#include <tqcursor.h>
#include <tqscrollview.h>
#include <tqwidget.h>
#include <tqdragobject.h>
#include <tqpopupmenu.h>
#include <tqlayout.h>
#include <tqtimer.h>
#if TDE_VERSION < 0x030300
//We will need to show the error message.
#include <tqmessagebox.h>
#endif

//KDE includes
#include <tdeapplication.h>
#include <tdehtmlview.h>
#include <tdeglobalsettings.h>
#include <tdehtml_events.h>

#include <dom/dom2_range.h>
#include <dom/html_element.h>
#include <dom/dom2_traversal.h>

using namespace InfoDisplay;

CHTMLReadDisplay::CHTMLReadDisplay(CReadWindow* readWindow, TQWidget* parentWidget)
: TDEHTMLPart((m_view = new CHTMLReadDisplayView(this, parentWidget ? parentWidget : readWindow)), TQT_TQOBJECT(readWindow ? readWindow : parentWidget)),
CReadDisplay(readWindow),
m_currentAnchorCache(TQString()) {
	setDNDEnabled(false);
	setJavaEnabled(false);
	setJScriptEnabled(false);
	setPluginsEnabled(false);

	m_view->setDragAutoScroll(false);

}

CHTMLReadDisplay::~CHTMLReadDisplay() {}

const TQString CHTMLReadDisplay::text( const CDisplay::TextType format, const CDisplay::TextPart part) {

	switch (part) {
		case Document: {
			if (format == HTMLText) {
				return document().toHTML();
			}
			else {
    			CDisplayWindow* window = parentWindow();
				CSwordKey* const key = window->key();
    			CSwordModuleInfo* module = key->module();
				//return htmlDocument().body().innerText().string().latin1();
				//This function is never used for Bibles, so it is not
				//implemented.  If it should be, see CReadDisplay::print() for
				//example code.
				Q_ASSERT(module->type() == CSwordModuleInfo::Lexicon || module->type() == CSwordModuleInfo::Commentary || module->type() == CSwordModuleInfo::GenericBook);
				if (module->type() == CSwordModuleInfo::Lexicon || module->type() == CSwordModuleInfo::Commentary || module->type() == CSwordModuleInfo::GenericBook) {
				//TODO: This is a BAD HACK, we have to fnd a better solution to manage the settings now
				CSwordBackend::FilterOptions filterOptions;
				filterOptions.footnotes = false;
				filterOptions.strongNumbers = false;
				filterOptions.morphTags = false;
				filterOptions.lemmas = false;
				filterOptions.scriptureReferences = false;
				filterOptions.textualVariants = false;

				CPointers::backend()->setFilterOptions(filterOptions);

				return TQString(key->strippedText()).append("\n(")
					.append(key->key())
					.append(", ")
					.append(key->module()->name())
					.append(")");
				}
			}
		}

		case SelectedText: {
			if (!hasSelection()) {
				return TQString();
			}
			else if (format == HTMLText) {
				DOM::Range range = selection();
				return range.toHTML().string();
			}
			else { //plain text requested
				return selectedText();
			}
		}

		case AnchorOnly: {
			TQString moduleName;
			TQString keyName;
			CReferenceManager::Type type;
			CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);

			return keyName;
		}

		case AnchorTextOnly: {
			TQString moduleName;
			TQString keyName;
			CReferenceManager::Type type;
			CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);

			if (CSwordModuleInfo* module = backend()->findModuleByName(moduleName)) {
				util::scoped_ptr<CSwordKey> key( CSwordKey::createInstance(module) );
				key->key( keyName );

				return key->strippedText();
			}
			return TQString();
		}

		case AnchorWithText: {
			TQString moduleName;
			TQString keyName;
			CReferenceManager::Type type;
			CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);

			if (CSwordModuleInfo* module = backend()->findModuleByName(moduleName)) {
				util::scoped_ptr<CSwordKey> key( CSwordKey::createInstance(module) );
				key->key( keyName );

				//TODO: This is a BAD HACK, we have to fnd a better solution to manage the settings now
				CSwordBackend::FilterOptions filterOptions;
				filterOptions.footnotes = false;
				filterOptions.strongNumbers = false;
				filterOptions.morphTags = false;
				filterOptions.lemmas = false;
				filterOptions.scriptureReferences = false;
				filterOptions.textualVariants = false;

				CPointers::backend()->setFilterOptions(filterOptions);

				return TQString(key->strippedText()).append("\n(")
					.append(key->key())
					.append(", ")
					.append(key->module()->name())
					.append(")");
				/*    ("%1\n(%2, %3)")
					.arg()
					.arg(key->key())
					.arg(key->module()->name());*/
			}
			return TQString();
		}
		default:
		return TQString();
	}
}

void CHTMLReadDisplay::setText( const TQString& newText ) {
	begin();
	write(newText);
	end();
}

/** No descriptions */
const bool CHTMLReadDisplay::hasSelection() {
	return TDEHTMLPart::hasSelection();
}


/** Reimplementation. */
TQScrollView* CHTMLReadDisplay::view() {
	return TDEHTMLPart::view();
}

void CHTMLReadDisplay::selectAll() {
	TDEHTMLPart::selectAll();
}

/** No descriptions */
void CHTMLReadDisplay::moveToAnchor( const TQString& anchor ) {
	m_currentAnchorCache = anchor;

	//This is an ugly hack to work around a KDE problem in KDE including 3.3.1 (no later versions tested so far)
	TQTimer::singleShot(0, this, TQT_SLOT(slotGoToAnchor()));

	// instead of:
	//  slotGoToAnchor();
}

void CHTMLReadDisplay::urlSelected( const TQString& url, int button, int state, const TQString& _target, KParts::URLArgs args) {
	TDEHTMLPart::urlSelected(url, button, state, _target, args);
	m_urlWorkaroundData.doWorkaround = false;
	//  tqWarning("clicked: %s", url.latin1());
	if (!url.isEmpty() && CReferenceManager::isHyperlink(url)) {
		TQString module;
		TQString key;
		CReferenceManager::Type type;

		CReferenceManager::decodeHyperlink(url, module, key, type);
		if (module.isEmpty()) {
			module = CReferenceManager::preferredModule( type );
		}

		// we have to use this workaround, otherwise the widget would scroll because it was interrupted
		// between mouseClick and mouseRelease (I guess)
		m_urlWorkaroundData.doWorkaround = true;
		m_urlWorkaroundData.url = url;
		m_urlWorkaroundData.state = state;
		m_urlWorkaroundData.button = button;
		m_urlWorkaroundData.target = _target;
		m_urlWorkaroundData.args = args;
		m_urlWorkaroundData.module = module;
		m_urlWorkaroundData.key = key;
	}
	else if (!url.isEmpty() && (url.left(1) == "#")) { //anchor
		moveToAnchor(url.mid(1));
	}
	else if (url.left(7) == "http://") { //open the bowser configured by kdeb
		TDEApplication::kApplication()->invokeBrowser( url ); //ToDo: Not yet tested
	}
}

/** Reimplementation. */
void CHTMLReadDisplay::tdehtmlMouseReleaseEvent( tdehtml::MouseReleaseEvent* event ) {
	TDEHTMLPart::tdehtmlMouseReleaseEvent(event);

	m_dndData.mousePressed = false;
	m_dndData.isDragging = false;
	m_dndData.node = DOM::Node();
	m_dndData.anchor = DOM::DOMString();

	if (m_urlWorkaroundData.doWorkaround) {
		m_urlWorkaroundData.doWorkaround = false;
		connectionsProxy()->emitReferenceClicked(
			m_urlWorkaroundData.module,
			m_urlWorkaroundData.key
		);
	}
}

void CHTMLReadDisplay::tdehtmlMousePressEvent( tdehtml::MousePressEvent* event ) {
	m_dndData.node = DOM::Node();
	m_dndData.anchor = DOM::DOMString();
	m_dndData.mousePressed = false;
	m_dndData.isDragging = false;

	if (event->qmouseEvent()->button() == Qt::RightButton) {
		DOM::Node tmpNode = event->innerNode();
		DOM::Node attr;
		m_nodeInfo[CDisplay::Lemma] = TQString();

		do {
			if (!tmpNode.isNull() && (tmpNode.nodeType() == 
						DOM::Node::ELEMENT_NODE) && tmpNode.hasAttributes()) {
				attr = tmpNode.attributes().getNamedItem("lemma");
				if (!attr.isNull()) {
					m_nodeInfo[ CDisplay::Lemma ] = attr.nodeValue().string();
					break;
				}
			}
			tmpNode = tmpNode.parentNode();
		} while ( !tmpNode.isNull() );
		
		setActiveAnchor( event->url().string() );
	}
	else if (event->qmouseEvent()->button() == Qt::LeftButton) {
		m_dndData.node = event->innerNode();
		m_dndData.anchor = event->url();
		m_dndData.mousePressed = true;
		m_dndData.isDragging = false;
		m_dndData.startPos = TQPoint(event->x(), event->y());
		m_dndData.selection = selectedText();

		if (!m_dndData.node.isNull()) { //we drag a valid link
			m_dndData.dragType = DNDData::Link;
		}
	}

	TDEHTMLPart::tdehtmlMousePressEvent(event);
}

/** Reimplementation for our drag&drop system. Also needed for the mouse tracking */
void CHTMLReadDisplay::tdehtmlMouseMoveEvent( tdehtml::MouseMoveEvent* e ) {
	if( e->qmouseEvent()->state() & Qt::LeftButton == Qt::LeftButton) { //left mouse button pressed
		const int delay = TDEGlobalSettings::dndEventDelay();
		TQPoint newPos = TQPoint(e->x(), e->y());
	
		if ( (newPos.x() > m_dndData.startPos.x()+delay || newPos.x() < (m_dndData.startPos.x()-delay) ||
				newPos.y() > m_dndData.startPos.y()+delay || newPos.y() < (m_dndData.startPos.y()-delay)) &&
				!m_dndData.isDragging && m_dndData.mousePressed  ) {
			TQDragObject* d = 0;
			if (!m_dndData.anchor.isEmpty() && (m_dndData.dragType == DNDData::Link) && !m_dndData.node.isNull() ) {
				// create a new bookmark drag!
				TQString module = TQString();
				TQString key = TQString();
				CReferenceManager::Type type;
				if ( !CReferenceManager::decodeHyperlink(m_dndData.anchor.string(), module, key, type) )
					return;
	
				CDragDropMgr::ItemList dndItems;
				//no description!
				dndItems.append( CDragDropMgr::Item(module, key, TQString()) ); 
				d = CDragDropMgr::dragObject(dndItems, TDEHTMLPart::view()->viewport());
			}
			else if ((m_dndData.dragType == DNDData::Text) && !m_dndData.selection.isEmpty()) {    
				// create a new plain text drag!
				CDragDropMgr::ItemList dndItems;
				dndItems.append( CDragDropMgr::Item(m_dndData.selection) ); //no description!
				d = CDragDropMgr::dragObject(dndItems, TDEHTMLPart::view()->viewport());
			}
	
			if (d) {
				m_dndData.isDragging = true;
				m_dndData.mousePressed = false;
	
				//first make a virtual mouse click to end the selection, it it's in progress
				TQMouseEvent e(TQEvent::MouseButtonRelease, TQPoint(0,0), Qt::LeftButton, Qt::LeftButton);
				TDEApplication::sendEvent(view()->viewport(), &e);
				d->drag();
			}
		}
	}
	else if (getMouseTracking() && !(e->qmouseEvent()->state() & TQt::ShiftButton == TQt::ShiftButton)) { 
		//no mouse button pressed and tracking enabled
		DOM::Node node = e->innerNode();
		//if no link was under the mouse try to find a title attribute
		if (!node.isNull() && (m_previousEventNode != node)) {
			// we want to avoid processing the node again
			// After some millisecs the new timer activates the Mag window update, see timerEvent()
			// SHIFT key not pressed, so we start timer
			if ( !(e->qmouseEvent()->state() & TQt::ShiftButton)) { 
				// TQObject has simple timer
				killTimers(); 
				startTimer( CBTConfig::get(CBTConfig::magDelay) );
			}
	
			m_previousEventNode = node;
		}
	}
		
	TDEHTMLPart::tdehtmlMouseMoveEvent(e);
}

/** The Mag window update happens here if the mouse has not moved to another node after starting the timer.*/
void CHTMLReadDisplay::timerEvent( TQTimerEvent *e ) {
	killTimers();
	DOM::Node currentNode = nodeUnderMouse();
	CInfoDisplay::ListInfoData infoList;
	
	// Process the node under cursor if it is the same as at the start of the timer
	if (!currentNode.isNull() && (currentNode != m_previousEventNode) && this->view()->hasMouse()) {
		DOM::Node attr;
		do {
			if (!currentNode.isNull() && (currentNode.nodeType() == DOM::Node::ELEMENT_NODE) && currentNode.hasAttributes()) { //found right node
				attr = currentNode.attributes().getNamedItem("note");
				if (!attr.isNull()) {
					infoList.append( tqMakePair(CInfoDisplay::Footnote, attr.nodeValue().string()) );
				}
	
				attr = currentNode.attributes().getNamedItem("lemma");
				if (!attr.isNull()) {
					infoList.append( tqMakePair(CInfoDisplay::Lemma, attr.nodeValue().string()) );
				}
	
				attr = currentNode.attributes().getNamedItem("morph");
				if (!attr.isNull()) {
					infoList.append( tqMakePair(CInfoDisplay::Morph, attr.nodeValue().string()) );
				}
	
				attr = currentNode.attributes().getNamedItem("expansion");
				if (!attr.isNull()) {
					infoList.append( tqMakePair(CInfoDisplay::Abbreviation, attr.nodeValue().string()) );
				}
				
				attr = currentNode.attributes().getNamedItem("crossrefs");
				if (!attr.isNull()) {
					infoList.append( tqMakePair(CInfoDisplay::CrossReference, attr.nodeValue().string()) );
				}
			}
	
			currentNode = currentNode.parentNode();
			if (!currentNode.isNull() && currentNode.hasAttributes()) {
				attr = currentNode.attributes().getNamedItem("class");
				if (!attr.isNull() && (attr.nodeValue().string() == "entry") || (attr.nodeValue().string() == "currententry") ) {
					break;
				}
			}
		}
		while ( !currentNode.isNull() );
	}
	
	// Update the mag if there is new content
	if (!(infoList.isEmpty())) {
		CPointers::infoDisplay()->setInfo(infoList);
	}

}

// ---------------------

CHTMLReadDisplayView::CHTMLReadDisplayView(CHTMLReadDisplay* displayWidget, TQWidget* parent) : TDEHTMLView(displayWidget, parent), m_display(displayWidget) {
	viewport()->setAcceptDrops(true);
	setMarginWidth(4);
	setMarginHeight(4);
};


/** Opens the popupmenu at the given position. */
void CHTMLReadDisplayView::popupMenu( const TQString& url, const TQPoint& pos) {
	if (!url.isEmpty()) {
		m_display->setActiveAnchor(url);
	}
	if (TQPopupMenu* popup = m_display->installedPopup()) {
		popup->exec(pos);
	}
}

/** Reimplementation from TQScrollView. Sets the right slots */
void CHTMLReadDisplayView::polish() {
	TDEHTMLView::polish();
	connect( part(), TQT_SIGNAL(popupMenu(const TQString&, const TQPoint&)),
			this, TQT_SLOT(popupMenu(const TQString&, const TQPoint&)));
}

/** Reimplementatiob from TQScrollView. */
void CHTMLReadDisplayView::contentsDropEvent( TQDropEvent* e ) {
	if (CDragDropMgr::canDecode(e) && CDragDropMgr::dndType(e) == CDragDropMgr::Item::Bookmark) {
		CDragDropMgr::ItemList dndItems = CDragDropMgr::decode(e);
		CDragDropMgr::Item item = dndItems.first();
		e->acceptAction();

		m_display->connectionsProxy()->emitReferenceDropped(item.bookmarkKey());
		return;
	};

	//don't accept the action!
	e->acceptAction(false);
	e->ignore();
}

/** Reimplementation from TQScrollView. */
void CHTMLReadDisplayView::contentsDragEnterEvent( TQDragEnterEvent* e ) {
	if (CDragDropMgr::canDecode(e) && CDragDropMgr::dndType(e) == CDragDropMgr::Item::Bookmark) {
		e->acceptAction();
		return;
	}
	
	e->acceptAction(false);
	e->ignore();
}

/*!
\fn CHTMLReadDisplay::slotPageLoaded()
*/
void CHTMLReadDisplay::slotGoToAnchor() {
	if (!m_currentAnchorCache.isEmpty()) {
		if (!gotoAnchor( m_currentAnchorCache ) ) {
			tqDebug("anchor %s not present!", m_currentAnchorCache.latin1());
		}
	}
	m_currentAnchorCache = TQString();
}

void CHTMLReadDisplay::zoomIn() {
	setZoomFactor( (int)((float)zoomFactor()*1.1) );
}

void CHTMLReadDisplay::zoomOut() {
	setZoomFactor( (int)((float)zoomFactor()*(1.0/1.1)) );
}

void CHTMLReadDisplay::openFindTextDialog() {
#if TDE_VERSION >= 0x030300
	findText();
#else
	TQMessageBox::information(0, "Not Supported",
	"This copy of BibleTime was built against a version of KDE older\n"
	"than 3.3 (probably due to your distro), so this search feature\n"
	"does not work.\n\n"
	"This is a known issue.  If we do not have a fix for the next\n"
	"version of BibleTime, we will remove the option.");
#endif
}

#include "chtmlreaddisplay.moc"
