/* -*- c++ -*-
 *
 * donkeyprotocol.cpp
 *
 * Copyright (C) 2003 Petter Stokke <ummo@hellokitty.com>
 * Copyright (C) 2003 Sebastian Sauer <mail@dipe.org>
 * Copyright (C) 2006 Christian Muehlhaeuser <chris@chris.de>
 *
 * 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 "donkeyprotocol.h"

#include "donkeysocket.h"
#include "fileinfo.h"
#include "serverinfo.h"
#include "network.h"
#include "clientinfo.h"
#include "shareinfo.h"
#include "searchinfo.h"
#include "hostmanager.h"
#include "hostiface.h"
#include "donkeyhost.h"
#include "searchquery.h"

#include <qregexp.h>
#include <kdebug.h>

DonkeyProtocol::DonkeyProtocol(bool poll, QObject *parent)
    : ProtocolInterface("mldonkey", parent)
{
    uname = "admin";
    passwd = "";
    donkeyError = NoError;
    connectedservers = 0;
    proto = MIN_PROTOCOL_VERSION;
    wantpoll = poll;
    download.setAutoDelete(true);
    downloaded.setAutoDelete(true);
    servers.setAutoDelete(true);
    networks.setAutoDelete(true);
    clients.setAutoDelete(true);
    shares.setAutoDelete(true);
    rooms.setAutoDelete(true);
    searches.setAutoDelete(true);
    unmappedResults.setAutoDelete(true);
    connect(&sock, SIGNAL(readyMessage()), this, SLOT(processMessage()));
    connect(&sock, SIGNAL(connectionClosed()), this, SLOT(socketDisconnected()));
    connect(&sock, SIGNAL(error(int)), this, SLOT(socketError(int)));
    connect(&sock, SIGNAL(delayedCloseFinished()), this, SLOT(socketDisconnected()));
}

DonkeyProtocol::~DonkeyProtocol()
{
}

void DonkeyProtocol::setPassword(const QString& username, const QString& pwd)
{
    uname = username;
    passwd = pwd;
}

void DonkeyProtocol::setPassword(const QString& pwd)
{
    uname = "admin";
    passwd = pwd;
}

const QString& DonkeyProtocol::username()
{
    return uname;
}

const QString& DonkeyProtocol::password()
{
    return passwd;
}

bool DonkeyProtocol::isConnected()
{
    return (sock.state() == QSocket::Connected);
}

bool DonkeyProtocol::connectToCore()
{
    donkeyError = NoError;
    if (isConnected()) {
        if (! disconnectFromCore()) return false;
    }
    else
        flushState();

    DonkeyHost *host = (DonkeyHost*)getHost();
    if(host) {
        setPassword(host->username(), host->password());
        sock.connectDonkey(host->address(), host->port());
    }
    else {
        sock.connectDonkey();
    }

    return true;
}

bool DonkeyProtocol::disconnectFromCore()
{
    // The QSocket-documentation says that unwritten data will be written
    // on socket.close(). But that doesn't seem to be the case if
    // disconnectFromCore() is called on KMLDonkey::queryClose() cause
    // the stopSearch()'s called at SearchPage::stopAllSearches() got
    // lost. So, we've explicit to call flush() to be sure.
    sock.flush();

    sock.close();
    kdDebug() << "Socket closed." << endl;
    if (sock.state() == QSocket::Idle)
        emit signalDisconnected(donkeyError);
    flushState();
    return true;
}

void DonkeyProtocol::flushState()
{
    download.clear();
    downloaded.clear();
    upload.clear();
    servers.clear();
    networks.clear();
    clients.clear();
    shares.clear();
    searches.clear();
    unmappedResults.clear();
    options.clear();
    friends.clear();
    rooms.clear();
    m_sectionOptions.clear();
    m_pluginOptions.clear();
    consoleCallbacks.clear();
    defSearches.clear();
    connectedservers = 0;
    downloadstarted = false;
}

void DonkeyProtocol::socketDisconnected()
{
    emit signalDisconnected(donkeyError);
    flushState();
}

void DonkeyProtocol::socketError(int err)
{
    switch (err) {
    case QSocket::ErrConnectionRefused:
        emit signalDisconnected(ConnectionRefusedError);
        break;
    case QSocket::ErrHostNotFound:
        emit signalDisconnected(HostNotFoundError);
        break;
    case QSocket::ErrSocketRead:
        donkeyError = CommunicationError;
        disconnectFromCore();
        break;
    }
}

void DonkeyProtocol::pruneClientRecord(int clientno)
{
    QIntDictIterator<FileInfo> it(download);
    for (; it.current(); ++it) {
        it.current()->removeSource(clientno);
        emit fileUpdated(it.current()->fileNo());
        emit fileSourceRemoved(it.current()->fileNo(), clientno);
    }
    if (friends.remove(clientno))
        emit friendRemoved(clientno);
}

const int DonkeyProtocol::protocolVersion()
{
    return proto;
}

void DonkeyProtocol::processMessage()
{
    DonkeyMessage* msg;
    while ((msg = sock.popMessage())) {
        DonkeyMessage* out;
        QString baz;
        emit messageReceived(msg);
        switch (msg->opcode()) {
        case CoreProtocol:
            proto = msg->readInt32();
            // FIXME: We might read the protocol 26 max_to_gui and max_from_gui values here (both int32).
            // We don't, because we don't really need them for anything useful right now.
            kdDebug() << "CoreProtocol message, version " << proto << endl;
            if (proto < MIN_PROTOCOL_VERSION) {
                kdDebug() << "Obsolete protocol version!" << endl;
                donkeyError = IncompatibleProtocolError;
                disconnectFromCore();
                break;
            }
            out = new DonkeyMessage(GuiProtocol);
            out->writeInt32(MAX_PROTOCOL_VERSION);
            sock.sendMessage(*out);
            coreProto = proto;
            if (proto > MAX_PROTOCOL_VERSION)
                proto = MAX_PROTOCOL_VERSION;
            kdDebug() << "Using protocol " << proto << endl;
            delete out;
            if (wantpoll) {
                out = new DonkeyMessage(GuiExtensions);
                out->writeInt16(1);
                out->writeInt32(1);
                out->writeInt8(1);
                sock.sendMessage(*out);
                delete out;
            }
            out = new DonkeyMessage(Password);
            out->writeString(passwd);
            out->writeString(uname);
            sock.sendMessage(*out);
            delete out;
            if (proto >= 31) requestVersion();
            emit signalConnected();
            break;
        case Console: {
            baz = msg->readString();
            bool ok = false;

            if (consoleCallbacks.count() > 0) {
                QRegExp rx("^\n[\\-]+\nEval[\\s]command:[\\s](.+)");
                if (rx.search(baz) >= 0) {
                    QString s = rx.cap(1);
                    int pos = s.find("\n");
                    if (pos > 0) {
                        QString cmd = s.left(pos).stripWhiteSpace();
                        QString res = s.mid(pos + 2, s.length() - pos - 4).stripWhiteSpace();
                        ConsoleCallbackInterface* cb = consoleCallbacks[cmd];
                        if (cb && !res.startsWith("No such command ")) {
                            consoleCallbacks.remove(cmd);
                            cb->callback(cmd, res);
                            ok = true;
                        }
                    }
                }
            }

            if (! ok) emit consoleMessage(baz);
        } break;
        case BadPassword:
            kdDebug() << "Bad password!" << endl;
            donkeyError = AuthenticationError;
            disconnectFromCore();
            break;
        case Client_stats_v1:
        case Client_stats_v2:
        case Client_stats_v3:
            kdDebug() << "Obsolete client stats message received" << endl;
            break;
        case Client_stats:
        {
            int64 ul = msg->readInt64();
            int64 dl = msg->readInt64();
            int64 sh = msg->readInt64();
            int32 nsh = msg->readInt32();
            int32 tul = msg->readInt32();
            int32 tdl = msg->readInt32();
            int32 uul = msg->readInt32();
            int32 udl = msg->readInt32();
            int32 ndl = msg->readInt32();
            int32 ncp = msg->readInt32();

            clientstatsmap.clear();
            int i,j = msg->readInt16();
            for (i=0; i<j; i++) {
                int nw = msg->readInt32();
                int s = msg->readInt32();
                clientstatsmap.replace(nw, s);
            }
            emit clientStats(ul,dl,sh,nsh,tul,tdl,uul,udl,ndl,ncp,&clientstatsmap);
        } break;
        case File_add_source:
        {
            int fn = msg->readInt32();
            int cl = msg->readInt32();
            FileInfo* file = findDownloadFileNo(fn);
            if (file) {
                file->addSource(cl);
                emit fileUpdated(fn);
                emit fileSourceUpdated(fn,cl);
            }
        } break;
        case File_remove_source:
        {
            int fn = msg->readInt32();
            int cl = msg->readInt32();
            FileInfo* file = findDownloadFileNo(fn);
            if (file) {
                file->removeSource(cl);
                emit fileUpdated(fn);
                emit fileSourceRemoved(fn,cl);
            }
        } break;
        case File_update_availability:
        {
            int fn = msg->readInt32();
            int cl = msg->readInt32();
            QString av = msg->readString();
            FileInfo* file = findDownloadFileNo(fn);
            if (file) {
                file->updateAvailability(cl, av);
                emit fileUpdated(fn);
                emit fileSourceUpdated(fn,cl);
            }
        } break;
        case Client_info:
        {
            ClientInfo* client = new ClientInfo(msg,  proto);
            clients.replace(client->clientNo(), client);

            if (client->clientType() == ClientInfo::FriendClient) {
                if (!friends.contains(client->clientNo())) {
                    friends.append(client->clientNo());
                    emit friendUpdated(client->clientNo());
                }
            }
            else if (friends.remove(client->clientNo()))
                emit friendRemoved(client->clientNo());

            if (client->clientState() == ClientInfo::Removed) {
                pruneClientRecord(client->clientNo());
                clients.remove(client->clientNo());
                emit clientRemoved(client->clientNo());

                if (upload.contains(client->clientNo())) {
                    upload.remove(client->clientNo());
                    emit uploadRemoved(client->clientNo());
                }
            } else {
                emit clientUpdated(client->clientNo());

                if (upload.contains(client->clientNo())) {
                    if (upload[client->clientNo()])
                        emit uploadUpdated(client->clientNo());
                    else {
                        upload.remove(client->clientNo());
                        emit uploadRemoved(client->clientNo());
                    }
                }
            }

        } break;
        case Client_state:
        {
            int clno = msg->readInt32();
            ClientInfo* client = findClientNo(clno);
            if (!client) {
                refreshClientInfo(clno);
                break;
            }
            client->setClientState(msg, proto);
            switch (client->clientState()) {
            case ClientInfo::Removed:
                emit clientRemoved(clno);
                pruneClientRecord(clno);
                clients.remove(clno);
                break;
            default:
                emit clientUpdated(clno);
                break;
            }
        } break;
        case Client_friend:
        {
            int clno = msg->readInt32();
            ClientInfo* client = findClientNo(clno);
            if (!client) {
                refreshClientInfo(clno);
                break;
            }
            client->setClientType(msg, proto);
            switch (client->clientType()) {
            case ClientInfo::FriendClient:
                if (!friends.contains(clno)) {
                    friends.append(clno);
                    emit friendUpdated(clno);
                }
                break;
            default:
                if (friends.remove(clno))
                    emit friendRemoved(clno);
            }
            emit clientUpdated(clno);
        } break;
        /*
        case Client_file:
        {
            kdDebug() << "Client_file message: " << msg->readInt32() << " \"" << msg->readString() << "\" " << msg->readInt32() << endl;
        } break;
        */
        case DownloadFiles_v1:
        case DownloadFiles_v2:
        case DownloadFiles_v3:
            kdDebug() << "Obsolete download files message received" << endl;
            break;
        case DownloadFiles_v4:
        case DownloadFiles:
        {
            download.clear();
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                FileInfo* fi = new FileInfo(msg, proto);
                FileInfo* fo = findDownloadFileNo(fi->fileNo());
                if (fo)
                    fo->updateFileInfo(fi);
                else
                    download.replace(fi->fileNo(), fi);
                emit fileUpdated(fi->fileNo());
            }
            emit updatedDownloadFiles();
        } break;
        case File_downloaded_v1:
        case File_downloaded:
        {
            int fn = msg->readInt32();
            FileInfo* fi = findDownloadFileNo(fn);
            if (fi) {
                fi->updateDownloadStatus(msg, proto);
                switch (fi->fileState()) {
                case FileInfo::Shared:
                case FileInfo::Cancelled:
                case FileInfo::Aborted:
                    emit fileRemoved(fn);
                    download.remove(fi->fileNo());
                    break;
                default:
                    emit fileUpdated(fn);
                    break;
                }
            }
        } break;
        case DownloadedFiles_v1:
            kdDebug() << "Obsolete downloaded files message received" << endl;
            break;
        case DownloadedFiles_v2:
        case DownloadedFiles:
        {
            downloaded.clear();
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                FileInfo* fi = new FileInfo(msg, proto);
                FileInfo* fo = findDownloadedFileNo(fi->fileNo());
                if (fo)
                    fo->updateFileInfo(fi);
                else
                    downloaded.replace(fi->fileNo(), fi);
            }
            emit updatedDownloadedFiles();
        } break;
        case File_info_v1:
        case File_info_v2:
            kdDebug() << "Obsolete file info message received" << endl;
            break;
        case File_info_v3:
        case File_info:
        {
            FileInfo* fi = findDownloadFileNo(msg->readInt32());
            msg->resetPosition();
            bool isnew = ! fi;
            if (isnew) {
                fi = new FileInfo(msg, proto);
                download.replace(fi->fileNo(), fi);
            } else {
                fi->updateFileInfo(msg, proto);
            }
            switch (fi->fileState()) {
            case FileInfo::Shared:
            case FileInfo::Cancelled:
            case FileInfo::Aborted:
                emit fileRemoved(fi->fileNo());
                download.remove(fi->fileNo());
                break;
            default:
                emit fileUpdated(fi->fileNo());
                if (isnew) emit fileAdded(fi->fileNo(), downloadstarted);
                break;
            }
        } break;
        case ConnectedServers:
        {
            servers.clear();
            connectedservers = 0;
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                ServerInfo* si = new ServerInfo(msg, proto);
                servers.replace(si->serverNo(), si);
                if (si->serverState() == ClientInfo::Connected || si->serverState() == ClientInfo::Connected2
                    || si->serverState() == ClientInfo::Connected3) connectedservers++;
            }
            emit updatedConnectedServers();
        } break;
        case Server_info_v1:
        case Server_info:
        {
            ServerInfo* si = new ServerInfo(msg, proto);
            ServerInfo* oldsi = findServerNo(si->serverNo());

            ClientInfo::State newstate = si->serverState(), oldstate = oldsi ? oldsi->serverState() : ClientInfo::NotConnected;
            if ((oldstate == ClientInfo::Connected || oldstate == ClientInfo::Connected2 || oldstate == ClientInfo::Connected3)
                && (newstate != ClientInfo::Connected && newstate != ClientInfo::Connected2 && newstate != ClientInfo::Connected3))
                connectedservers--;
            if ((oldstate != ClientInfo::Connected && oldstate != ClientInfo::Connected2 && oldstate != ClientInfo::Connected3)
                && (newstate == ClientInfo::Connected || newstate == ClientInfo::Connected2 || newstate == ClientInfo::Connected3))
                connectedservers++;
            servers.replace(si->serverNo(), si);
            switch (si->serverState()) {
            case ClientInfo::Removed:
                emit serverRemoved(si->serverNo());
                servers.remove(si->serverNo());
                break;
            default:
                emit serverUpdated(si->serverNo());
                break;
            }
        } break;
        case Server_state:
        {
            int sn = msg->readInt32();
            ServerInfo* si = findServerNo(sn);
            if (!si) {
                kdDebug() << "Core sent an invalid server number!" << endl;
                break;
            }
            ClientInfo::State oldstate = si->serverState();
            si->updateServerState(msg, proto);
            ClientInfo::State newstate = si->serverState();
            if ((oldstate == ClientInfo::Connected || oldstate == ClientInfo::Connected2 || oldstate == ClientInfo::Connected3)
                && (newstate != ClientInfo::Connected && newstate != ClientInfo::Connected2 && newstate != ClientInfo::Connected3))
                connectedservers--;
            if ((oldstate != ClientInfo::Connected && oldstate != ClientInfo::Connected2 && oldstate != ClientInfo::Connected3)
                && (newstate == ClientInfo::Connected || newstate == ClientInfo::Connected2 || newstate == ClientInfo::Connected3))
                connectedservers++;
            switch (si->serverState()) {
            case ClientInfo::Removed:
                emit serverRemoved(sn);
                servers.remove(si->serverNo());
                break;
            default:
                emit serverUpdated(sn);
                break;
            }
        } break;
        case Network_info:
        {
            Network* nw = new Network(msg, proto);
            networks.replace(nw->networkNo(), nw);
            emit networkUpdated(nw->networkNo());
        } break;
        case Shared_file_info_v1:
        case Shared_file_info:
        {
            ShareInfo* si = new ShareInfo(msg, proto);
            shares.replace(si->shareNo(), si);
            emit shareUpdated(si->shareNo());
        } break;
        case Shared_file_upload:
        {
            ShareInfo* si = findShareNo(msg->readInt32());
            if (si) {
                si->updateShare(msg, proto);
                emit shareUpdated(si->shareNo());
            }
        } break;
        case Shared_file_unshared:
        {
            int shno = msg->readInt32();
            if (shares.remove(shno))
                emit shareRemoved(shno);
        } break;
        case Options_info:
        {
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                QString key, value;
                key = msg->readString();
                value = msg->readString();
                options.replace(key,value);
            }
            emit optionsUpdated();
        } break;

        case DefineSearches: {
            defSearches.clear();
            defSearch = QString::null;
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                QString name = msg->readString();
                if(i==0) defSearch = name; // first item is always the default one
                defSearches.replace(name, SearchQuery::getQuery(msg));
            }
            emit definedSearchesUpdated();
        } break;

        // Following both messages are used to transfer the Searchresults back.
        // First Result_info's are recieved. Later the Search_result's defines
        // to what Search the previous noted Result_info's belongs too.
        // MLDonkey does cache searches. So, if you start again a search for
        // something that was previously searched for, it returns only
        // Search_result's.
        case Result_info:
        {
            ResultInfo *si = new ResultInfo(msg, proto);
            unmappedResults.replace(si->resultNo(), si);
        } break;

        case Search_result:
        {
            int searchnum = msg->readInt32();
            int resultnum = msg->readInt32();
            ResultInfo* ri = unmappedResults[resultnum];
            if (!ri) return;
            SearchInfo *si = searches[searchnum];
            if (!si) {
                si = new SearchInfo(searchnum);
                searches.replace(searchnum, si);
            }
            si->addResult(ri);
            emit searchUpdated(searchnum, ri);
        } break;

        /*
        case Pending:
        {
            int i, j = msg->readInt16();
            QString o("Pending: ");
            for (i=0; i<j; i++) {
                int v = msg->readInt32();
                o += QString::number(v) + " ";
            }
            kdDebug() << o << endl;
        } break;
        */

        /*
        // We get those message as result of getSearch(searchnum) and
        // getSearches() requests.
        case Search:
        {
            int searchnum = msg->readInt32();
            //kdDebug() << "Search num=" << searchnum << endl;
            SearchInfo *si = searches[searchnum];
            if (! si) {
                si = new SearchInfo(searchnum);
                searches.replace(searchnum, si);
            }
            si->setQuery(msg, proto);
            emit searchRequest(searchnum);
        } break;
        */

        case Client_file:
        {
            int clno = msg->readInt32();
            QString dir = msg->readString();
            int result = msg->readInt32();
            emit clientFileListing(clno, dir, result);
        } break;

        case CleanTables:
        {
            QIntDict<ClientInfo> newClients;
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                ClientInfo* cl = clients.take(msg->readInt32());
                if (cl) newClients.replace(cl->clientNo(), cl);
            }
            QIntDictIterator<ClientInfo> cit(clients);
            for (; cit.current(); ++cit) {
                emit clientRemoved(cit.current()->clientNo());
                pruneClientRecord(cit.current()->clientNo());
            }
            clients = newClients;

            QIntDict<ServerInfo> newServers;
            j = msg->readInt16();
            for (i=0; i<j; i++) {
                ServerInfo* si = servers.take(msg->readInt32());
                if (si) newServers.replace(si->serverNo(), si);
            }
            QIntDictIterator<ServerInfo> sit(servers);
            for (; sit.current(); ++sit)
                emit serverRemoved(sit.current()->serverNo());
            servers = newServers;

            // Don't know if operator=() copies the autodelete state, best to be certain.
            clients.setAutoDelete(true);
            servers.setAutoDelete(true);
        } break;

        case MessageFromClient:
        {
            int clno = msg->readInt32();
            if (!findClientNo(clno))
                refreshClientInfo(clno);
            emit messageFromClient(clno, msg->readString());
        } break;

        case Add_section_option:
        {
            DonkeyOption opt(msg, proto);
            m_sectionOptions.append(opt);
            emit newSectionOption(opt);
        } break;

        case Add_plugin_option:
        {
            DonkeyOption opt(msg, proto);
            m_pluginOptions.append(opt);
            emit newPluginOption(opt);
        } break;

        case Room_info_v2:
        case Room_info:
        {
            int roomno = msg->readInt32();
            RoomInfo* ri = rooms.find(roomno);
            if (ri)
                ri->update(msg, proto);
            else {
                ri = new RoomInfo(msg, proto, roomno);
                rooms.replace(roomno, ri);
            }
            emit roomUpdated(roomno);
        } break;

        case Room_message:
        {
            int roomno = msg->readInt32();
            RoomInfo* ri = rooms.find(roomno);
            if (ri) {
                RoomMessage* rm = new RoomMessage(msg, proto);
                ri->addMessage(rm);
                emit roomMessage(roomno, rm);
            }
            else kdDebug() << QString("Room_message for not existing room %1").arg(roomno) << endl;
        } break;

        case Room_add_user:
        {
            int roomnum = msg->readInt32();
            int usernum = msg->readInt32();
            emit roomAddUser(roomnum, usernum);
            kdDebug() << "Room_add_user roomnum=" << roomnum << " usernum=" << usernum << endl;
        } break;

        case Room_remove_user:
        {
            int roomnum = msg->readInt32();
            int usernum = msg->readInt32();
            emit roomRemoveUser(roomnum, usernum);
            kdDebug() << "Room_remove_user roomnum=" << roomnum << " usernum=" << usernum << endl;
        } break;

        case UploadFiles:
        {
            for (QMap<int,bool>::Iterator it = upload.begin(); it != upload.end(); ++it)
                it.data() = false; // We use the bool-value to indicate if an uploaditem gots dirty (removed).
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                int num = msg->readInt32();
                upload.replace(num, true);
                refreshClientInfo(num);
            }
            for (QMap<int,bool>::Iterator it = upload.begin(); it != upload.end(); ++it)
                if (! it.data()) refreshClientInfo(it.key());
        } break;

        case Version:
        {
            QString version = msg->readString();
            emit coreVersion(version);
            kdDebug() << "Core reported version " << version << endl;
        } break;

        default:
            emit unhandledMessage(msg);
            break;
        }
        delete msg;
    }
}

