/* -*- c++ -*-
 *
 * mmserver.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 "mmserver.h"
#include "mmconnection.h"
#include "version.h"

#include "hostmanager.h"
#include "serverinfo.h"
#include "clientinfo.h"

#include <sys/types.h>
#include <time.h>

#include <qregexp.h>

#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kconfig.h>
#include <kmdcodec.h>
#include <kapplication.h>
#include <kmessagebox.h>



ConsoleStatusCallback::ConsoleStatusCallback(QObject* parent)
    : QObject(parent)
{
}

void ConsoleStatusCallback::callback(const QString&, const QString& res)
{
    int ul = -1, dl = -1;
    QString nick;
    QRegExp nickRe("client_name += +(.+)$");
    QRegExp ulRe("max_hard_upload_rate += +([0-9]+)");
    QRegExp dlRe("max_hard_download_rate += +([0-9]+)");
    QStringList lines = QStringList::split("\n", res);
    QStringList::Iterator it;
    for (it = lines.begin(); it != lines.end(); ++it) {
	if (ulRe.search(*it) != -1)
	    ul = ulRe.cap(1).toInt();
	else if (dlRe.search(*it) != -1)
	    dl = dlRe.cap(1).toInt();
	else if (nickRe.search(*it) != -1)
	    nick = nickRe.cap(1);
    }
    emit updatedInfo(nick, ul, dl);
    deleteLater();
}





MMServer::MMServer(const QString& listenAddress, int listenPort, const QString& host, const QString& password)
    : KExtendedSocket(listenAddress, listenPort, KExtendedSocket::passiveSocket | KExtendedSocket::inetSocket)
    , m_donkeyHost(host)
    , m_useFakeContentType(false)
    , m_sessionID(0)
    , m_dwBlocked(0)
    , m_cPWFailed(0)
    , m_currentServer(0)
{
    m_password = password;

    hostManager = new HostManager(this);
    donkey = new DonkeyProtocol(true, this);

    QObject::connect(donkey, SIGNAL(signalDisconnected(int)), this, SLOT(donkeyDisconnected(int)));
    QObject::connect(donkey, SIGNAL(signalConnected()), this, SLOT(donkeyConnected()));
    QObject::connect(donkey, SIGNAL(clientStats(int64, int64, int64, int, int, int, int, int, int, int, QMap<int,int>*)),
		     this, SLOT(clientStats(int64, int64, int64, int, int, int, int, int, int, int, QMap<int,int>*)));
    QObject::connect(donkey, SIGNAL(updatedConnectedServers()), this, SLOT(updatedConnectedServers()));

    QObject::connect(hostManager, SIGNAL(hostListUpdated()), this, SLOT(hostListUpdated()));

    hostListUpdated();

    kdDebug(7020) << "MMServer::MMServer(\"" << listenAddress << "\", " << listenPort << ");" << endl;

    setAddressReusable(true);

    QObject::connect(this, SIGNAL(readyAccept()), this, SLOT(incomingConnection()));
    if (listen()) {
	kdDebug(7020) << "Failed to bind socket." << endl;
	return;
    }

    kdDebug(7020) << "Socket is listening." << endl;
}

void MMServer::hostListUpdated()
{
    if (!m_donkeyHost.isNull() && hostManager->validHostName(m_donkeyHost))
        donkey->setHost( hostManager->hostProperties(m_donkeyHost) );
    else
        donkey->setHost( hostManager->defaultHost() );
    donkey->connectToCore();
}


void MMServer::donkeyConnected()
{
    m_donkeyConnected = true;
}

void MMServer::donkeyDisconnected(int)
{
    m_donkeyConnected = false;
}

void MMServer::clientStats(int64 ul, int64 dl, int64 sh, int nsh, int tul, int tdl, int uul, int udl, int ndl, int ncp, QMap<int,int>* nws)
{
    m_uploaded = ul;
    m_downloaded = dl;
    m_shared = sh;
    m_sharedFiles = nsh;
    m_tcpUpload = tul;
    m_tcpDownload = tdl;
    m_udpUpload = uul;
    m_udpDownload = udl;
    m_downloadingFiles = ndl;
    m_completedFiles = ncp;
    m_networks = *nws;

/*
    kdDebug(7020) << "ul " << (int)ul << " dl " << (int)dl << " sh " << (int)sh << " nsh " << nsh << " tul " << tul << " tdl " << tdl
	      << " uul " << uul << " udl " << udl << " ndl " << ndl << " ncp " << ncp << endl;
*/

    donkey->updateConnectedServers();
    donkey->updateDownloadFiles();
    donkey->updateDownloadedFiles();
    ConsoleStatusCallback* cb = new ConsoleStatusCallback(this);
    QObject::connect(cb, SIGNAL(updatedInfo(const QString&,int,int)), SLOT(updatedOptionInfo(const QString&,int,int)));
    donkey->sendConsoleMessage("vo", cb);
}

