/* -*- c++ -*-
 *
 * friendpage.cpp
 *
 * Copyright (C) 2003 Petter Stokke <ummo@hellokitty.com>
 *
 * 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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kaction.h>
#include <kiconloader.h>
#include <kmessagebox.h>
#include <ktextbrowser.h>
#include <klineedit.h>
#include <kinputdialog.h>
#include <ktabwidget.h>

#include <qintdict.h>
#include <qpopupmenu.h>
#include <qclipboard.h>
#include <qtimer.h>
#include <qlayout.h>

#include <kdeversion.h>

#include "donkeyprotocol.h"
#include "clientinfo.h"
#include "network.h"

#include "infolist.h"
#include "prefs.h"
#include "kmldonkey.h"
#include "friendpage.h"
#include "friendpage.moc"

FriendPage::FriendPage(QWidget* parent)
    : QVBox(parent, "friendPage")
    , KMLDonkeyPage()
    , ClipboardHelper()
{
    friendList.setAutoDelete(true);
    shares.setAutoDelete(true);
    fileViews.setAutoDelete(true);
    chats.setAutoDelete(true);

    friendHSplitter = new QSplitter(this, "friendHSplitter");
    friendHSplitter->setOrientation(QSplitter::Horizontal);

    friendView = new InfoList(friendHSplitter, "friendView", true);
    friendView->addColumn( i18n( "Name" ) );
    friendView->addColumn( i18n( "Network" ) );
    friendView->addColumn( i18n( "Type" ) );
    friendView->addColumn( i18n( "Location" ) );
    friendView->addColumn( i18n( "State" ) );

    friendVSplitter = new QSplitter(friendHSplitter, "friendVSplitter");
    friendVSplitter->setOrientation(QSplitter::Vertical);

    fileTab = new KTabWidget(friendVSplitter, "fileTab");
    fileTab->setHoverCloseButton(true);
    fileTab->setTabReorderingEnabled(true);

    chatTab = new KTabWidget(friendVSplitter, "chatTab");
    chatTab->setHoverCloseButton(true);
    chatTab->setTabReorderingEnabled(true);

    QTimer* timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(mightUpdateShareList()));
    timer->start(100);

    connect(friendView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
            this, SLOT(contextFriend(KListView*, QListViewItem*, const QPoint&)));
    connect(friendView, SIGNAL(executed(QListViewItem*)), this, SLOT(openFriendFiles(QListViewItem*)));
    connect(friendView, SIGNAL(selectionChanged()), SLOT(pleaseUpdateActions()));
    connect(friendView, SIGNAL(gotFocus()), SLOT(pleaseUpdateActions()));

    connect(fileTab, SIGNAL(closeRequest(QWidget*)), this, SLOT(closeFileList(QWidget*)));
    connect(chatTab, SIGNAL(closeRequest(QWidget*)), this, SLOT(closeChat(QWidget*)));

    connect(KMLDonkey::App->donkey, SIGNAL(friendUpdated(int)), this, SLOT(friendUpdated(int)));
    connect(KMLDonkey::App->donkey, SIGNAL(friendRemoved(int)), this, SLOT(friendRemoved(int)));
    connect(KMLDonkey::App->donkey, SIGNAL(clientFileListing(int,const QString&,int)), this, SLOT(receiveShare(int,const QString&,int)));
    connect(KMLDonkey::App->donkey, SIGNAL(messageFromClient(int, const QString&)), SLOT(messageReceived(int, const QString&)));
}

void FriendPage::handleGenericAction(const QString& action)
{
    if (action == "copy_url") copyFileToClipboard(URL);
    else if (action == "copy_html") copyFileToClipboard(HTML);
    else if (action == "copy_hash") copyFileToClipboard(Hash);
    else if (action == "search_download") actionDownload();
    else if (action == "search_force_download") actionForceDownload();
    else if (action == "search_download_as") actionDownloadAs();
}

void FriendPage::deactivatePageActions()
{
    m_chatAction->setEnabled(false);
    enableActionList(friendActions, false);
    enableActionList(shareActions, false);
}

QStringList FriendPage::supportedGenericActions()
{
    QStringList actions;
    QPtrList<QListViewItem> fl = friendView->selectedItems();
    InfoList* fileView = static_cast<InfoList*>(fileTab->currentPage());

    if (friendView->hasFocus()) {
        enableActionList(shareActions, false);
        if (!fl.isEmpty()) enableActionList(friendActions, true);
        else enableActionList(friendActions, false);
        bool chatEnabled = false;
        QPtrListIterator<QListViewItem> it(fl);
        for (; it.current(); ++it) {
            ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(static_cast<ClientItem*>(*it)->fileNo());
            if (cl) {
                Network* nw = KMLDonkey::App->donkey->findNetworkNo(cl->clientNetwork());
                if (nw && nw->networkFlags() & Network::NetworkHasChat)
                    chatEnabled = true;
            }
        }
        m_chatAction->setEnabled(chatEnabled);
    }

    else if (fileView && fileView->hasFocus()) {
        enableActionList(friendActions, false);
        if (!currentFileListSelection().isEmpty()) {
            enableActionList(shareActions, true);
            actions.append("copy_url");
            actions.append("copy_html");
            actions.append("copy_hash");
            actions.append("search_download");
            actions.append("search_force_download");
            actions.append("search_download_as");
        }
        else enableActionList(shareActions, false);
    }

    else {
        enableActionList(friendActions, false);
        enableActionList(shareActions, false);
    }

    return actions;
}

void FriendPage::plugGenericActions(QObject* object, const char* slot)
{
    connect(this, SIGNAL(genericActionsChanged(KMLDonkeyPage*)), object, slot);
}

void FriendPage::pleaseUpdateActions()
{
    emit genericActionsChanged(static_cast<KMLDonkeyPage*>(this));
}

void FriendPage::setupActions(KActionCollection* actionCollection)
{
    KMLDonkey::App->addCoreAction( new KAction(i18n("&Search for Friend..."), "find", 0, this, SLOT(actionAddFriend()),
                                   actionCollection, "add_friend") );
    friendActions.append(new KAction(i18n("C&onnect to Friend"), "connecting", 0, this, SLOT(actionConnectFriend()),
                                     actionCollection, "connect_friend"));
    m_chatAction = new KAction(i18n("Open Friend &Chat"), "personal", 0, this, SLOT(actionOpenChat()),
                               actionCollection, "open_friend_chat");
    m_chatAction->setEnabled(false);
    friendActions.append(new KAction(i18n("&Remove Friend"), "edit_remove", 0, this, SLOT(actionRemoveFriend()),
                                     actionCollection, "remove_friend"));
    m_removeAllAction = new KAction(i18n("Remove All Friends"), "editshred", 0, this, SLOT(actionRemoveAllFriends()),
                                    actionCollection, "remove_all_friends");
    m_removeAllAction->setEnabled(false);

    (void)new KAction(i18n("Activate Friends Page"), 0, 0, this, SLOT(actionActivatePage()),
                      actionCollection, "activate_page_friends");

    deactivatePageActions();
}

void FriendPage::applyPreferences(KMLDonkeyPreferences*)
{
    if (KMLDonkey::App->listFont !=  friendView->font())
        friendView->setFont(KMLDonkey::App->listFont);

    QIntDictIterator<InfoList> it(fileViews);
    for (; it.current(); ++it)
        if (KMLDonkey::App->listFont !=  it.current()->font())
            it.current()->setFont(KMLDonkey::App->listFont);
}

void FriendPage::clear()
{
    closeAllFileLists();
    friendList.clear();
    chats.clear();
    shares.clear();
    friendView->clear();
    m_removeAllAction->setEnabled(false);
}

void FriendPage::restoreState(KConfig* conf)
{
    friendView->initialise();

    conf->setGroup("Splitters");
    friendHSplitter->setSizes(conf->readIntListEntry("FriendHSplitter"));
    friendVSplitter->setSizes(conf->readIntListEntry("FriendVSplitter"));

    applyPreferences();
}

void FriendPage::saveState(KConfig* conf)
{
    conf->setGroup("Splitters");
    conf->writeEntry("FriendHSplitter", friendHSplitter->sizes());
    conf->writeEntry("FriendVSplitter", friendVSplitter->sizes());

    friendView->saveLayout();

    QIntDictIterator<InfoList> it(fileViews);
    if (it.current())
        it.current()->saveLayout();
}

void FriendPage::closeAllFileLists()
{
    fileViews.clear();
    pleaseUpdateShareList.clear();
    pleaseUpdateActions();
}

void FriendPage::openFileList(int client)
{
    InfoList* fileView = fileViews.find(client);
    if (!fileView) {
        ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(client);
        fileView = new InfoList(fileTab, "fileView");
        fileView->setFont(KMLDonkey::App->listFont);
        fileView->addColumn( i18n( "Filename" ) );
        fileView->addColumn( i18n( "Network" ) );
        fileView->addColumn( i18n( "Size" ) );
        fileView->addColumn( i18n( "Format" ) );
        fileView->addColumn( i18n( "Comment" ) );
        fileView->addColumn( i18n( "Hash" ) );
        fileView->initialise();
        fileTab->addTab(fileView, KGlobal::iconLoader()->loadIconSet("personal", KIcon::Small), cl->clientName());
        fileViews.insert(client, fileView);
        constructFileList(client);
        connect(fileView, SIGNAL(contextMenu(KListView*,QListViewItem*,const QPoint&)),
                this, SLOT(contextFile(KListView*,QListViewItem*,const QPoint&)));
        connect(fileView, SIGNAL(selectionChanged()), SLOT(pleaseUpdateActions()));
        connect(fileView, SIGNAL(gotFocus()), SLOT(pleaseUpdateActions()));
        connect(fileView, SIGNAL(doubleClicked(QListViewItem*)),
                this, SLOT(fileDoubleClick(QListViewItem*)));
    }
    fileTab->showPage(fileView);
}

void FriendPage::closeFileList(QWidget* widget)
{
    InfoList* fileView = static_cast<InfoList*>(widget);
    QIntDictIterator<InfoList> it(fileViews);
    for (; it.current(); ++it)
        if (it.current() == fileView) {
            it.current()->saveLayout();
            fileViews.remove(it.currentKey());
            pleaseUpdateActions();
            return;
        }
}

void FriendPage::friendUpdated(int client)
{
    ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(client);
    if (!cl) return;

    ClientItem* it = friendList[client];
    if (it) {
        it->refresh();
        return;
    }
    it = new ClientItem(friendView, client);
    friendList.insert(client, it);
    if (! m_removeAllAction->isEnabled()) m_removeAllAction->setEnabled(true);
}

void FriendPage::friendRemoved(int client)
{
    friendList.remove(client);
    shares.remove(client);
    fileViews.remove(client);
    if (friendList.count() < 1) m_removeAllAction->setEnabled(false);
}

void FriendPage::contextFriend(KListView*, QListViewItem*, const QPoint& pt)
{
    QPopupMenu *pop = (QPopupMenu*)(KMLDonkey::App->factory())->container("friend_actions", KMLDonkey::App);
    if (!pop)
        KMLDonkey::App->showBadInstallDialog();
    else
        pop->popup(pt);
}

void FriendPage::contextFile(KListView*, QListViewItem*, const QPoint& pt)
{
    QPopupMenu *pop = (QPopupMenu*)(KMLDonkey::App->factory())->container("friend_share_actions", KMLDonkey::App);
    if (!pop)
        KMLDonkey::App->showBadInstallDialog();
    else
        pop->popup(pt);
}

void FriendPage::actionAddFriend()
{
    bool ok;
    QString name = KInputDialog::getText( i18n("Search for Friend"),
                                          i18n("Enter the name of the friend you are searching for:"),
                                          QString::null, &ok, this );
    if (ok)
        KMLDonkey::App->donkey->searchForFriend(name);
}

void FriendPage::actionRemoveFriend()
{
    QPtrList<QListViewItem> list = friendView->selectedItems();
    ClientItem* it;
    for (it = (ClientItem*)list.first(); it; it = (ClientItem*)list.next())
        KMLDonkey::App->donkey->removeFriend(it->fileNo());
}

void FriendPage::actionRemoveAllFriends()
{
    if (KMessageBox::warningYesNo(this, i18n("Should all friends be removed?"),
                                  i18n("Remove all Friends")) != KMessageBox::Yes) return;
    KMLDonkey::App->donkey->removeAllFriends();
}

void FriendPage::actionConnectFriend()
{
    QPtrList<QListViewItem> list = friendView->selectedItems();
    ClientItem* it;
    for (it = (ClientItem*)list.first(); it; it = (ClientItem*)list.next())
        KMLDonkey::App->donkey->connectFriend(it->fileNo());
}

void FriendPage::actionOpenChat()
{
    QPtrList<QListViewItem> list = friendView->selectedItems();
    ClientItem* it;
    for (it = (ClientItem*)list.first(); it; it = (ClientItem*)list.next()) {
        ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(it->fileNo());
        if (cl) {
            ChatWidget* chat = chats.find(cl->clientNo());
            if (!chat) {
                chat = new ChatWidget(cl->clientNo(), chatTab);
                chatTab->addTab(chat, KGlobal::iconLoader()->loadIconSet("fileclose", KIcon::Small), cl->clientName());
                chats.insert(cl->clientNo(), chat);
                // Workaround for a strange rendering bug
                chatTab->hide();
                chatTab->show();
            } else {
                chatTab->showPage((QWidget*)chat);
            }
        }
    }
}

void FriendPage::receiveShare(int clno, const QString& dir, int result)
{
    FriendShares* sh = shares[clno];
    if (!sh) {
        shares.insert(clno, new FriendShares());
        sh = shares[clno];
        QPtrList<QListViewItem> list = friendView->selectedItems();
        ClientItem* it;
        for (it = (ClientItem*)list.first(); it; it = (ClientItem*)list.next())
            if (it->fileNo() == clno) it->setFilesListed(true);
    }

    const ResultInfo* si = KMLDonkey::App->donkey->findClientFile(result);
    if (!si) { kdDebug() << "No result #" << result << " available!" << endl; return; }

    QString dirname = dir;
    if (dirname.isEmpty()) dirname = ".";
    QValueList<int>& files = sh->dirs[dirname];
    if (!files.contains(si->resultNo()))
        files.append(si->resultNo());
    if (fileViews.find(clno))
        pleaseUpdateShareList.insert(clno, true);
}

void FriendPage::constructFileList(int clno)
{
    InfoList* fileView = fileViews.find(clno);
    if (!fileView) return;

    fileView->clear();
    FriendShares* sh = shares[clno];
    if (!sh) {
        DonkeyMessage out(DonkeyProtocol::GetClient_files);
        out.writeInt32(clno);
        KMLDonkey::App->donkey->sendMessage(out);
        return;
    }

    QMap<QString,QValueList<int> >::iterator it;
    for (it = sh->dirs.begin(); it != sh->dirs.end(); ++it) {
        QValueList<int>::iterator lit;
        for (lit = it.data().begin(); lit != it.data().end(); ++lit) {
            new ClientFile(fileView, *lit);
        }
    }
}

void FriendPage::openFriendFiles(QListViewItem* item)
{
    ClientItem* cl = (ClientItem*)item;
    openFileList(cl->fileNo());
}

void FriendPage::mightUpdateShareList()
{
    QMap<int,bool>::Iterator it;
    for (it = pleaseUpdateShareList.begin(); it != pleaseUpdateShareList.end(); ++it)
        if (it.data())
            constructFileList(it.key());
    pleaseUpdateShareList.clear();
}

QPtrList<QListViewItem> FriendPage::currentFileListSelection()
{
    QPtrList<QListViewItem> list;
    InfoList* fileView = static_cast<InfoList*>(fileTab->currentPage());
    if (!fileView) return list;
    list = fileView->selectedItems();
    return list;
}

void FriendPage::fileDoubleClick(QListViewItem*)
{
    downloadSelected(false, true);
}

void FriendPage::downloadSelected(bool force, bool ask)
{
    QPtrList<QListViewItem> list = currentFileListSelection();
    ClientFile* it;
    for (it = (ClientFile*)list.first(); it; it = (ClientFile*)list.next()) {
        const ResultInfo* fi = KMLDonkey::App->donkey->findClientFile(it->fileNo());
        if (!fi) continue;
        QStringList names;
        if (ask || !fi->resultName().length()) {
            bool ok;
            QString filename = KInputDialog::getText( i18n("Download As"),
                                                      i18n("Choose a filename for the new download:"),
                                                      fi->resultName(), &ok, this );
            if (!ok) continue;
            names.append(filename);
            if (filename != fi->resultName())
                names.append(fi->resultName());
        } else {
            names.append(fi->resultName());
        }
        KMLDonkey::App->donkey->startDownload(names, fi->resultNo(), force);
    }
}

void FriendPage::actionDownload()
{
    downloadSelected(false, false);
}

void FriendPage::actionForceDownload()
{
    downloadSelected(true, false);
}

void FriendPage::actionDownloadAs()
{
    downloadSelected(false, true);
}


void FriendPage::copyFileToClipboard(ClipFormat format)
{
    QStringList l;
    QPtrList<QListViewItem> list = currentFileListSelection();
    ClientFile* it;
    for (it = (ClientFile*)list.first(); it; it = (ClientFile*)list.next()) {
        const ResultInfo* fi = KMLDonkey::App->donkey->findClientFile(it->fileNo());
        if (!fi) continue;
        l.append(fi->resultName());
        l.append(fi->resultUid("ed2k"));
        l.append(QString::number((long int)fi->resultSize()));
    }
    copyToClipboard(l, format);
}

void FriendPage::actionActivatePage()
{
    KMLDonkey::App->activatePage(this);
}

void FriendPage::messageReceived(int client, const QString& message)
{
    if (!chats.find(client)) {
        ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(client);
        if (!cl) return;
        ChatWidget* chat = new ChatWidget(client, chatTab);
        chatTab->addTab(chat, KGlobal::iconLoader()->loadIconSet("fileclose", KIcon::Small), cl->clientName());
        chats.insert(client, chat);
        // Workaround for a strange rendering bug
        chatTab->hide();
        chatTab->show();
        chat->messageFromClient(client, message);
    }
}

void FriendPage::closeChat(QWidget* widget)
{
    ChatWidget* chat = static_cast<ChatWidget*>(widget);
    QIntDictIterator<ChatWidget> it(chats);
    for (; it.current(); ++it)
        if (it.current() == chat) {
            chats.remove(it.currentKey());
            // pleaseUpdateActions();
            return;
        }
}



ChatWidget::ChatWidget(int client, QWidget* parent)
    : QWidget(parent)
{
    clno = client;

    QVBoxLayout* layout = new QVBoxLayout(this);

    output = new KTextBrowser(this);
    output->setFocusPolicy(QTextBrowser::ClickFocus);
    output->setTextFormat(QTextBrowser::RichText);
    output->setLinkUnderline(true);
    layout->addWidget(output);

    input = new KLineEdit(this);
    layout->addWidget(input);

    connect(input, SIGNAL(returnPressed(const QString&)), input->completionObject(), SLOT(addItem(const QString&)));

    connect(input, SIGNAL(returnPressed(const QString&)), SLOT(messageEntered(const QString&)));
    connect(KMLDonkey::App->donkey, SIGNAL(messageFromClient(int, const QString&)), SLOT(messageFromClient(int, const QString&)));
}

void ChatWidget::messageFromClient(int client, const QString& message)
{
    if (client != clno) return;
    const ClientInfo* cl = KMLDonkey::App->donkey->findClientNo(client);
    output->append(i18n("chat message: <Nick> Message", "<b>&lt;%1&gt;</b> %2\n")
                   .arg(cl ? cl->clientName() : QString::number(client)).arg( QStyleSheet::escape(message) ));
}

void ChatWidget::messageEntered(const QString& message)
{
    QString nick = KMLDonkey::App->donkey->getOption("client_name");
    if (!nick) nick = "MLDonkey";
    output->append(i18n("chat message: <Nick> Message", "<b>&lt;%1&gt;</b> %2\n").arg(nick).arg( QStyleSheet::escape(message) ));
    KMLDonkey::App->donkey->sendPrivateMessage(clno, message);
    input->clear();
}
