/*
    kopetehistorydialog.cpp - Kopete History Dialog

    Copyright (c) 2002 by  Richard Stellingwerff <remenic@linuxfromscratch.org>
    Copyright (c) 2004 by  Stefan Gehn <metz AT gehn.net>

    Kopete    (c) 2002-2004 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * 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.                                   *
    *                                                                       *
    *************************************************************************
*/

#include "historydialog.h"
#include "historylogger.h"
#include "historyviewer.h"
#include "kopetemetacontact.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopetecontactlist.h"
#include "kopeteprefs.h"

#include <dom/dom_doc.h>
#include <dom/dom_element.h>
#include <dom/html_document.h>
#include <dom/html_element.h>
#include <tdehtml_part.h>
#include <tdehtmlview.h>

#include <tqpushbutton.h>
#include <tqlineedit.h>
#include <tqcheckbox.h>
#include <tqlayout.h>
#include <tqdir.h>
#include <tqdatetime.h>
#include <tqheader.h>
#include <tqlabel.h>
#include <tqclipboard.h>

#include <tdeapplication.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <tdelistview.h>
#include <tdelistviewsearchline.h>
#include <kprogress.h>
#include <kiconloader.h>
#include <kcombobox.h>
#include <tdepopupmenu.h>
#include <kstdaction.h>
#include <tdeaction.h>

class TDEListViewDateItem : public TDEListViewItem
{
public:
    TDEListViewDateItem(TDEListView* parent, TQDate date, Kopete::MetaContact *mc);
	TQDate date() { return mDate; }
	Kopete::MetaContact *metaContact() { return mMetaContact; }

public:
	int compare(TQListViewItem *i, int col, bool ascending) const;
private:
    TQDate mDate;
	Kopete::MetaContact *mMetaContact;
};



TDEListViewDateItem::TDEListViewDateItem(TDEListView* parent, TQDate date, Kopete::MetaContact *mc)
		: TDEListViewItem(parent, date.toString(TQt::ISODate), mc->displayName())
{
	mDate = date;
	mMetaContact = mc;
}

int TDEListViewDateItem::compare(TQListViewItem *i, int col, bool ascending) const
{
	if (col) 
		return TQListViewItem::compare(i, col, ascending);

	//compare dates - do NOT use ascending var here
	TDEListViewDateItem* item = static_cast<TDEListViewDateItem*>(i);
	if ( mDate < item->date() )
		return -1;
	return ( mDate > item->date() );
}