void MMServer::updatedOptionInfo(const QString& nick, int ul, int dl)
{
    m_nick = nick;
    m_maxUpload = ul;
    m_maxDownload = dl;
}

void MMServer::updatedConnectedServers()
{
    const ServerInfo* si = 0;
    int score = 0;

    const QIntDict<ServerInfo>& s = donkey->connectedServers();
    QIntDictIterator<ServerInfo> it(s);
    for (; it.current(); ++it) {
	// kdDebug(7020) << "Server " << it.currentKey() << ": " << it.current()->serverScore() << " " << it.current()->serverName() << endl;
	if (!si || it.current()->serverScore() > score)
	    si = it.current();
    }
    m_currentServer = si;
}





void MMServer::incomingConnection()
{
    kdDebug(7020) << "Inbound connection." << endl;

    KExtendedSocket* foo;
    if (accept(foo)) {
	kdDebug(7020) << "Accept failed." << endl;
	return;
    }
    kdDebug(7020) << "Connection accepted." << endl;
    if (m_donkeyConnected)
	QObject::connect(new MMConnection(foo, this), SIGNAL(processMessage(MMConnection*, MMPacket*)),
			 SLOT(processMessage(MMConnection*, MMPacket*)));
    else {
	QString out;
	out = "HTTP/1.1 404 Not Found\r\n";
	out += QString("Server: KMLDonkeyMobileMule/%1\r\n").arg(KMLDONKEY_VERSION);
	out += "Connection: close\r\nContent-Type: text/html; charset=utf-8\r\n\r\n";
	out += "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n";
	out += "<html><head><title>404 Not Found</title></head>\r\n";
	out += "<body><h1>404 Not Found</h1><p>MobileMule is currently disconnected from the MLDonkey core.</p></body></html>\r\n";
	QCString data = out.utf8();
	foo->writeBlock((const char*)data, data.length());
	foo->flush();
	foo->closeNow();
	foo->deleteLater();
    }
}

const char* MMServer::getContentType()
{
    return m_useFakeContentType ? "image/vnd.wap.wbmp" : "application/octet-stream";
}

void MMServer::processMessage(MMConnection* sender, MMPacket* p)
{
    int16 sid = p->readShort();
    if ( (m_sessionID && sid != m_sessionID) && p->opcode() != MMP_HELLO) {
	sender->sendPacket(MMPacket(MMP_INVALIDID));
	m_sessionID = 0;
	return;
    }

    kdDebug(7020) << "Received message, opcode " << (int)p->opcode() << " sid " << sid << endl;

    switch (p->opcode())
    {
    case MMP_HELLO:
	processHelloPacket(p, sender);
	break;
    case MMP_FILECOMMANDREQ:
	processFileCommand(p, sender);
	break;
    case MMP_FILEDETAILREQ:
	processDetailRequest(p, sender);
	break;
    case MMP_COMMANDREQ:
	processCommandRequest(p, sender);
	break;
    case MMP_SEARCHREQ:
	processSearchRequest(p, sender);
	break;
    case MMP_DOWNLOADREQ:
	processDownloadRequest(p, sender);
	break;
    case MMP_PREVIEWREQ:
	processPreviewRequest(p, sender);
	break;
    case MMP_CHANGELIMIT:
	processChangeLimitRequest(p, sender);
	break;
    case MMP_STATUSREQ:
	processStatusRequest(sender);
	break;
    case MMP_FILELISTREQ:
	processFileListRequest(sender);
	break;
    case MMP_FINISHEDREQ:
	processFinishedListRequest(sender);
	break;
    default:
	sender->sendPacket(MMPacket(MMP_GENERALERROR));
	break;
    }
}

