/*
    kopetehistory2dialog.cpp - Kopete History2 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 "history2dialog.h"

#include <QPointer>
#include <QDir>
#include <QTextStream>
#include <QClipboard>
#include <QTextDocument>
#include <QtAlgorithms>

#include <kdebug.h>
#include <krun.h>
#include <QMenu>
#include <kaction.h>
#include <kstandardaction.h>
#include <dom/dom_doc.h>
#include <dom/dom_element.h>
#include <dom/html_document.h>
#include <dom/html_element.h>
#include <khtml_part.h>
#include <khtmlview.h>

#include "kopetemetacontact.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopetecontact.h"
#include "kopetecontactlist.h"
#include "kopeteappearancesettings.h"

#include "history2logger.h"
#include "history2import.h"
#include "ui_history2viewer.h"

class KListViewDateItem : public QTreeWidgetItem
{
public:
    KListViewDateItem(QTreeWidget *parent, QDate date, Kopete::MetaContact *mc);
    QDate date() const
    {
        return mDate;
    }

    Kopete::MetaContact *metaContact() const
    {
        return mMetaContact;
    }

    virtual bool operator<(const QTreeWidgetItem &other) const;
private:
    QDate mDate;
    Kopete::MetaContact *mMetaContact;
};

KListViewDateItem::KListViewDateItem(QTreeWidget *parent, QDate date, Kopete::MetaContact *mc)
    : QTreeWidgetItem(parent)
    , mDate(date)
    , mMetaContact(mc)
{
    setText(0, mDate.toString(Qt::ISODate));
    setText(1, mMetaContact->displayName());
}

bool KListViewDateItem::operator<(const QTreeWidgetItem &other) const
{
    QTreeWidget *tw = treeWidget();
    int column = tw ? tw->sortColumn() : 0;
    if (column > 0) {
        return text(column) < other.text(column);
    }

    //compare dates - do NOT use ascending var here
    const KListViewDateItem *item = static_cast<const KListViewDateItem *>(&other);
    return mDate < item->date();
}

bool metaContactSort(Kopete::MetaContact *mc1, Kopete::MetaContact *mc2)
{
    return mc1->displayName() < mc2->displayName();
}

History2Dialog::History2Dialog(Kopete::MetaContact *mc, QWidget *parent)
    : KDialog(parent)
    , mSearching(false)
{
    setAttribute(Qt::WA_DeleteOnClose, true);
    setCaption(i18n("History for %1", mc->displayName()));
    setButtons(KDialog::Close);
    QString fontSize;
    QString htmlCode;
    QString fontStyle;

    kDebug(14310) << "called.";

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

    // Widgets initializations
    QWidget *w = new QWidget(this);
    mMainWidget = new Ui::History2Viewer();
    mMainWidget->setupUi(w);
    mMainWidget->searchLine->setFocus();
    mMainWidget->searchLine->setTrapReturnKey(true);
    mMainWidget->searchLine->setClearButtonEnabled(true);

    mMainWidget->contactComboBox->addItem(i18n("All"));
    mMetaContactList = Kopete::ContactList::self()->metaContacts();

    qSort(mMetaContactList.begin(), mMetaContactList.end(), metaContactSort);

    foreach (Kopete::MetaContact *metaContact, mMetaContactList) {
        mMainWidget->contactComboBox->addItem(metaContact->displayName());
    }

    if (mMetaContact) {
        mMainWidget->contactComboBox->setCurrentIndex(mMetaContactList.indexOf(mMetaContact)+1);
    }

    mMainWidget->dateSearchLine->setTreeWidget(mMainWidget->dateTreeWidget);
    mMainWidget->dateTreeWidget->sortItems(0, Qt::DescendingOrder); //newest-first

    setMainWidget(w);

    // Initializing HTML Part
    QVBoxLayout *l = new QVBoxLayout(mMainWidget->htmlFrame);
    mHtmlPart = new KHTMLPart(mMainWidget->htmlFrame);

    //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(Qt::ClickFocus);
    mHtmlView->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
    l->setMargin(0);
    l->addWidget(mHtmlView);

    QTextStream(&fontSize) << Kopete::AppearanceSettings::self()->chatFont().pointSize();
    fontStyle = "<style>.hf { font-size:" + fontSize + ".0pt; font-family:" + Kopete::AppearanceSettings::self()->chatFont().family() + "; color: "
                + Kopete::AppearanceSettings::self()->chatTextColor().name() + "; }</style>";

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

    connect(mHtmlPart->browserExtension(), SIGNAL(openUrlRequestDelayed(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)),
            this, SLOT(slotOpenURLRequest(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)));
    connect(mMainWidget->dateTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(dateSelected(QTreeWidgetItem*)));
    connect(mMainWidget->searchButton, SIGNAL(clicked()), this, SLOT(slotSearch()));
    connect(mMainWidget->searchLine, SIGNAL(returnPressed()), this, SLOT(slotSearch()));
    connect(mMainWidget->searchLine, SIGNAL(textChanged(QString)), this, SLOT(slotSearchTextChanged(QString)));
    connect(mMainWidget->contactComboBox, SIGNAL(activated(int)), this, SLOT(slotContactChanged(int)));
    connect(mMainWidget->messageFilterBox, SIGNAL(activated(int)), this, SLOT(slotFilterChanged(int)));
    connect(mMainWidget->importHistory2, SIGNAL(clicked()), this, SLOT(slotImportHistory2()));
    connect(mHtmlPart, SIGNAL(popupMenu(QString,QPoint)), this, SLOT(slotRightClick(QString,QPoint)));

    //initActions
    mCopyAct = KStandardAction::copy(this, SLOT(slotCopy()), mHtmlView);
    mHtmlView->addAction(mCopyAct);

    mCopyURLAct = new QAction(KIcon("edit-copy"), i18n("Copy Link Address"), mHtmlView);
    mHtmlView->addAction(mCopyURLAct);
    connect(mCopyURLAct, SIGNAL(triggered(bool)), this, SLOT(slotCopyURL()));

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

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

    // Load history2 dates in the listview
    init("");
}

History2Dialog::~History2Dialog()
{
    // end the search function, if it's still running
    mSearching = false;
    delete mMainWidget;
}

void History2Dialog::init(QString search)
{
    mMainWidget->dateTreeWidget->clear();
    if (mMetaContact) {
        QList<QDate> dayList = History2Logger::instance()->getDays(mMetaContact, search);
        for (int i = 0; i < dayList.count(); i++) {
            new KListViewDateItem(mMainWidget->dateTreeWidget, dayList[i], mMetaContact);
        }
    } else {
        QList<DMPair> dayList = History2Logger::instance()->getDays(search);
        for (int i = 0; i < dayList.count(); i++) {
            new KListViewDateItem(mMainWidget->dateTreeWidget, dayList[i].date(), dayList[i].metaContact());
        }
    }
}

void History2Dialog::dateSelected(QTreeWidgetItem *it)
{
    kDebug(14310);

    KListViewDateItem *item = static_cast<KListViewDateItem *>(it);

    if (!item) {
        return;
    }

    QDate chosenDate = item->date();

    setMessages(History2Logger::instance()->readMessages(chosenDate, item->metaContact()));
}

void History2Dialog::setMessages(QList<Kopete::Message> msgs)
{
    kDebug(14310);

    // Clear View
    DOM::HTMLElement htmlBody = mHtmlPart->htmlDocument().body();
    while (htmlBody.hasChildNodes()) {
        htmlBody.removeChild(htmlBody.childNodes().item(htmlBody.childNodes().length() - 1));
    }
    // ----

    QString dir = (QApplication::isRightToLeft() ? QString::fromLatin1("rtl")
                   : QString::fromLatin1("ltr"));

    QString accountLabel;
    QString date = msgs.isEmpty() ? "" : msgs.front().timestamp().date().toString();
    QString resultHTML = "<b><font color=\"red\">" + date + "</font></b><br/>";

    DOM::HTMLElement newNode = mHtmlPart->document().createElement(QString::fromLatin1("span"));
    newNode.setAttribute(QString::fromLatin1("dir"), dir);
    newNode.setInnerHTML(resultHTML);
    mHtmlPart->htmlDocument().body().appendChild(newNode);

    const QString searchForEscaped = mMainWidget->searchLine->text().toHtmlEscaped();

    // Populating HTML Part with messages
    foreach (const Kopete::Message &msg, msgs) {
        if (mMainWidget->messageFilterBox->currentIndex() == 0
            || (mMainWidget->messageFilterBox->currentIndex() == 1 && msg.direction() == Kopete::Message::Inbound)
            || (mMainWidget->messageFilterBox->currentIndex() == 2 && msg.direction() == Kopete::Message::Outbound)) {
            resultHTML.clear();

            if (accountLabel.isEmpty() || accountLabel != msg.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\">" + msg.from()->account()->accountLabel() + "</font></b><br/>";
            }
            accountLabel = msg.from()->account()->accountLabel();

            QString body = msg.parsedBody();

            // If there is a search, then we highlight the keywords
            if (!searchForEscaped.isEmpty() && body.contains(searchForEscaped, Qt::CaseInsensitive)) {
                body = highlight(body, searchForEscaped);
            }

            QString name;
            if (msg.from()->metaContact() && msg.from()->metaContact() != Kopete::ContactList::self()->myself()) {
                name = msg.from()->metaContact()->displayName();
            } else {
                name = msg.from()->displayName();
            }

            QString fontColor;
            if (msg.direction() == Kopete::Message::Outbound) {
                fontColor = Kopete::AppearanceSettings::self()->chatTextColor().dark().name();
            } else {
                fontColor = Kopete::AppearanceSettings::self()->chatTextColor().light(200).name();
            }

            QString messageTemplate = "<b>%1&nbsp;<font color=\"%2\">%3</font></b>&nbsp;%4";
            resultHTML += messageTemplate.arg(msg.timestamp().time().toString(),
                                              fontColor, name, body);

            newNode = mHtmlPart->document().createElement(QString::fromLatin1("span"));
            newNode.setAttribute(QString::fromLatin1("dir"), dir);
            newNode.setInnerHTML(resultHTML);

            mHtmlPart->htmlDocument().body().appendChild(newNode);
        }
    }
}

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

void History2Dialog::slotOpenURLRequest(const KUrl &url, const KParts::OpenUrlArguments &, const KParts::BrowserArguments &)
{
    kDebug(14310) << "url=" << url.url();
    new KRun(url, 0, false); // false = non-local files
}

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

void History2Dialog::treeWidgetHideElements(bool s)
{
    KListViewDateItem *item;
    for (int i = 0; i < mMainWidget->dateTreeWidget->topLevelItemCount(); i++) {
        item = static_cast<KListViewDateItem *>(mMainWidget->dateTreeWidget->topLevelItem(i));
        if (item) {
            item->setHidden(s);
        }
    }
}

/*
* 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 KListView (KListViewDateItems) 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 monthsSearched. The matches are placed in the matches QMap.
* Finally, the current date item is checked in the matches QMap, 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 History2Dialog::slotSearch()
{
    QString searchFor = mMainWidget->searchLine->text();

    init(searchFor);
    searchFinished();
}

void History2Dialog::searchFinished()
{
    mMainWidget->searchButton->setText(i18n("&Search"));
    mSearching = false;
    doneProgressBar();
}

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

void History2Dialog::initProgressBar(const QString &text, int nbSteps)
{
    mMainWidget->searchProgress->setMaximum(nbSteps);
    mMainWidget->searchProgress->setValue(0);
    mMainWidget->searchProgress->show();
    mMainWidget->statusLabel->setText(text);
}

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

void History2Dialog::slotRightClick(const QString &url, const QPoint &point)
{
    QMenu *chatWindowPopup = new QMenu();

    if (!url.isEmpty()) {
        mURL = url;
        chatWindowPopup->addAction(mCopyURLAct);
        chatWindowPopup->addSeparator();
    }
    mCopyAct->setEnabled(mHtmlPart->hasSelection());
    chatWindowPopup->addAction(mCopyAct);

    connect(chatWindowPopup, SIGNAL(aboutToHide()), chatWindowPopup, SLOT(deleteLater()));
    chatWindowPopup->popup(point);
}

void History2Dialog::slotCopy()
{
    QString qsSelection;
    qsSelection = mHtmlPart->selectedText();
    if (qsSelection.isEmpty()) {
        return;
    }

    disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
    QApplication::clipboard()->setText(qsSelection, QClipboard::Clipboard);
    QApplication::clipboard()->setText(qsSelection, QClipboard::Selection);
    connect(QApplication::clipboard(), SIGNAL(selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
}

void History2Dialog::slotCopyURL()
{
    disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
    QApplication::clipboard()->setText(mURL, QClipboard::Clipboard);
    QApplication::clipboard()->setText(mURL, QClipboard::Selection);
    connect(QApplication::clipboard(), SIGNAL(selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
}

void History2Dialog::slotImportHistory2(void)
{
    QPointer <History2Import> importer = new History2Import(this);
    importer->exec();
    delete importer;
}

QString History2Dialog::highlight(const QString &htmlText, const QString &highlight) const
{
    const int highlightLength = highlight.length();
    QString highlightedText;
    int eIndex = -1;
    int sIndex = 0;
    int midLen;

    for (;;) {
        sIndex = htmlText.indexOf("<", eIndex + 1);
        midLen = (sIndex == -1) ? -1 : sIndex - eIndex - 1;

        //Text to highlight
        QString body = htmlText.mid(eIndex + 1, midLen);
        int highlightIndex = 0;
        while ((highlightIndex = body.indexOf(highlight, highlightIndex, Qt::CaseInsensitive)) > -1) {
            QString after = QString("<span style=\"background-color:yellow\">%1</span>").arg(body.mid(highlightIndex, highlightLength));
            body.replace(highlightIndex, highlightLength, after);
            highlightIndex += after.length();
        }
        highlightedText += body;

        if (sIndex == -1) {
            break;
        }

        eIndex = htmlText.indexOf(">", sIndex);
        midLen = (eIndex == -1) ? -1 : eIndex - sIndex + 1;
        highlightedText += htmlText.mid(sIndex, midLen); // Tag element

        if (eIndex == -1) {
            break;
        }
    }

    return highlightedText;
}

QString History2Dialog::escapeXMLText(const QString &text) const
{
    if (text.isEmpty()) {
        return QString();
    }

    QDomDocument doc;
    QDomElement tmpElement = doc.createElement("tmpElement");
    QDomText tmpTextNode = doc.createTextNode(text);
    tmpElement.appendChild(tmpTextNode); //We have to attach the text to element otherwise save won't work correctly

    QString excapedText;
    QTextStream stream(&excapedText, QIODevice::WriteOnly);
    tmpTextNode.save(stream, 0);
    return excapedText;
}