HistoryDialog::HistoryDialog(Kopete::MetaContact *mc, TQWidget* parent,
	const char* name) : KDialogBase(parent, name, false,
		i18n("History for %1").arg(mc->displayName()), 0), mSearching(false)
{
	TQString fontSize;
	TQString htmlCode;
	TQString fontStyle;

	kdDebug(14310) << k_funcinfo << "called." << endl;
	setWFlags(TQt::WDestructiveClose);	// send TQ_SIGNAL(closing()) on quit

	// FIXME: Allow to show this dialog for only one contact
	mMetaContact = mc;

	

	// Widgets initializations
	mMainWidget = new HistoryViewer(this, "HistoryDialog::mMainWidget");
	mMainWidget->searchLine->setFocus(); 
	mMainWidget->searchLine->setTrapReturnKey (true);
	mMainWidget->searchLine->setTrapReturnKey(true);
	mMainWidget->searchErase->setPixmap(BarIcon("locationbar_erase"));

	mMainWidget->contactComboBox->insertItem(i18n("All"));
	mMetaContactList = Kopete::ContactList::self()->metaContacts();
	TQPtrListIterator<Kopete::MetaContact> it(mMetaContactList);
	for(; it.current(); ++it)
	{
		mMainWidget->contactComboBox->insertItem((*it)->displayName());
	}

	if (mMetaContact)
		mMainWidget->contactComboBox->setCurrentItem(mMetaContactList.find(mMetaContact)+1);

	mMainWidget->dateSearchLine->setListView(mMainWidget->dateListView);
	mMainWidget->dateListView->setSorting(0, 0); //newest-first

	setMainWidget(mMainWidget);

	// Initializing HTML Part
	mMainWidget->htmlFrame->setFrameStyle(TQFrame::WinPanel | TQFrame::Sunken);
	TQVBoxLayout *l = new TQVBoxLayout(mMainWidget->htmlFrame);
	mHtmlPart = new TDEHTMLPart(mMainWidget->htmlFrame, "htmlHistoryView");

	//Security settings, we don't need this stuff
	mHtmlPart->setJScriptEnabled(false);
	mHtmlPart->setJavaEnabled(false);
	mHtmlPart->setPluginsEnabled(false);
	mHtmlPart->setMetaRefreshEnabled(false);
        mHtmlPart->setOnlyLocalReferences(true);

	mHtmlView = mHtmlPart->view();
	mHtmlView->setMarginWidth(4);
	mHtmlView->setMarginHeight(4);
	mHtmlView->setFocusPolicy(TQWidget::NoFocus);
	mHtmlView->setSizePolicy(
	TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding));
	l->addWidget(mHtmlView);

	TQTextOStream( &fontSize ) << KopetePrefs::prefs()->fontFace().pointSize();
	fontStyle = "<style>.hf { font-size:" + fontSize + ".0pt; font-family:" + KopetePrefs::prefs()->fontFace().family() + "; color: " + KopetePrefs::prefs()->textColor().name() + "; }</style>";

	mHtmlPart->begin();
	htmlCode = "<html><head>" + fontStyle + "</head><body class=\"hf\"></body></html>";
	mHtmlPart->write( TQString::fromLatin1( htmlCode.latin1() ) );
	mHtmlPart->end();

	
	connect(mHtmlPart->browserExtension(), TQ_SIGNAL(openURLRequestDelayed(const KURL &, const KParts::URLArgs &)),
		this, TQ_SLOT(slotOpenURLRequest(const KURL &, const KParts::URLArgs &)));
	connect(mMainWidget->dateListView, TQ_SIGNAL(clicked(TQListViewItem*)), this, TQ_SLOT(dateSelected(TQListViewItem*)));
	connect(mMainWidget->searchButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotSearch()));
	connect(mMainWidget->searchLine, TQ_SIGNAL(returnPressed()), this, TQ_SLOT(slotSearch()));
	connect(mMainWidget->searchLine, TQ_SIGNAL(textChanged(const TQString&)), this, TQ_SLOT(slotSearchTextChanged(const TQString&)));
	connect(mMainWidget->searchErase, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotSearchErase()));
	connect(mMainWidget->contactComboBox, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotContactChanged(int)));
	connect(mMainWidget->messageFilterBox, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotFilterChanged(int )));
	connect(mHtmlPart, TQ_SIGNAL(popupMenu(const TQString &, const TQPoint &)), this, TQ_SLOT(slotRightClick(const TQString &, const TQPoint &)));

	//initActions
	TDEActionCollection* ac = new TDEActionCollection(this);
	mCopyAct = KStdAction::copy( this, TQ_SLOT(slotCopy()), ac );
	mCopyURLAct = new TDEAction( i18n( "Copy Link Address" ), TQString::fromLatin1( "edit-copy" ), 0, this, TQ_SLOT( slotCopyURL() ), ac );

	resize(650, 700);
	centerOnScreen(this);

	// show the dialog before people get impatient
	show();

	// Load history dates in the listview
	init();
}

HistoryDialog::~HistoryDialog()
{
	mSearching = false;
}

void HistoryDialog::init()
{
	if(mMetaContact)
	{
		HistoryLogger logger(mMetaContact, this);
		init(mMetaContact);
	}
	else
	{
		TQPtrListIterator<Kopete::MetaContact> it(mMetaContactList);
		for(; it.current(); ++it)
		{
			HistoryLogger logger(*it, this);
			init(*it);
		}

	}

	initProgressBar(i18n("Loading..."),mInit.dateMCList.count());
	TQTimer::singleShot(0,this,TQ_SLOT(slotLoadDays()));
}

void HistoryDialog::slotLoadDays()
{
		if(mInit.dateMCList.isEmpty())
		{
				if (!mMainWidget->searchLine->text().isEmpty())
					TQTimer::singleShot(0, this, TQ_SLOT(slotSearch()));
				doneProgressBar();
				return;
		}
		
		DMPair pair(mInit.dateMCList.first());
		mInit.dateMCList.pop_front();
		HistoryLogger logger(pair.metaContact(), this);
		TQValueList<int> dayList = logger.getDaysForMonth(pair.date());
		for (unsigned int i=0; i<dayList.count(); i++)
		{
				TQDate c2Date(pair.date().year(),pair.date().month(),dayList[i]);
				if (mInit.dateMCList.find(pair) == mInit.dateMCList.end())
						new TDEListViewDateItem(mMainWidget->dateListView, c2Date, pair.metaContact());
		}
		mMainWidget->searchProgress->advance(1);
		TQTimer::singleShot(0,this,TQ_SLOT(slotLoadDays()));


}