void MMServer::processHelloPacket(MMPacket* p, MMConnection* sender)
{
    kdDebug(7020) << "processHelloPacket()" << endl;

    MMPacket* packet = new MMPacket(MMP_HELLOANS);
    if (p->readByte() != MM_VERSION) {
	packet->writeByte(MMT_WRONGVERSION);
	sender->sendPacket(packet);
	return;
    }

    if (m_dwBlocked && m_dwBlocked > time(0)) {
	packet->writeByte(MMT_WRONGPASSWORD);
	sender->sendPacket(packet);
	return;
    }

    QString password = p->readString();
    if (password != m_password) {
	m_dwBlocked = 0;
	packet->writeByte(MMT_WRONGPASSWORD);
	sender->sendPacket(packet);
	m_cPWFailed++;
	if (m_cPWFailed == 3) {
	    kdDebug(7020) << "3 failed logins for MobileMule logged - any further attempt is blocked for 10 min!" << endl;
	    m_cPWFailed = 0;
	    m_dwBlocked = time(0) + MMS_BLOCKTIME;
	}
	return;
    }

    m_useFakeContentType = (p->readByte() != 0);

    packet->writeByte(MMT_OK);
    m_sessionID = (int16)KApplication::random();
    kdDebug(7020) << "Logged in successfully, sid set to " << m_sessionID << endl;
    packet->writeShort(m_sessionID);
    packet->writeString(m_nick);
    packet->writeShort(m_maxUpload);
    packet->writeShort(m_maxDownload);
    processStatusRequest(sender, packet);
}

void MMServer::processStatusRequest(MMConnection* sender, MMPacket* packet)
{
    if (!packet)
	packet = new MMPacket(MMP_STATUSANSWER);
    else
	packet->writeByte(MMP_STATUSANSWER);

    packet->writeShort((int16)((m_tcpUpload + m_udpUpload) / 100));
    packet->writeShort((int16)((m_maxUpload * 1024) / 100));
    packet->writeShort((int16)((m_tcpDownload + m_udpDownload) / 100));
    packet->writeShort((int16)((m_maxDownload * 1024) / 100));
    QIntDictIterator<FileInfo> it(donkey->downloadFiles());
    int8 d = 0, p = 0;
    for (; it.current(); ++it) {
	if (it.current()->fileState() == FileInfo::Paused)
	    p++;
	else
	    d++;
    }
    packet->writeByte(d);
    packet->writeByte(p);
    packet->writeInt((int32)(m_downloaded / 1048576));
    packet->writeShort((int16)((m_tcpDownload + m_udpDownload) / 100)); // should be average dl rate, not current

    if (m_currentServer) {
	packet->writeByte(2); // 1 if LowID or 2 otherwise, but there's no way to tell
	packet->writeInt(m_currentServer->serverNUsers());
    } else {
	packet->writeByte(0);
	packet->writeInt(0);
    }
    sender->sendPacket(packet);
}

void MMServer::processFileListRequest(MMConnection* sender, MMPacket* packet)
{
    if (!packet)
	packet = new MMPacket(MMP_FILELISTANS);
    else
	packet->writeByte(MMP_FILELISTANS);

    packet->writeByte(1); // Number of categories, followed by this number of strings containing category titles
    packet->writeString(i18n("the generic file category name", "All"));

    const QIntDict<FileInfo>& l = donkey->downloadFiles();
    packet->writeByte(l.count());
    QIntDictIterator<FileInfo> lit(l);
    m_sentFileList.clear();
    for (; lit.current(); ++lit) {
	FileInfo* fi = lit.current();
	if (fi->fileState() == FileInfo::Paused)
	    packet->writeByte(MMT_PAUSED);
	else {
	    if (fi->fileSpeed())
		packet->writeByte(MMT_DOWNLOADING);
	    else
		packet->writeByte(MMT_WAITING);
	}
	packet->writeString(fi->fileName());
	packet->writeByte(0); // The file's category, 0 means no category (I hope)
	m_sentFileList.append(*fi);
    }
    sender->sendPacket(packet);
}

void MMServer::processFinishedListRequest(MMConnection* sender)
{
    MMPacket* packet = new MMPacket(MMP_FINISHEDANS);

    packet->writeByte(1); // Number of categories, followed by this number of strings containing category titles
    packet->writeString(i18n("the generic file category name", "All"));

    const QIntDict<FileInfo>& l = donkey->downloadedFiles();
    packet->writeByte(l.count());
    QIntDictIterator<FileInfo> lit(l);
    m_sentFinishedList.clear();
    for (; lit.current(); ++lit) {
	FileInfo* fi = lit.current();
	packet->writeByte(0xFF); // I guess this means the file's status is finished
	packet->writeString(fi->fileName());
	packet->writeByte(0); // Category
	m_sentFinishedList.append(*fi);
    }
    sender->sendPacket(packet);
}

