//$Id: dcc.cpp,v 1.5 1997/11/30 04:06:52 parallax Exp $
// handles DCC connections for kirc
// Aaron Granick

#include "client.h"
#include "numeric.h"
#include "irc.h"
#include <iostream.h>
#include <kmsgbox.h>
#include <qdatetm.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <qfile.h>
#include <qdir.h>

void Client::CloseDCCChat(const char *n)
{
    for (DCCChatSession *c = dccChats->first(); c!=0; c=dccChats->next())
    {
        if (QString(c->Nick()).lower() == QString(n).lower())
            dccChats->remove();
    }
}

void Client::SendToDCCChat(const char *nick, const char *say)
{
    debug("writing data to socket");
    for (DCCChatSession *c = dccChats->first(); c!=0; c=dccChats->next())
    {
        if (QString(c->Nick()).lower() == QString(nick).lower())
        {
            if (c->IsOpen())
            {
                bool i  = c->Process()->writeStdin((char *)say, strlen(say));
                //   debug("Sending %s to socket: %d", str.data(), c->Socket());
                if (!i)
                {
                    Output(Output::INTERNAL_MESSAGE, nick, "Could not write to DCC process.  DCC session closed.");
                    c->SetOpen(false);
                }
            }
            else
            {
                Output(Output::INTERNAL_MESSAGE, nick, "DCC session is not active.");
            }
                
        }
    }
}

void Client::DoDCC(const char *cmd)
{
    char type[512], nick[512], file[512];
    memset(type, 0,512);
    memset(nick, 0, 512);
    memset(file, 0, 512);
    sscanf(cmd, "%[^ ] %[^ ] %[^\r\n]\r\n", type, nick, file);
    if (QString(nick).isEmpty())
    {
        emit Output(Output::INTERNAL_MESSAGE, NULL, "No nick specified.");
        return;
    }        
    QString dccType(type);
    if (dccType.lower() == "chat")
    {
        DCCChat(nick);
        return;
    }
   
    if (dccType.lower() == "send")
    {
        if (QString(file).isEmpty())
        {
            emit Output(Output::INTERNAL_MESSAGE, NULL, "No file specified.");
            return;
        }
        DCCSend(nick, file);
        return;
    }

    if (dccType.lower() == "get")
    {
        DCCGet(nick);
        return;
    }

}

void Client::DCCSend(const char *nick, const char *file)
{
    QString fileName(file);
    if (fileName[0] == '~')
    {
        fileName.remove(0,1);
        fileName.prepend(QDir::homeDirPath());
    }
    QFile f(fileName.data());
    if (!f.exists())
    {
        QString txt;
        txt.sprintf("File %s does not exist.", fileName.data());
        emit Output(Output::INTERNAL_MESSAGE, NULL, txt);
        return;
    }
    QString fSize;
    fSize.sprintf("%d", f.size());
    // tell the user what is going on
    QString out;
    out.sprintf("DCC Sending file %s to %s (%s bytes)", nick, fileName.data(), fSize.data());
    emit Output(Output::INTERNAL_MESSAGE, NULL, out);
    int port = 0;
    int fd = CreateDCCPassiveConnection(port);
    QString fileNoPath(fileName.data());
    int slash = fileNoPath.findRev('/');
    if (slash != -1)
    {
        
        QString path = fileNoPath.left(slash);
        fileNoPath.remove(0, slash+1);
    }
    QString notice;
    notice.sprintf("PRIVMSG %s :%cDCC SEND %s %lu %u %s%c\r\n", nick, 0x01, \
                   fileNoPath.data(), ntohl(atoi(localAddress.data())), port, fSize.data(), 0x01);
    //debug(notice);
    SendToServer(notice);
    QString addr;
    addr.sprintf("%d", fd);
    KProcess *proc = new KProcess();
    proc->setExecutable("kdcc");
    *proc << "-s" << nick << fileName << fSize << addr << "0";
    dccProcs->append(proc);
    connect(proc, SIGNAL(processExited(KProcess *)), SLOT(DCCProcessExited(KProcess *)) );
    if (!proc->start())
        warning("could not start kdcc");
    
}