void HistoryDialog::init(Kopete::MetaContact *mc)
{
	TQPtrList<Kopete::Contact> contacts=mc->contacts();
	TQPtrListIterator<Kopete::Contact> it( contacts );

	for( ; it.current(); ++it )
	{
		init(*it);
	}
}

void HistoryDialog::init(Kopete::Contact *c)
{
	// Get year and month list
	TQRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" );
	const TQString contact_in_filename=c->contactId().replace( TQRegExp( TQString::fromLatin1( "[./~?*]" ) ), TQString::fromLatin1( "-" ) );
	TQFileInfo *fi;
	

	// BEGIN check if there are Kopete 0.7.x
	TQDir d1(locateLocal("data",TQString("kopete/logs/")+
			c->protocol()->pluginId().replace( TQRegExp(TQString::fromLatin1("[./~?*]")),TQString::fromLatin1("-"))
					   ));
	d1.setFilter( TQDir::Files | TQDir::NoSymLinks );
	d1.setSorting( TQDir::Name );

	const TQFileInfoList *list1 = d1.entryInfoList();
	if ( list1 != 0 )
	{
		TQFileInfoListIterator it1( *list1 );
		while ( (fi = it1.current()) != 0 )
		{
			if(fi->fileName().contains(contact_in_filename))
			{
				rx.search(fi->fileName());
				
				TQDate cDate = TQDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1);

				DMPair pair(cDate, c->metaContact());
				mInit.dateMCList.append(pair);
	
			}
			++it1;
		}
	}
	// END of kopete 0.7.x check

	TQString logDir = locateLocal("data",TQString("kopete/logs/")+
			c->protocol()->pluginId().replace( TQRegExp(TQString::fromLatin1("[./~?*]")),TQString::fromLatin1("-")) +
					TQString::fromLatin1( "/" ) +
					c->account()->accountId().replace( TQRegExp( TQString::fromLatin1( "[./~?*]" ) ), TQString::fromLatin1( "-" ) )
								);
	TQDir d(logDir);
	d.setFilter( TQDir::Files | TQDir::NoSymLinks );
	d.setSorting( TQDir::Name );
	const TQFileInfoList *list = d.entryInfoList();
	if ( list != 0 )
	{
		TQFileInfoListIterator it( *list );
		while ( (fi = it.current()) != 0 )
		{
			if(fi->fileName().contains(contact_in_filename))
			{
				
				rx.search(fi->fileName());
				
				// We search for an item in the list view with the same year. If then we add the month
				TQDate cDate = TQDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1);

				DMPair pair(cDate, c->metaContact());
				mInit.dateMCList.append(pair);
			}
			++it;
		}
	}
}

void HistoryDialog::dateSelected(TQListViewItem* it)
{
	TDEListViewDateItem *item = static_cast<TDEListViewDateItem*>(it);

	if (!item) return;

	TQDate chosenDate = item->date();

	HistoryLogger logger(item->metaContact(), this);
	TQValueList<Kopete::Message> msgs=logger.readMessages(chosenDate);

	setMessages(msgs);
}

void HistoryDialog::setMessages(TQValueList<Kopete::Message> msgs)
{
	// Clear View
	DOM::HTMLElement htmlBody = mHtmlPart->htmlDocument().body();
	while(htmlBody.hasChildNodes())
		htmlBody.removeChild(htmlBody.childNodes().item(htmlBody.childNodes().length() - 1));
	// ----

	TQString dir = (TQApplication::reverseLayout() ? TQString::fromLatin1("rtl") :
		TQString::fromLatin1("ltr"));

	TQValueList<Kopete::Message>::iterator it = msgs.begin();


	TQString accountLabel;
	TQString resultHTML = "<b><font color=\"red\">" + (*it).timestamp().date().toString() + "</font></b><br/>";
	DOM::HTMLElement newNode = mHtmlPart->document().createElement(TQString::fromLatin1("span"));
	newNode.setAttribute(TQString::fromLatin1("dir"), dir);
	newNode.setInnerHTML(resultHTML);
	mHtmlPart->htmlDocument().body().appendChild(newNode);

	// Populating HTML Part with messages
	for ( it = msgs.begin(); it != msgs.end(); ++it )
	{
		if ( mMainWidget->messageFilterBox->currentItem() == 0
			|| ( mMainWidget->messageFilterBox->currentItem() == 1 && (*it).direction() == Kopete::Message::Inbound )
			|| ( mMainWidget->messageFilterBox->currentItem() == 2 && (*it).direction() == Kopete::Message::Outbound ) )
		{
			resultHTML = "";
	
			if (accountLabel.isEmpty() || accountLabel != (*it).from()->account()->accountLabel())
			// If the message's account is new, just specify it to the user
			{
				if (!accountLabel.isEmpty())
					resultHTML += "<br/><br/><br/>";
				resultHTML += "<b><font color=\"blue\">" + (*it).from()->account()->accountLabel() + "</font></b><br/>";
			}
			accountLabel = (*it).from()->account()->accountLabel();
	
			TQString body = (*it).parsedBody();
	
			if (!mMainWidget->searchLine->text().isEmpty())
			// If there is a search, then we hightlight the keywords
			{
				body = body.replace(mMainWidget->searchLine->text(), "<span style=\"background-color:yellow\">" + mMainWidget->searchLine->text() + "</span>", false);
			}
		
			resultHTML += "(<b>" + (*it).timestamp().time().toString() + "</b>) "
					+ ((*it).direction() == Kopete::Message::Outbound ?
									"<font color=\"" + KopetePrefs::prefs()->textColor().dark().name() + "\"><b>&gt;</b></font> "
									: "<font color=\"" + KopetePrefs::prefs()->textColor().light(200).name() + "\"><b>&lt;</b></font> ")
					+ body + "<br/>";
	
			newNode = mHtmlPart->document().createElement(TQString::fromLatin1("span"));
			newNode.setAttribute(TQString::fromLatin1("dir"), dir);
			newNode.setInnerHTML(resultHTML);
	
			mHtmlPart->htmlDocument().body().appendChild(newNode);
		}
	}
}