void DonkeyProtocol::updateDownloadFiles()
{
    sock.sendMessage(DonkeyMessage(GetDownloadFiles));
}

void DonkeyProtocol::updateDownloadedFiles()
{
    sock.sendMessage(DonkeyMessage(GetDownloadedFiles));
}

void DonkeyProtocol::updateConnectedServers()
{
    sock.sendMessage(DonkeyMessage(GetConnectedServers));
}

const QIntDict<FileInfo>& DonkeyProtocol::downloadFiles()
{
    return download;
}

const QIntDict<FileInfo>& DonkeyProtocol::downloadedFiles()
{
    return downloaded;
}

const QIntDict<ServerInfo>& DonkeyProtocol::connectedServers()
{
    return servers;
}

const QIntDict<Network>& DonkeyProtocol::availableNetworks()
{
    return networks;
}

const QIntDict<ClientInfo>& DonkeyProtocol::clientList()
{
    return clients;
}

const QIntDict<ShareInfo>& DonkeyProtocol::sharedFiles()
{
    return shares;
}

const QValueList<int>& DonkeyProtocol::friendList()
{
    return friends;
}

FileInfo* DonkeyProtocol::findDownloadFileNo(int fileno)
{
    return download[fileno];
}

FileInfo* DonkeyProtocol::findDownloadedFileNo(int fileno)
{
    return downloaded[fileno];
}