void Client::DCCChat(const char *nick)
{
    HandleEvent(Event::CREATE_DCC_WINDOW, nick);
    QString port = "0";
    QString addr = "0";
    // see if a request exists
    for (DCCOffer *o = dccOffers->first(); o!= 0; o = dccOffers->next())
    {
        if (QString(o->Nick()).lower() == QString(nick).lower() && o->Type() == DCCOffer::CHAT)
        {
            port = o->Port();
            addr = o->Address();
            dccOffers->remove();
            o = NULL;
        }
    }

    if (port == "0")
    {
        // tell the user what is going on
        QString out;
        out.sprintf("Sending DCC CHAT request to %s", nick);
        emit Output(Output::INTERNAL_MESSAGE, NULL, out);
        int p = 0;
        int fd = CreateDCCPassiveConnection(p);
        QString notice;
        if (fd == -1)
        {
            notice.sprintf("Could not establish a passive socket for DCC CHAT with %s", nick);
            Output(Output::INTERNAL_MESSAGE, NULL, notice);
            return;
        }
        
        notice.sprintf("PRIVMSG %s :%cDCC CHAT chat %lu %u%c\r\n", \
                       nick, 0x01,  ntohl(atoi(localAddress.data())), p, 0x01);
        //debug(notice);
        SendToServer(notice);
        addr.sprintf("%d", fd);
    }

    KProcess *proc = new KProcess();
    proc->setExecutable("kdcc");
    *proc << "-c" << nick << "0" << "0" << addr << port;
    dccProcs->append(proc);
    DCCChatSession *dc = new DCCChatSession(nick, proc);
    dc->SetOpen(false);
    dccChats->append(dc);
    connect(proc, SIGNAL(processExited(KProcess *)), SLOT(DCCProcessExited(KProcess *)) );
    connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), SLOT(ReceiveDCCChatData(KProcess *, char *, int)) ); 
    connect(proc, SIGNAL(receivedStderr(KProcess *, char *, int)), SLOT(ReceiveDCCChatData(KProcess *, char *, int)) );
    if (!proc->start(KProcess::NotifyOnExit, KProcess::All))
        warning("could not start kdcc for chat");
}

    
void Client::DCCGet(const char *nick)
{
    // find the DCC offer
    for (unsigned int i=0; i<dccOffers->count(); i++)
    {
        DCCOffer *o = dccOffers->at(i);
        if (QString(o->Nick()).lower() == QString(nick).lower() && o->Type()== DCCOffer::SEND)
        {
            // we have found a match
            // first, inform the user
            QString out;
            out.sprintf("DCC Getting file %s from %s (%s bytes)", o->File(), o->Nick(), o->FileSize());
            emit Output(Output::INTERNAL_MESSAGE, NULL, out);
            // start the DCC process
            KProcess *proc = new KProcess();
            proc->setExecutable("kdcc");
            QString realFile;
            realFile.sprintf("%s/%s", Settings::dccDirectory.data(), o->File());
            debug("nick: %s, file: %s, size: %s, address: %s, port: %s", o->Nick(), realFile.data(), o->FileSize(), o->Address(), o->Port());
            *proc << "-g" << o->Nick() << realFile.data() << o->FileSize() << o->Address() << o->Port();
            dccProcs->append(proc);
            connect(proc, SIGNAL(processExited(KProcess *)), SLOT(DCCProcessExited(KProcess *)) );
            if (!proc->start())
                warning("could not start kdcc");
            //now remove this from our list
            dccOffers->remove(i);
            return;
        }
    }
     
    // no offer found    
    QString out;
    out.sprintf("No file offered by %s.", nick);
    emit Output(Output::INTERNAL_MESSAGE, NULL, out);
        

}
    
int Client::CreateDCCPassiveConnection(int &port)
{

    struct sockaddr_in name;
    
    bzero((char *)&name, sizeof(struct sockaddr_in));
    name.sin_family = AF_INET;
    name.sin_addr.s_addr = htonl(INADDR_ANY);
    name.sin_port = htons(port);
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(fd, (struct sockaddr *)&name, sizeof(name)))
        return -1;

    int length = sizeof (name);
    if (getsockname(fd, (struct sockaddr *)&name, &length))
        return -1;
    
    port = ntohs(name.sin_port);
    fcntl(fd, F_SETFL, O_NONBLOCK); // make the socket non-blocking
    if (listen(fd, 4) < 0)
            return -1;

    return fd;
}

void Client::DCCProcessExited(KProcess *proc)
{
    debug("dcc process exiting");
    if(dccProcs->find(proc) == -1)
    {
        warning("could not find dcc process");
        return;
    }
    else
    {
        dccProcs->remove(); // remove it, thus deleting it
    }
}

void Client::ReceiveDCCChatData(KProcess *, char *buff, int len)
{
    QString str(buff, len);
    debug("Received DCC CHAT: %s", str.data());
    if (str.isEmpty())
        return;
    int endline = str.find('\n');
    if (endline != -1)
        str = str.left(endline);
    
    debug(str);
    int space = str.find(' ');
    QString nick = str.left(space);
    str.remove(0, space+1);
    if (nick == ">connect")
    {
        space = str.find(' ');
        nick = str.left(space);
        str.remove(0, space+1);
        int fd = atoi(str.data());
        for (DCCChatSession *c = dccChats->first(); c!= 0; c= dccChats->next())
        {
            if (nick.lower() == QString(c->Nick()).lower())
            {
                c->SetSocket(fd);
                c->SetOpen(true);
            }
        }
        debug("Socket: %d", fd);
        QString out;
        out.sprintf("DCC CHAT connection with %s established.", nick.data());
        Output(Output::INTERNAL_MESSAGE, nick, out);
        return;
    }
    if (nick == ">closed")
    {
        nick = str;
        for (DCCChatSession *c = dccChats->first(); c!= 0; c= dccChats->next())
        {
            if (nick.lower() == QString(c->Nick()).lower())
                c->SetOpen(false);
        }
        Output(Output::INTERNAL_MESSAGE, nick, "DCC session closed: Remote end closed connection.");
        return;
    }
        
    // treat it like a normal PRIVMESSAGE
    HandlePrivmsg(nick.data(), NULL, nick.data(), str.data());
    debug("Received %s", str.data());  
}