void HistoryDialog::slotFilterChanged(int /* index */)
{
	dateSelected(mMainWidget->dateListView->currentItem());
}

void HistoryDialog::slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/)
{
	kdDebug(14310) << k_funcinfo << "url=" << url.url() << endl;
	new KRun(url, 0, false); // false = non-local files
}

// Disable search button if there is no search text
void HistoryDialog::slotSearchTextChanged(const TQString& searchText)
{
	if (searchText.isEmpty())
	{
		mMainWidget->searchButton->setEnabled(false);
		slotSearchErase();
	}
	else
	{
		mMainWidget->searchButton->setEnabled(true);
	}
}

void HistoryDialog::listViewShowElements(bool s)
{
	TDEListViewDateItem* item = static_cast<TDEListViewDateItem*>(mMainWidget->dateListView->firstChild());
	while (item != 0)
	{
			item->setVisible(s);
			item = static_cast<TDEListViewDateItem*>(item->nextSibling());
	}
}

// Erase the search line, show all date/metacontacts items in the list (accordint to the
// metacontact selected in the combobox)
void HistoryDialog::slotSearchErase()
{
	mMainWidget->searchLine->clear();
	listViewShowElements(true);
}

/*
* How does the search work
* ------------------------
* We do the search respecting the current metacontact filter item. To do this, we iterate over the
* elements in the TDEListView (TDEListViewDateItems) and, for each one, we iterate over its subcontacts,
* manually searching the log files of each one. To avoid searching files twice, the months that have
* been searched already are stored in searchedMonths. The matches are placed in the matches TQMap.
* Finally, the current date item is checked in the matches TQMap, and if it is present, it is shown.
*
* Keyword highlighting is done in setMessages() : if the search field isn't empty, we highlight the
* search keyword.
*
* The search is _not_ case sensitive
*/
void HistoryDialog::slotSearch()
{
	if (mMainWidget->dateListView->childCount() == 0) return;

	TQRegExp rx("^ <msg.*time=\"(\\d+) \\d+:\\d+:\\d+\" >([^<]*)<");
	TQMap<TQDate, TQValueList<Kopete::MetaContact*> > monthsSearched;
	TQMap<TQDate, TQValueList<Kopete::MetaContact*> > matches;

	// cancel button pressed
	if (mSearching)
	{
		listViewShowElements(true);
		goto searchFinished;
	}

	listViewShowElements(false);

	initProgressBar(i18n("Searching..."), mMainWidget->dateListView->childCount());
	mMainWidget->searchButton->setText(i18n("&Cancel"));
	mSearching = true;

	// iterate over items in the date list widget
	for(TDEListViewDateItem *curItem = static_cast<TDEListViewDateItem*>(mMainWidget->dateListView->firstChild());
		curItem != 0;
		curItem = static_cast<TDEListViewDateItem *>(curItem->nextSibling())
	)
	{
		tqApp->processEvents();
		if (!mSearching) return;

		TQDate month(curItem->date().year(),curItem->date().month(),1);
		// if we haven't searched the relevant history logs, search them now
		if (!monthsSearched[month].contains(curItem->metaContact()))
		{
			monthsSearched[month].push_back(curItem->metaContact());
			TQPtrList<Kopete::Contact> contacts = curItem->metaContact()->contacts();
			for(TQPtrListIterator<Kopete::Contact> it( contacts ); it.current(); ++it)
			{
				// get filename and open file
				TQString filename(HistoryLogger::getFileName(*it, curItem->date()));
				if (!TQFile::exists(filename)) continue;
				TQFile file(filename);
				file.open(IO_ReadOnly);
				if (!file.isOpen())
				{
					kdWarning(14310) << k_funcinfo << "Error opening " <<
							file.name() << ": " << TQString(file.errorString()).ascii() << endl;
					continue;
				}

				TQTextStream stream(&file);
				TQString textLine;
				while(!stream.atEnd())
				{
					textLine = stream.readLine();
					if (textLine.contains(mMainWidget->searchLine->text(), false))
					{
						if(rx.search(textLine) != -1)
						{
							// only match message body
							if (rx.cap(2).contains(mMainWidget->searchLine->text()))
								matches[TQDate(curItem->date().year(),curItem->date().month(),rx.cap(1).toInt())].push_back(curItem->metaContact());
						}
						// this will happen when multiline messages are searched, properly
						// parsing the files would fix this
						else { }
					}
					tqApp->processEvents();
					if (!mSearching) return;
				}
				file.close();
			}
		}

		// relevant logfiles have been searched now, check if current date matches
		if (matches[curItem->date()].contains(curItem->metaContact()))
			curItem->setVisible(true);

		// Next date item
		mMainWidget->searchProgress->advance(1);
	}

searchFinished:
	mMainWidget->searchButton->setText(i18n("Se&arch"));
	mSearching = false;
	doneProgressBar();
}



