/* -*- 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>
 * Copyright (C) 2009 Gioacchino Mazzurco <gmazzurco89@gmail.com>
 * Copyright (C) 2009 Aleksey Markelov <markelovai@gmail.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 Street, 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 "torrenthost.h"
#include "options.h"

#include <QRegExp>
#include <QFileInfo>
#include <QHostAddress>
#include <kdebug.h>

template<class T> void ptrContainerClear(T &cont)
{
    qDeleteAll(cont);
    cont.clear();
}

DonkeyProtocol::DonkeyProtocol(bool poll, QObject *parent)
    : ProtocolInterface("mldonkey", parent)
    , m_socket(new DonkeySocket(this))
{
    uname = "admin";
    passwd = "";
    donkeyError = NoError;
    connectedservers = 0;
    proto = MIN_PROTOCOL_VERSION;
    wantpoll = poll;
    connect(m_socket, SIGNAL(readyMessage()), SLOT(processMessage()));
    connect(m_socket, SIGNAL(connectionClosed()), SLOT(socketDisconnected()));
    connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
}

DonkeyProtocol::~DonkeyProtocol()
{
    flushState();
}


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 (m_socket->state() == DonkeySocket::ConnectedState);
}

bool DonkeyProtocol::connectToCore()
{
    donkeyError = NoError;
    if (isConnected()) {
        kDebug() << "Is already connected. Trying to disconnect...";
        if (! disconnectFromCore()) {
            kWarning() << "Failed to disconnect.";
            return false;
        }
    }
    else
        flushState();

    DonkeyHost *host = (DonkeyHost*)getHost();
    if(host) {
        setPassword(host->username(), host->password());
        m_socket->connectDonkey(host->address(), host->port());
    }
    else {
        m_socket->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.
    m_socket->flush();

    m_socket->close();
    kDebug() << "Socket closed.";
    flushState();
    return true;
}

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

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

void DonkeyProtocol::socketError(QAbstractSocket::SocketError err)
{
    kDebug() << "Socket error:" << err;
    switch (err) {
    case QAbstractSocket::ConnectionRefusedError:
        emit signalDisconnected(ConnectionRefusedError);
        break;
    case QAbstractSocket::HostNotFoundError:
        emit signalDisconnected(HostNotFoundError);
        break;
    default:
        donkeyError = CommunicationError;
        disconnectFromCore();
        break;
    }
}

void DonkeyProtocol::pruneClientRecord(int clientno)
{
    foreach (FileInfo *fi, download) {
        if (fi->removeSource(clientno)) {
            emit fileUpdated(fi->fileNo());
            emit fileSourceRemoved(fi->fileNo(), clientno);
        }
    }
    if (friends.removeAll(clientno))
        emit friendRemoved(clientno);
}

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

void DonkeyProtocol::processMessage()
{
    DonkeyMessage* msg;
    while ((msg = m_socket->popMessage())) {
        //kDebug()<<"DonkeyProtocol::processMessage opcode="<<msg->opcode();
        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.
            kDebug()<<"CoreProtocol message, version="<<proto<<"MIN_PROTOCOL_VERSION="<<MIN_PROTOCOL_VERSION<<"MAX_PROTOCOL_VERSION="<<MAX_PROTOCOL_VERSION;
            if (proto < MIN_PROTOCOL_VERSION) {
                kWarning() << "Obsolete protocol version!";
                donkeyError = IncompatibleProtocolError;
                disconnectFromCore();
                break;
            }
            out = new DonkeyMessage(GuiProtocol);
            out->writeInt32(MAX_PROTOCOL_VERSION);
            m_socket->sendMessage(*out);
            coreProto = proto;
            if (proto > MAX_PROTOCOL_VERSION)
                proto = MAX_PROTOCOL_VERSION;
            kDebug()<<"Using protocol="<<proto<<"wantpoll="<<wantpoll;
            delete out;
            if (wantpoll) {
                out = new DonkeyMessage(GuiExtensions);
                out->writeInt16(1);
                out->writeInt32(1);
                out->writeInt8(1);
                m_socket->sendMessage(*out);
                delete out;
            }
            out = new DonkeyMessage(Password);
            out->writeString(passwd);
            out->writeString(uname);
            m_socket->sendMessage(*out);
            delete out;
            if (proto >= 31) requestVersion();
            emit signalConnected();
            break;
        case Console: {
            baz = msg->readString();
            bool ok = false;

            while (consoleCallbacks.count() > 0) { //don't worry, it's not a cycle
                QRegExp rx("^\n[\\-]+\nEval[\\s]command:[\\s]([^\n]+)\n\n(.*)");
                if (!rx.exactMatch(baz)) break;

                const QString cmd = rx.cap(1).trimmed();
                const QString res = rx.cap(2).trimmed();
                ConsoleCallbackInterface* cb = consoleCallbacks.value(cmd);
                if (!cb)
                    break;

                if (!res.startsWith("No such command")) {
                    cb->callback(cmd, res);
                }
                consoleCallbacks.remove(cmd);
                //check if this callback is referenced several times before deleting
                if (consoleCallbacks.keys(cb).count() == 0 )
                    delete cb;

                ok = true;
                break; //ok
            }

            if (! ok) emit consoleMessage(baz);
        } break;
        case BadPassword:
            kDebug() << "Bad password!";
            donkeyError = AuthenticationError;
            disconnectFromCore();
            break;
        case Client_stats_v1:
        case Client_stats_v2:
        case Client_stats_v3:
            kDebug() << "Obsolete client stats message received";
            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.insert(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();
            QByteArray av = msg->readByteArray();
            FileInfo* file = findDownloadFileNo(fn);
            if (file) {
                file->updateAvailability(cl, av);
                emit fileUpdated(fn);
                emit fileSourceUpdated(fn,cl);
            }
        } break;
        case Client_info:
        {
            int cn = msg->readInt32();
            ClientInfo *client = findClientNo(cn);
            if (client) {
                client->updateClientInfo(msg,proto);
            } else {
                client = new ClientInfo(cn, msg, proto);
                clients.insert(cn, client);
            }

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

            if (client->clientState() == ClientInfo::Removed) {
                pruneClientRecord(cn);
                delete clients.take(cn);
                emit clientRemoved(cn);

                if (upload.contains(cn)) {
                    upload.remove(cn);
                    emit uploadRemoved(cn);
                }
            } else {
                emit clientUpdated(cn);

                if (upload.contains(cn)) {
                    if (upload[cn])
                        emit uploadUpdated(cn);
                    else {
                        upload.remove(cn);
                        emit uploadRemoved(cn);
                    }
                }
            }

        } 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);
                delete clients.take(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.removeAll(clno))
                    emit friendRemoved(clno);
            }
            emit clientUpdated(clno);
        } break;
        /*
        case Client_file:
        {
            kDebug() << "Client_file message: " << msg->readInt32() << " \"" << msg->readString() << "\" " << msg->readInt32();
        } break;
        */
        case DownloadFiles_v1:
        case DownloadFiles_v2:
        case DownloadFiles_v3:
            kDebug() << "Obsolete download files message received";
            break;
        case DownloadFiles_v4:
        case DownloadFiles:
        {
            ptrContainerClear(download);
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                int fn = msg->readInt32();
                FileInfo *fi = findDownloadFileNo(fn);
                if (fi) {
                    fi->updateFileInfo(msg, proto);
                } else {
                    fi = new FileInfo(fn, msg, proto);
                    download.insert(fn, 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);
                    delete download.take(fi->fileNo());
                    break;
                default:
                    emit fileUpdated(fn);
                    break;
                }
            }
        } break;
        case DownloadedFiles_v1:
            kDebug() << "Obsolete downloaded files message received";
            break;
        case DownloadedFiles_v2:
        case DownloadedFiles:
        {
            ptrContainerClear(downloaded);
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                int fn = msg->readInt32();
                FileInfo *fi = findDownloadedFileNo(fn);
                if (fi) {
                    fi->updateFileInfo(msg, proto);
                } else {
                    fi = new FileInfo(fn, msg, proto);
                    downloaded.insert(fn, fi);
                }
            }
            emit updatedDownloadedFiles();
        } break;
        case File_info_v1:
        case File_info_v2:
            kDebug() << "Obsolete file info message received";
            break;
        case File_info_v3:
        case File_info:
        {
            int fn = msg->readInt32();
            FileInfo* fi = findDownloadFileNo(fn);
            bool isnew = ! fi;
            if (isnew) {
                fi = new FileInfo(fn, msg, proto);
                download.insert(fn, fi);
            } else {
                fi->updateFileInfo(msg, proto);
            }
            switch (fi->fileState()) {
            case FileInfo::Shared:
            case FileInfo::Cancelled:
            case FileInfo::Aborted:
                if (!isnew) emit fileRemoved(fn);
                delete download.take(fn);
                break;
            default:
                emit fileUpdated(fn);
                if (isnew) emit fileAdded(fn, downloadstarted);
                break;
            }
        } break;
        case ConnectedServers:
        {
            ptrContainerClear(servers);
            connectedservers = 0;
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                int sn = msg->readInt32();
                ServerInfo *si = findServerNo(sn); //should always return null actually
                if (si) {
                    si->updateServerInfo(msg,proto);
                } else {
                    si = new ServerInfo(sn, msg, proto);
                    servers.insert(sn, si);
                    if (si->isConnected()) ++connectedservers;
                }
            }
            emit updatedConnectedServers();
        } break;
        case Server_info_v1:
        case Server_info:
        {
            int sn = msg->readInt32();
            ServerInfo *si = findServerNo(sn);
            bool oldstate = si->isConnected();
            if (si) {
                si->updateServerInfo(msg,proto);
            } else {
                si = new ServerInfo(sn, msg, proto);
                servers.insert(sn, si);
            }
            bool newstate = si->isConnected();

            if (oldstate != newstate) connectedservers += newstate ? 1 : -1;

            switch (si->serverState()) {
            case ClientInfo::Removed:
                emit serverRemoved(sn);
                delete servers.take(sn);
                break;
            default:
                emit serverUpdated(sn);
                break;
            }
        } break;
        case Server_state:
        {
            int sn = msg->readInt32();
            ServerInfo* si = findServerNo(sn);
            if (!si) {
                kDebug() << "Core sent an invalid server number!";
                break;
            }
            bool oldstate = si->isConnected();
            si->updateServerState(msg,proto);
            bool newstate = si->isConnected();
            if (oldstate != newstate) connectedservers += newstate ? 1 : -1;
            switch (si->serverState()) {
            case ClientInfo::Removed:
                emit serverRemoved(sn);
                delete servers.take(sn);
                break;
            default:
                emit serverUpdated(sn);
                break;
            }
        } break;
        case Network_info:
        {
            int nn = msg->readInt32();
            Network *nw = findNetworkNo(nn);
            if (nw) {
                nw->updateNetwork(msg,proto);
            } else {
                nw = new Network(nn, msg, proto);
                networks.insert(nn, nw);
            }
            emit networkUpdated(nn);
        } break;
        case Shared_file_info_v1:
        case Shared_file_info:
        {
            int sn = msg->readInt32();
            ShareInfo *si = findShareNo(sn);
            if (si) {
                si->updateShareInfo(msg, proto);
            } else {
                si = new ShareInfo(sn, msg, proto);
                shares.insert(sn, si);
            }
            emit shareUpdated(sn);
        } break;
        case Shared_file_upload:
        {
            int sn = msg->readInt32();
            ShareInfo* si = findShareNo(sn);
            if (si) {
                si->updateShare(msg, proto);
                emit shareUpdated(sn);
            }
        } break;
        case Shared_file_unshared:
        {
            int shno = msg->readInt32();
            if (ShareInfo *si = shares.take(shno)) {
                emit shareRemoved(shno);
                delete si;
            }
        } break;
        case Options_info:
        {
            int i, j = msg->readInt16();
            bool ok = true;
            for (i=0; i<j; i++) {
                QString key, value;
                key = msg->readString(&ok);
                if (! ok) break;
                value = msg->readString(&ok);
                if (! ok) break;
                options.insert(key,value);
            }
            emit optionsUpdated();
        } break;

        case DefineSearches: {
            ptrContainerClear(defSearches);
            defSearch = QString();
            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.insert(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.insert(si->resultNo(), si);
        } break;

        case Search_result:
        {
            int searchnum = msg->readInt32();
            int resultnum = msg->readInt32();
            ResultInfo* ri = unmappedResults.value(resultnum);
            if (!ri) return;
            SearchInfo *si = findSearchNo(searchnum);
            if (!si) {
                si = new SearchInfo(searchnum);
                searches.insert(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) + " ";
            }
            kDebug() << o;
        } break;
        */

        /*
        // We get those message as result of getSearch(searchnum) and
        // getSearches() requests.
        case Search:
        {
            int searchnum = msg->readInt32();
            //kDebug() << "Search num=" << searchnum;
            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:
        {
            ClientInfoHash newClients;
            int i, j = msg->readInt16();
            for (i=0; i<j; i++) {
                int cn = msg->readInt32();
                ClientInfo* cl = clients.take(cn);
                if (cl) newClients.insert(cn, cl);
            }
            ClientInfoHash::iterator it = clients.begin(), itend = clients.end();
            for (; it != itend; ++it) {
                emit clientRemoved(it.key());
                pruneClientRecord(it.key());
            }
            ptrContainerClear(clients);
            clients = newClients;

            ServerInfoHash newServers;
            j = msg->readInt16();
            for (i=0; i<j; i++) {
                int sn = msg->readInt32();
                ServerInfo* si = servers.take(sn);
                if (si) newServers.insert(sn, si);
            }
            ServerInfoHash::iterator sit = servers.begin(), sitend = servers.end();
            for (; sit != sitend; ++sit) {
                emit serverRemoved(sit.key());
            }
            ptrContainerClear(servers);
            servers = newServers;
        } 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 = findRoomNo(roomno);
            if (ri) {
                ri->updateRoomInfo(msg, proto);
            } else {
                ri = new RoomInfo(roomno, msg, proto);
                rooms.insert(roomno, ri);
            }
            emit roomUpdated(roomno);
        } break;

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

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

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

        case UploadFiles:
        {
            for (QMap<int,bool>::Iterator it = upload.begin(); it != upload.end(); ++it)
                it.value() = 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.insert(num, true);
                refreshClientInfo(num);
            }
            for (QMap<int,bool>::Iterator it = upload.begin(); it != upload.end(); ++it)
                if (! it.value()) refreshClientInfo(it.key());
        } break;

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

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

void DonkeyProtocol::updateDownloadFiles()
{
    m_socket->sendMessage(DonkeyMessage(GetDownloadFiles));
}

void DonkeyProtocol::updateDownloadedFiles()
{
    m_socket->sendMessage(DonkeyMessage(GetDownloadedFiles));
}

void DonkeyProtocol::updateConnectedServers()
{
    m_socket->sendMessage(DonkeyMessage(GetConnectedServers));
}

const FileInfoHash& DonkeyProtocol::downloadFiles()
{
    return download;
}

const FileInfoHash& DonkeyProtocol::downloadedFiles()
{
    return downloaded;
}

const ServerInfoHash& DonkeyProtocol::connectedServers()
{
    return servers;
}

const NetworkHash& DonkeyProtocol::availableNetworks()
{
    return networks;
}

const ClientInfoHash& DonkeyProtocol::clientList()
{
    return clients;
}

const ShareInfoHash& DonkeyProtocol::sharedFiles()
{
    return shares;
}

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

FileInfo* DonkeyProtocol::findDownloadFileNo(int fileno)
{
    return download.value(fileno);
}

FileInfo* DonkeyProtocol::findDownloadedFileNo(int fileno)
{
    return downloaded.value(fileno);
}

ServerInfo* DonkeyProtocol::findServerNo(int serverno)
{
    return servers.value(serverno);
}

Network* DonkeyProtocol::findNetworkNo(int nwno)
{
    return networks.value(nwno);
}


Network *DonkeyProtocol::findNetworkName(const QString &name)
{
    foreach (Network *net, networks) {
        if (net->networkName() == name) return net;
    }
    return 0;
}

ClientInfo* DonkeyProtocol::findClientNo(int clno)
{
    return clients.value(clno);
}

ShareInfo* DonkeyProtocol::findShareNo(int shno)
{
    return shares.value(shno);
}

void DonkeyProtocol::sendConsoleMessage(const QString& msg, ConsoleCallbackInterface* callback)
{
    if (callback) {
        delete consoleCallbacks.take(msg);
        consoleCallbacks.insert(msg, callback);
    }
    DonkeyMessage out(Command);
    out.writeString(msg);
    m_socket->sendMessage(out);
}

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

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

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

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

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

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

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

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

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

void DonkeyProtocol::submitUrl(const KUrl &url)
{
    downloadstarted = true;//?
    const QString scheme = url.scheme().toLower();
    const bool isTorrent = url.url().endsWith(".torrent",Qt::CaseInsensitive);
    if ( (scheme.isEmpty() || scheme == "file") && isTorrent ) {
        //local torrent
        const QString fileName = url.path();
        const QHostAddress localAddress = m_socket->localAddress();
        const QHostAddress donkeyAddress = QHostAddress(getHost()->address());
        //XXX: may be improved by using one instance of TorrentHost 
        //and make distinction inside instead of using instance per file
        TorrentHost * torrentSender = new TorrentHost(fileName, localAddress, donkeyAddress);
        kDebug() << "submitting file: " << fileName
            << "local address: " << localAddress
            << "donkey host: " << donkeyAddress;

        KUrl httpUrl;
        httpUrl.setScheme("http");
        httpUrl.setHost(localAddress.toString());
        httpUrl.setPort(torrentSender->serverPort());
        httpUrl.setPath(QFileInfo(fileName).fileName());
        //resubmit
        submitUrl(httpUrl);
    } else if ( (scheme == "http" || scheme == "ftp") && !isTorrent) {
        //web file
        sendConsoleMessage("http \"" + url.url() + "\"");
    }
    else {
        //other
        DonkeyMessage out(Url);
        out.writeString(url.url());
        m_socket->sendMessage(out);
    }
}


void DonkeyProtocol::sendMessage(const DonkeyMessage& msg)
{
    m_socket->sendMessage(msg);
}

void DonkeyProtocol::connectMoreServers()
{
    m_socket->sendMessage(DonkeyMessage(ConnectMore_query));
}

void DonkeyProtocol::cleanOldServers()
{
    m_socket->sendMessage(DonkeyMessage(CleanOldServers));
}

void DonkeyProtocol::addServer(int network, const QString& ip, int16 port)
{
    DonkeyMessage out(AddServer_query);
    out.writeInt32(network);
    out.writeIPAddress(QHostAddress(ip));
    out.writeInt16(port);
    m_socket->sendMessage(out);
}

void DonkeyProtocol::removeServer(int serverno)
{
    DonkeyMessage out(RemoveServer_query);
    out.writeInt32(serverno);
    m_socket->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);
    m_socket->sendMessage(out);
}

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

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

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

SearchInfo* DonkeyProtocol::findSearchNo(int num)
{
    return searches.value(num);
}

const SearchInfoHash& 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
    m_socket->sendMessage(out);
}

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

void DonkeyProtocol::getPending()
{
    DonkeyMessage out(GetPending);
    m_socket->sendMessage(out);
}

void DonkeyProtocol::getSearches()
{
    DonkeyMessage out(GetSearches);
    m_socket->sendMessage(out);
}

void DonkeyProtocol::getSearch(int search)
{
    DonkeyMessage out(GetSearch);
    out.writeInt32(search);
    m_socket->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);

    m_socket->sendMessage(out);
}

void DonkeyProtocol::refreshShared()
{
    DonkeyMessage out(RefreshUploadStats);
    m_socket->sendMessage(out);
}

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

void DonkeyProtocol::refreshClientInfo(int clientno)
{
    DonkeyMessage out(GetClient_info);
    out.writeInt32(clientno);
    m_socket->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);
    m_socket->sendMessage(out);
}

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

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

void DonkeyProtocol::removeAllFriends()
{
    m_socket->sendMessage(DonkeyMessage(RemoveAllFriends));
}

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

const ResultInfo* DonkeyProtocol::findClientFile(int fileno)
{
    return unmappedResults.value(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);
    m_socket->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.value() );
    }
    m_socket->sendMessage(out);
}

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

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

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

void DonkeyProtocol::killCore()
{
    m_socket->sendMessage(DonkeyMessage(KillServer));
}

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

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

RoomInfo* DonkeyProtocol::findRoomNo(int roomno)
{
    return rooms.value(roomno);
}

void DonkeyProtocol::setRoomState(int roomno, RoomInfo::RoomState state)
{
    kDebug() << "DonkeyProtocol::setRoomState() roomno=" << roomno << " state=" << state;
    DonkeyMessage out(SetRoomState);
    out.writeInt32(roomno);
    out.writeInt32(state);
    m_socket->sendMessage(out);
}

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

void DonkeyProtocol::updateUploaders()
{
    DonkeyMessage out(GetUploaders);
    m_socket->sendMessage(out);
}

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

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

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

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

void DonkeyProtocol::requestVersion()
{
    DonkeyMessage out(GetVersion);
    m_socket->sendMessage(out);
}

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

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




#include "donkeyprotocol.moc"