void MMServer::processFileCommand(MMPacket* data, MMConnection* sender)
{
    int8 byCommand = data->readByte();
    int8 byFileIndex = data->readByte();
    if (byFileIndex >= m_sentFileList.size()) {
	sender->sendPacket(MMPacket(MMP_GENERALERROR));
	return;
    }
    const FileInfo& fi = m_sentFileList[byFileIndex];
    switch (byCommand) {
    case MMT_PAUSE:
	donkey->pauseFile(fi.fileNo(), true);
	break;
    case MMT_RESUME:
	donkey->pauseFile(fi.fileNo(), false);
	break;
    case MMT_CANCEL:
	donkey->cancelFile(fi.fileNo());
	break;
    default:
	sender->sendPacket(MMPacket(MMP_GENERALERROR));
	return;
    }
    MMPacket* packet = new MMPacket(MMP_FILECOMMANDANS);
    processFileListRequest(sender, packet);
}

void MMServer::processDetailRequest(MMPacket* data, MMConnection* sender)
{
    int8 byFileIndex = data->readByte();
    if (byFileIndex >= m_sentFileList.size()) {
	sender->sendPacket(MMPacket(MMP_GENERALERROR));
	return;
    }

    FileInfo* fi = donkey->findDownloadFileNo(m_sentFileList[byFileIndex].fileNo());
    MMPacket* packet = new MMPacket(MMP_FILEDETAILANS);
    packet->writeInt(fi->fileSize());
    packet->writeInt(fi->fileDownloaded());
    packet->writeInt(fi->fileDownloaded());
    packet->writeShort((int)fi->fileSpeed() / 100);
    packet->writeShort(fi->fileSources().size());
    QValueList<int> clients = fi->fileSources().keys();
    QValueListIterator<int> clit;
    int dlct = 0;
    for (clit = clients.begin(); clit != clients.end(); ++clit) {
	ClientInfo* ci = donkey->findClientNo(*clit);
	if (!ci) continue;
	if (ci->clientState() == ClientInfo::Downloading)
	    dlct++;
    }
    packet->writeShort(dlct);
    packet->writeByte(fi->filePriority() < 0 ? 1 : (fi->filePriority() > 0 ? 3 : 2));
    packet->writeByte(fi->fileChunks().size());
    packet->writeByteArray(fi->fileChunks());
    sender->sendPacket(packet);
}

void MMServer::processCommandRequest(MMPacket* data, MMConnection* sender)
{
    int8 byCommand = data->readByte();
    switch (byCommand) {
    case MMT_SDEMULE:
	donkey->killCore();
	break;
    case MMT_SDPC:
	// Shutdown PC. I don't think I want to implement this at all.
	break;
    case MMT_SERVERCONNECT:
	donkey->connectMoreServers();
	break;
    default:
	sender->sendPacket(MMPacket(MMP_GENERALERROR));
	return;
    }
    sender->sendPacket(MMPacket(MMP_COMMANDANS));
}

void MMServer::processSearchRequest(MMPacket* data, MMConnection* sender)
{
    QString strSearch(data->readString());
    int8 byType = data->readByte();
    QString strLocalSearchType;
    switch (byType) {
    case 0:
	strLocalSearchType = "";
	break;
    case 1:
	strLocalSearchType = "Program"; // FIXME: Should be archive
	break;
    case 2:
	strLocalSearchType = "Audio";
	break;
    case 3:
	strLocalSearchType = "Image";
	break;
    case 4:
	strLocalSearchType = "Program";
	break;
    case 5:
	strLocalSearchType = "Video";
	break;
    default:
	strLocalSearchType = "";
	break;
    }

    if (!m_currentServer) {
	MMPacket packet(MMP_SEARCHANS);
	packet.writeByte(MMT_NOTCONNECTED);
	sender->sendPacket(packet);
	return;
    }

    // FIXME: I'm too lazy to implement searching right now.
    sender->sendPacket(MMPacket(MMP_GENERALERROR));
}

void MMServer::processChangeLimitRequest(MMPacket* data, MMConnection* sender)
{
    int16 nNewUpload = data->readShort();
    int16 nNewDownload = data->readShort();
    donkey->setOption("max_hard_upload_rate", QString::number(nNewUpload));
    donkey->setOption("max_hard_download_rate", QString::number(nNewDownload));

    MMPacket packet(MMP_CHANGELIMITANS);
    packet.writeShort(nNewUpload); // Report the new rates; assume they were successfully changed
    packet.writeShort(nNewDownload);
    sender->sendPacket(packet);
}

void MMServer::processDownloadRequest(MMPacket*, MMConnection* sender)
{
    // FIXME: Implement searching.
    sender->sendPacket(MMPacket(MMP_GENERALERROR));
}

void MMServer::processPreviewRequest(MMPacket*, MMConnection* sender)
{
    // FIXME: Implement previewing.
    sender->sendPacket(MMPacket(MMP_GENERALERROR));
}



#include "mmserver.moc"