// When a contact is selected in the combobox. Item 0 is All contacts.
void HistoryDialog::slotContactChanged(int index)
{
	mMainWidget->dateListView->clear();
	if (index == 0)
	{
        setCaption(i18n("History for All Contacts"));
        mMetaContact = 0;
		init();
	}
	else
	{
		mMetaContact = mMetaContactList.at(index-1);
        setCaption(i18n("History for %1").arg(mMetaContact->displayName()));
		init();
	}
}

void HistoryDialog::initProgressBar(const TQString& text, int nbSteps)
{
		mMainWidget->searchProgress->setTotalSteps(nbSteps);
		mMainWidget->searchProgress->setProgress(0);
		mMainWidget->searchProgress->show();
		mMainWidget->statusLabel->setText(text);
}

void HistoryDialog::doneProgressBar()
{
		mMainWidget->searchProgress->hide();
		mMainWidget->statusLabel->setText(i18n("Ready"));
}

void HistoryDialog::slotRightClick(const TQString &url, const TQPoint &point)
{
	TDEPopupMenu *chatWindowPopup = 0L;
	chatWindowPopup = new TDEPopupMenu();
	
	if ( !url.isEmpty() )
	{
		mURL = url;
		mCopyURLAct->plug( chatWindowPopup );
		chatWindowPopup->insertSeparator();
	}
	mCopyAct->setEnabled( mHtmlPart->hasSelection() );
	mCopyAct->plug( chatWindowPopup );
	
	connect( chatWindowPopup, TQ_SIGNAL( aboutToHide() ), chatWindowPopup, TQ_SLOT( deleteLater() ) );
	chatWindowPopup->popup(point);
}

void HistoryDialog::slotCopy()
{
	TQString qsSelection;
	qsSelection = mHtmlPart->selectedText();
	if ( qsSelection.isEmpty() ) return;
	
	disconnect( kapp->clipboard(), TQ_SIGNAL( selectionChanged()), mHtmlPart, TQ_SLOT(slotClearSelection()));
	TQApplication::clipboard()->setText(qsSelection, TQClipboard::Clipboard);
	TQApplication::clipboard()->setText(qsSelection, TQClipboard::Selection);
	connect( kapp->clipboard(), TQ_SIGNAL( selectionChanged()), mHtmlPart, TQ_SLOT(slotClearSelection()));
}

void HistoryDialog::slotCopyURL()
{
	disconnect( kapp->clipboard(), TQ_SIGNAL( selectionChanged()), mHtmlPart, TQ_SLOT(slotClearSelection()));
	TQApplication::clipboard()->setText( mURL, TQClipboard::Clipboard);
	TQApplication::clipboard()->setText( mURL, TQClipboard::Selection);
	connect( kapp->clipboard(), TQ_SIGNAL( selectionChanged()), mHtmlPart, TQ_SLOT(slotClearSelection()));
}

#include "historydialog.moc"