ServerInfo* DonkeyProtocol::findServerNo(int serverno)
{
    return servers[serverno];
}

Network* DonkeyProtocol::findNetworkNo(int nwno)
{
    return networks[nwno];
}

ClientInfo* DonkeyProtocol::findClientNo(int clno)
{
    return clients[clno];
}

ShareInfo* DonkeyProtocol::findShareNo(int shno)
{
    return shares[shno];
}

void DonkeyProtocol::sendConsoleMessage(const QString& msg, ConsoleCallbackInterface* callback)
{
    if (callback) consoleCallbacks.replace(msg, callback);
    DonkeyMessage out(Command);
    out.writeString(msg);
    sock.sendMessage(out);
}

void DonkeyProtocol::saveFile(int fileno, const QString& name)
{
    DonkeyMessage out(SaveFile);
    out.writeInt32(fileno);
    out.writeString(name);
    sock.sendMessage(out);
}

void DonkeyProtocol::pauseFile(int fileno, bool pause)
{
    DonkeyMessage out(SwitchDownload);
    out.writeInt32(fileno);
    out.writeInt8((int8)!pause);
    sock.sendMessage(out);
}

void DonkeyProtocol::cancelFile(int fileno)
{
    DonkeyMessage out(RemoveDownload_query);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::setFilePriority(int fileno, int pri)
{
    DonkeyMessage out(SetFilePriority);
    out.writeInt32(fileno);
    out.writeInt32(pri);
    sock.sendMessage(out);
}

void DonkeyProtocol::verifyFileChunks(int fileno)
{
    DonkeyMessage out(VerifyAllChunks);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::getFileFormat(int fileno)
{
    DonkeyMessage out(QueryFormat);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::previewFile(int fileno)
{
    DonkeyMessage out(Preview);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::retryFile(int fileno)
{
    DonkeyMessage out(ConnectAll);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::submitURL(const QString& url)
{
    downloadstarted = true;
    QRegExp rx("^(ftp|http)://.+");
    if (rx.search(url) >= 0 && ! url.lower().endsWith(".torrent")) {
        sendConsoleMessage("http \"" + url + "\"");
    }
    else {
        DonkeyMessage out(Url);
        out.writeString(url);
        sock.sendMessage(out);
    }
}

void DonkeyProtocol::sendMessage(const DonkeyMessage& msg)
{
    sock.sendMessage(msg);
}

void DonkeyProtocol::connectMoreServers()
{
    sock.sendMessage(DonkeyMessage(ConnectMore_query));
}

void DonkeyProtocol::cleanOldServers()
{
    sock.sendMessage(DonkeyMessage(CleanOldServers));
}

void DonkeyProtocol::addServer(int network, const QString& ip, int16 port)
{
    DonkeyMessage out(AddServer_query);
    out.writeInt32(network);
    out.writeInt8(1); // True means following IP address is a string
    out.writeString(ip);
    out.writeInt16(port);
    sock.sendMessage(out);
}

void DonkeyProtocol::removeServer(int serverno)
{
    DonkeyMessage out(RemoveServer_query);
    out.writeInt32(serverno);
    sock.sendMessage(out);
}

void DonkeyProtocol::blacklistServer(int serverno)
{
    ServerInfo* si = findServerNo(serverno);
    if (si) sendConsoleMessage("bs " + si->serverAddress());
}

void DonkeyProtocol::getServerInfo(int serverno)
{
    DonkeyMessage out(GetServer_info);
    out.writeInt32(serverno);
    sock.sendMessage(out);
}

void DonkeyProtocol::getServerUsers(int serverno)
{
    DonkeyMessage out(GetServer_users);
    out.writeInt32(serverno);
    sock.sendMessage(out);
}

void DonkeyProtocol::connectServer(int serverno)
{
    DonkeyMessage out(ConnectServer);
    out.writeInt32(serverno);
    sock.sendMessage(out);
}

void DonkeyProtocol::disconnectServer(int serverno)
{
    DonkeyMessage out(DisconnectServer);
    out.writeInt32(serverno);
    sock.sendMessage(out);
}

SearchInfo* DonkeyProtocol::findSearchNo(int num)
{
    return searches[num];
}

const QIntDict<SearchInfo>& DonkeyProtocol::activeSearches()
{
    return searches;
}

const QString& DonkeyProtocol::definedSearch()
{
    return defSearch;
}

const QMap<QString, SearchQuery*> DonkeyProtocol::definedSearches()
{
    return defSearches;
}

void DonkeyProtocol::startSearch(int searchNum,
                                 SearchQuery* query,
                                 int maxHits,
                                 SearchType searchType,
                                 int network)
{
    DonkeyMessage out(Search_query);
    out.writeInt32((int32)searchNum); // search_num to identify searches
    query->writeQuery(out); // write the query into the message
    out.writeInt32((int32)maxHits); // search_max_hits
    out.writeInt8((int8)searchType); // search_type
    out.writeInt32((int32)network); // network to search
    sock.sendMessage(out);
}

void DonkeyProtocol::stopSearch(int searchNum)
{
    DonkeyMessage out(CloseSearch);
    out.writeInt32(searchNum);
    out.writeInt8(true);
    sock.sendMessage(out);
}

void DonkeyProtocol::getPending()
{
    DonkeyMessage out(GetPending);
    sock.sendMessage(out);
}

void DonkeyProtocol::getSearches()
{
    DonkeyMessage out(GetSearches);
    sock.sendMessage(out);
}

void DonkeyProtocol::getSearch(int search)
{
    DonkeyMessage out(GetSearch);
    out.writeInt32(search);
    sock.sendMessage(out);
}

void DonkeyProtocol::startDownload(const QStringList &names, int num, bool force)
{
    downloadstarted = true;

    DonkeyMessage out(Download_query);

    out.writeInt16(names.count());
    for (int i = 0; i < (int)names.count(); i++) out.writeString(names[i]);

    out.writeInt32(num);
    out.writeInt8(force);

    sock.sendMessage(out);
}

void DonkeyProtocol::refreshShared()
{
    DonkeyMessage out(RefreshUploadStats);
    sock.sendMessage(out);
}

void DonkeyProtocol::refreshFileInfo(int fileno)
{
    DonkeyMessage out(GetFile_info);
    out.writeInt32(fileno);
    sock.sendMessage(out);
}

void DonkeyProtocol::refreshClientInfo(int clientno)
{
    DonkeyMessage out(GetClient_info);
    out.writeInt32(clientno);
    sock.sendMessage(out);
}

uint DonkeyProtocol::connectedServerCount()
{
    return connectedservers;
}

uint DonkeyProtocol::totalServerCount()
{
    return servers.count();
}

void DonkeyProtocol::searchForFriend(const QString& name)
{
    DonkeyMessage out(FindFriend);
    out.writeString(name);
    sock.sendMessage(out);
}

void DonkeyProtocol::addClientFriend(int client)
{
    DonkeyMessage out(AddClientFriend);
    out.writeInt32(client);
    sock.sendMessage(out);
}

void DonkeyProtocol::removeFriend(int client)
{
    DonkeyMessage out(RemoveFriend);
    out.writeInt32(client);
    sock.sendMessage(out);
}

void DonkeyProtocol::removeAllFriends()
{
    sock.sendMessage(DonkeyMessage(RemoveAllFriends));
}

void DonkeyProtocol::connectFriend(int client)
{
    DonkeyMessage out(ConnectFriend);
    out.writeInt32(client);
    sock.sendMessage(out);
}

const ResultInfo* DonkeyProtocol::findClientFile(int fileno)
{
    return unmappedResults[fileno];
}

const QMap<QString, QString>& DonkeyProtocol::optionsList()
{
    return options;
}

QString DonkeyProtocol::getOption(const QString& option)
{
    return options.contains(option) ? options[option] : QString::null;
}

void DonkeyProtocol::setOption(const QString& option, const QString& value)
{
    DonkeyMessage out(SetOption);
    out.writeString(option);
    out.writeString(value);
    sock.sendMessage(out);
}

void DonkeyProtocol::setOptions(const QMap<QString, QString>& opts)
{
    if (opts.count() < 1) return;
    DonkeyMessage out(SaveOptions_query);
    out.writeInt16( (int16) opts.count() );
    for (QMap<QString, QString>::ConstIterator it = opts.begin(); it != opts.end(); ++it) {
        out.writeString( it.key() );
        out.writeString( it.data() );
    }
    sock.sendMessage(out);
}

void DonkeyProtocol::enableNetwork(int nwno, bool enable)
{
    Network* nw = networks[nwno];
    if (! nw) return;
    DonkeyMessage out(EnableNetwork);
    out.writeInt32( (int32) nw->networkNo() );
    out.writeInt8( (int8) (enable ? 1 : 0) );
    sock.sendMessage(out);
}

const QValueList<DonkeyOption>& DonkeyProtocol::sectionOptions()
{
    return m_sectionOptions;
}

const QValueList<DonkeyOption>& DonkeyProtocol::pluginOptions()
{
    return m_pluginOptions;
}

void DonkeyProtocol::killCore()
{
    sock.sendMessage(DonkeyMessage(KillServer));
}

void DonkeyProtocol::sendPrivateMessage(int client, const QString& message)
{
    DonkeyMessage out(MessageToClient);
    out.writeInt32(client);
    out.writeString(message);
    sock.sendMessage(out);
}

const int DonkeyProtocol::coreProtocolVersion()
{
    return coreProto;
}

RoomInfo* DonkeyProtocol::findRoomNo(int roomno)
{
    return rooms[roomno];
}

void DonkeyProtocol::setRoomState(int roomno, RoomInfo::RoomState state)
{
    kdDebug() << "DonkeyProtocol::setRoomState() roomno=" << roomno << " state=" << state << endl;
    DonkeyMessage out(SetRoomState);
    out.writeInt32(roomno);
    out.writeInt32(state);
    sock.sendMessage(out);
}

void DonkeyProtocol::renameFile(int fileno, const QString& name)
{
    DonkeyMessage out(RenameFile);
    out.writeInt32(fileno);
    out.writeString(name);
    sock.sendMessage(out);
}

void DonkeyProtocol::updateUploaders()
{
    DonkeyMessage out(GetUploaders);
    sock.sendMessage(out);
}

void DonkeyProtocol::connectClient(int client)
{
    DonkeyMessage out(ConnectClient);
    out.writeInt32(client);
    sock.sendMessage(out);
}

void DonkeyProtocol::disconnectClient(int client)
{
    DonkeyMessage out(DisconnectClient);
    out.writeInt32(client);
    sock.sendMessage(out);
}

void DonkeyProtocol::sendNetworkMessage(int network, const QString& msg)
{
    DonkeyMessage out(NetworkMessage);
    out.writeInt32(network);
    out.writeString(msg);
    sock.sendMessage(out);
}

void DonkeyProtocol::interestedInSources(bool interested)
{
    DonkeyMessage out(InterestedInSources);
    out.writeBool(interested);
    sock.sendMessage(out);
}

void DonkeyProtocol::requestVersion()
{
    DonkeyMessage out(GetVersion);
    sock.sendMessage(out);
}

void DonkeyProtocol::renameServer(int id, const QString& name)
{
    DonkeyMessage out(ServerRename);
    out.writeInt32(id);
    out.writeString(name);
    sock.sendMessage(out);
}

void DonkeyProtocol::setServerPreferred(int id, bool preferred)
{
    DonkeyMessage out(ServerSetPreferred);
    out.writeInt32(id);
    out.writeBool(preferred);
    sock.sendMessage(out);
}




#include "donkeyprotocol.moc"
