//$Id: client.cpp,v 1.6 1997/11/30 12:16:42 parallax Exp $
//Aaron Granick
//class Client is the core, interpreting the server messages, and
//sorting them out to the right places

#include "client.h"
#include "numeric.h"
#include "irc.h"
#include "ircapp.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>
//KDE
#include <kmsgbox.h>

QString Client::localAddress;

Client::Client ( QObject *parent, const char *name)
        : QObject (parent, name)
{
    connected = FALSE; // we are not connected yet
    registered = false; // nor are we registered
    incomplete = "";
    rsn = NULL;
    wsn = NULL;
    version.sprintf("%s %s %s", APPNAME, VERSION, Settings::versionReply.data());
    dccProcs = new QList<KProcess>();
    dccProcs->setAutoDelete(true);
    dccOffers = new QList<DCCOffer>();
    dccOffers->setAutoDelete(true);
    dccChats = new QList<DCCChatSession>();
    dccChats->setAutoDelete(TRUE);
    processes = new QList<KProcess>();
    processes->setAutoDelete(true);
    procNames = new QList<QString>();
    procNames->setAutoDelete(true);
    kickBanList = new QList<BanRequest>();
    kickBanList->setAutoDelete(true);
}

Client::~Client()
{
    delete dccProcs;
    delete dccOffers;
    delete processes;
}

void Client::HandleEvent (int type, const char *arg1, const char *arg2)
{
    IrcApp::HandleEvent(type, arg1, arg2);
}

void Client::Output(int type, const char *window, const char *txt)
{
    (IrcApp::WindowViewObj())->OutputToWindow(type, window, txt);
}

int Client::ConnectToServer (const char *server, int port )
{
    int fd;
    struct sockaddr_in sin;
    struct hostent* hostent;
    struct protoent* p;

    // app->UpdateAll (); // give us at least time to print stuff out
    
    if ((hostent = gethostbyname(server)) == NULL)
    {

        KMsgBox::message(0, "Get host for server",
                         "The server you tried to connect to does\n"
                         "not have a DNS entry, or the DNS server\n"
                         "containing the server's information is\n"
                         "not responding (or your link is dead.)", KMsgBox::STOP);
	return 0;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    memcpy(&sin.sin_addr, hostent->h_addr,  hostent->h_length);
    p = getprotobyname("tcp");

    fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(fd, F_SETFL, O_NONBLOCK); // make the socket non-blocking
    ::connect(fd, (struct sockaddr *) &sin, sizeof(sin));

    // now get our address from the server
    struct sockaddr_in 	localaddr;
    memset(&localaddr, 0, sizeof(localaddr));
    int len = sizeof(struct sockaddr_in);
    getsockname(fd, (struct sockaddr *) &localaddr, &len);
    localAddress.sprintf("%u",  localaddr.sin_addr.s_addr);
    return fd;
}			

void Client::ConnectClient(int fd)
{
    if (rsn)
        delete rsn;
    if (wsn)
        delete wsn;
    
    rsn = new QSocketNotifier(fd, QSocketNotifier::Read);
    wsn = new QSocketNotifier(fd, QSocketNotifier::Write);

    QObject::connect(rsn, SIGNAL(activated(int)), this, SLOT(ReadData()));
    QObject::connect(wsn, SIGNAL(activated(int)), this, SLOT(WriteData()));

    rsn->setEnabled(TRUE);
    wsn->setEnabled(TRUE);

    HandleEvent ( Event::CONNECTED, NULL);
    connected = true;
    registered = false;
}
bool Client::SendToServer ( const char *buff )
{
    if (!connected)
    {
        Output(Output::INTERNAL_MESSAGE, NULL, "You are not on a server.");
        return FALSE;
    }
    write(wsn->socket(), buff, strlen(buff));

    if (Settings::debug)
        debug("Sent %s to server", buff);

    return TRUE;
}

void Client::ReadData()
{
    if (Settings::debug)
         debug("Reading data from server");
     char dat[8192];
     memset(dat, 0, 8192);
     int len = read(rsn->socket(), dat, 8192);
     if (len <= 0) {
         debug("received eof");
         rsn->setEnabled(FALSE);
         wsn->setEnabled(FALSE);
         connected = FALSE;
         registered = FALSE;
         HandleEvent(Event::DISCONNECTED, NULL);
     }
     else
     {
         QString str(dat);
         str.prepend(incomplete); // tack on anything we might be missing
         incomplete = "";
         while (!str.isEmpty())
         {
             if (Settings::echo)
                 debug("%s", str.data());
         
             int i = str.find("\n");
             if (i > -1)
             {
                 // cout << "Processing: " << str.left(i+1);
                 ParseServer(str.left(i+1));
                 str.remove(0, i+1);
             }
             else
             {
                 incomplete = str.copy();
                 str = "";
             }
         }
     }
}

void Client::WriteData()
{
    signal(SIGPIPE,SIG_IGN);  // capture broken pipes
       
    if (Settings::debug)
        debug("writing data to socket");

    // this should proabably be better...but it works for me
    QString str(600);
    str.sprintf("USER %s foo.com foo.com :%s\r\n",
	Settings::userName.data(), Settings::realName.data());

    int i =  write(wsn->socket(), str, strlen(str));
    if (i == -1)
    {
        wsn->setEnabled(FALSE);
        return;
    }
   
    str = "";
    str.sprintf("NICK %s\r\n", Settings::primaryNick.data());
    i = write(wsn->socket(), str, strlen(str));
    if (i == -1)
    {
        wsn->setEnabled(FALSE);
        return;
    }
    
    wsn->setEnabled(FALSE);
}


bool Client::DisconnectFromServer ( const char *quitMsg)
{
    if (!connected)
    {
        if (Settings::debug)
            debug("Cannot disconnect from server: you are not connected");
        return false;
    }
    QString msg(512);
    if (!quitMsg)
        msg.sprintf("QUIT :%s\r\n", (const char *)Settings::quitMessage );
    else
        msg.sprintf( "QUIT :%s\r\n", quitMsg );
    SendToServer (msg);
    connected = false;
    return true;
}


// takes a unix time (time since 1970 in seconds or some crap like that),
// and turns it into a more readable format
QString Client::UnixTimeToDate ( const char *t )
{
      unsigned int date = atoi(t);
      QDateTime dt;
      dt.setTime_t(date);
      return (dt.toString());
}
    

// the main method in Client. takes a string from the server and breaks it down,
// and sends it to either the word handler or the number handler depending on the
// nature of the message
void Client::ParseServer(const char *dat)
{
    char data[8192], status[600], server[600];

    memset(data, 0, 8192);
    memset(status, 0, 600);
    memset(server, 0, 600);

    // Any data beginning with a ":" that *does not* have an "@"
    // symbol means that it's a server sending you a message.  Any
    // message with a ":" and a "@" means it's a message from
    // a nickname.

    if (dat[0] == ':')
    {
        sscanf(dat, ":%[^ ] %[^ ] %[^\r\n]\r\n", server, status, data);
        if (atoi(status) > 0)
        {
            ParseNumeric(atoi(status), server, data);
        }
        else
        {
            ParseWord(status, server, data);
        }
    }
    else
    {
        // Since no data came in beginning with a ":" at this point,
        // we can safely assume that it's a message from the server
        // directly.
        sscanf(dat, "%[^ ] %[^\r\n]\r\n", status, data);
        
        if (atoi(status) > 0)
            ParseNumeric(atoi(status), server, data);
        else
            ParseWord(status, server, data);
    }
}

void Client::ParseNumeric(int stat, char *, char *dat)
{
    if (Settings::debug)
        debug("parsing numeric message");
    QString str(dat);
    int colon = str.find(':');
    if (colon > -1)
        str.remove(colon, 1); // remove that pesky colon
    // here we split the string apart, seperating the first few words
    QString word1, word2, word3;
    int space = str.find(' ');
    if (space != -1)
    {
        word1 =  str.left(space);
        str.remove(0, space+1);
        space = str.find(' ');
        if (space != -1)
        {
            word2 = str.left(space);
            str.remove(0, space+1);
            space = str.find(' ');
            if (space != -1)
            word3 = str.left(space); // word3 is only used by a few so we don't actually remove it
        }
    }
    if (!registered && stat < 400) // now make sure our nick is correct
    {
        if (Settings::myNick != word1)
            HandleEvent(Event::CHANGE_MY_NICK, word1);
        HandleEvent(Event::REGISTERED, NULL);
        registered = TRUE;
    }
    QString out(660); // our output string
    int type = Output::SERVER_MESSAGE;
    const char *window = NULL;
    if (stat < 5 || (stat >250 && stat <267)) // this is MOTD material...siphon it off
        window = CONSOLENAME;
    if (stat > 399) // an error
        type = Output::ERROR;
    switch (stat)
    {
        case ERR_BANNEDFROMCHAN:
            str += " (you're banned)";
            break;
        case ERR_INVITEONLYCHAN:
            str += " (invite only channel)";
            break;
        case ERR_NICKNAMEINUSE:          
            HandleEvent(Event::NICK_IN_USE, word2);
            break;
        case RPL_NAMREPLY:
            word2 = "Users on";
            // update our channel name for capitalization
            HandleEvent(Event::UPDATE_WINDOW_NAME, word3, word3);
            str.insert(str.find(' '), ':');
            {
                QString nicks = str.copy();
                char *nick = strtok(nicks.data(), " ");
                 nick = strtok(NULL, " "); // remove the first one (its the channel name)
                while(nick)
                {
                    HandleEvent(Event::ADD_NICK, word3, nick);
                    nick = strtok(NULL, " ");
                }
            }
            window = CONSOLENAME;
            break;
        case RPL_ISON:
        {
            QString tmp = word2;
            tmp += " ";
            tmp += str;
            HandleEvent(Event::ISON, tmp);
            return;
        }
            break;
        case RPL_TOPICSETBY:
        {
            QString t = str.right((str.length() - word3.length()-1));
            QString date = UnixTimeToDate(t);
            out.sprintf("topic set by %s on %s", word3.data(), date.data());
            Output(Output::NOTICE, word2, out);
            return;
        }
        break;
        case RPL_TOPIC:
            HandleEvent(Event::TOPIC, word2, str);
            debug(word2);
            str.prepend(" topic is: \"");
            str.prepend(word2);
            str += "\"";
            Output(Output::NOTICE, word2, str);
            return;
            break;
        case RPL_INVITING:
            word2.prepend("Inviting ");
            str.prepend("to channel ");
            type = Output::INFO;
            break;
        case RPL_WHOISOPERATOR:
            type = Output::INFO;
            break;
        case RPL_WHOISUSER:
        {
            word2 += " is";
            int space = str.find(' ');
            str.replace(space, 1, "@");
            type = Output::INFO;
        }
        break ;
        case RPL_WHOISSERVER:
            word2 += " on server";
            type = Output::INFO;
            break;
        case RPL_WHOWASUSER:                         
            word2 += " was";
            type = Output::INFO;
            break;
        case RPL_WHOISCHANNELS:
            word2 += " on channels:";
            type = Output::INFO;
            break;
        case RPL_AWAY:
            word2 += " is away (";
            str += ")";
            type = Output::INFO;
            break;
        case RPL_WHOISIDLE:
        {
            word2 += " has been idle ";
            int secs = atoi(word3);
            if (secs > 3600)
            {
                int hours = secs/3600;
                secs = secs%3600;
                QString h;
                h.sprintf("%d hr, ", hours);
                word2 += h;
            }
            if (secs > 60)
            {
                int minutes = secs/60;
                secs = secs%60;
                QString m;
                m.sprintf("%d min, ", minutes);
                word2 += m;
            }
            str = "";
            str.sprintf("%d sec.", secs);
            type = Output::INFO;
        }
        break;
        case RPL_USERHOST:
        {
            QString tmp = str.copy();
            int i = tmp.find("=");
            QString nick = tmp.left(i);
            tmp.remove(0, i+2);
            for (BanRequest *s=kickBanList->first(); s!= 0; s=kickBanList->next())
            {                             
                if (QString(s->Nick()).lower() == nick.lower())
                {
                    QString banString = "*!*";
                    int a = str.find('@');
                    switch(s->Type())
                    {
                        case BanRequest::NORMAL:
                            tmp.remove(0,a);
                            banString += tmp;
                            break;
                        case BanRequest::HOST:
                        {
                            tmp.remove(0, a);
                            int p = tmp.find('.');
                            tmp.remove(0, p);
                            tmp.prepend("@*");
                            banString += tmp;
                        }
                        break;
                        case BanRequest::DOMAIN:
                        {
                            tmp.remove(0, a);
                            int p = tmp.find('.');
                            tmp.remove(0, p+1);
                            p = tmp.find('.');
                            if (p != -1)
                                tmp.remove(0, p+1);
                            tmp.prepend("@*.");
                            banString += tmp;
                        }
                        break;
                        case BanRequest::NICK:
                            banString = s->Nick();
                            break;
                    }              
                            
                    QString out;
                    out.sprintf("MODE %s +b %s\r\n", s->Channel(), banString.data());
                    HandleEvent(Event::SOCKET_WRITE, out);
                    out = "";
                    out.sprintf("KICK %s %s :%s\r\n", s->Channel(), s->Nick(), s->Reason());
                    HandleEvent(Event::SOCKET_WRITE, out);
                    kickBanList->remove();
                    return;
                }
            }
        }
        break;
            // ignored replys
        case RPL_ENDOFWHOWAS:
        case RPL_ENDOFWHOIS:
        case RPL_ENDOFNAMES:
        case RPL_ENDOFBANLIST:
        case RPL_ENDOFINFO:
        case RPL_ENDOFMOTD:
        case RPL_ENDOFUSERS:
        case RPL_ENDOFSTATS:
        case RPL_ENDOFSERVICES:
            return;
            break;
            // motd stuff
        case RPL_MOTDSTART:
        case RPL_MOTD: 
        case RPL_MOTD2:
        case RPL_MOTD3:
        case RPL_IDENT:
            window = CONSOLENAME;
    }
    // the default implementation. covers most cases
    if (!str.isEmpty())
        word2 += " "; 
   
    Output(type, window, word2 + str);
    
}

void Client::ParseWord( char *stat, char *svr, char *dat)
{
    if (Settings::debug)
        debug("Parsing word.");
    QString word(stat);
    QString str(dat);
    int colon = str.find(':');
    if (colon > -1)
        str.remove(colon, 1);
    // break apart the first few words
    QString rest = str.copy();
    int space = rest.find(' ');
    QString word1 = rest.left(space);
    rest.remove(0, space+1);
    QString address(svr);
    int bam = address.find('!');
    QString nick = address.left(bam);
    address.remove(0, bam+1);
   
    if (word == "NOTICE")
    {
         QString out(660);
        if (nick.isEmpty()) // if no nick, assume it is a server message
        {
            Output(Output::NOTICE, CONSOLENAME, rest);
            return;
        }
        // Check to see if the message is a CTCP message.
        // CTCP messages always begin and end in a CTRL-A (^A).
        if ((rest[0] == 0x01) && (rest[rest.length() -1] == 0x01))
        {
            // Trim off the first part and end part, eliminating the ^A
            rest.remove((rest.length()-1), 1);
            rest.remove(0, 1);
            
            if ((rest.left(5)).lower() == "ping ")
            {
                rest.remove(0, 5);
                unsigned int oldTime = (unsigned int)(atoi(rest.data()));
                time_t currentTime = time(NULL);
                unsigned int pingTime = (unsigned int)currentTime - oldTime;
                out.sprintf("%s PING reply: %u seconds", nick.data(), pingTime);
            }
            else
                out.sprintf ("CTCP Notice from %s: %s", nick.data(), rest.data());    
        }
        else
            out.sprintf("Notice from %s: %s", nick.data(), rest.data());

        Output(Output::NOTICE, NULL, out);
    
        return;
    }
    // this would be a ping from the server.  Pings from other users are
    // CTCP pings and are in the form of a PRIVMSG
    if (word == "PING") 
    {
        QString out;
        out.sprintf("PONG %s\r\n", str.data());
        HandleEvent(Event::SOCKET_WRITE, out);
        return;
    }
    
    if (word == "JOIN")
    {
        if (nick.lower() == Settings::myNick.lower())
            HandleEvent(Event::JOIN_CHANNEL, word1);
         QString out(660);
        out.sprintf("%s [%s] has joined the channel.", nick.data(), address.data());
        Output(Output::NOTICE, word1, out);
        HandleEvent(Event::ADD_NICK, word1, nick);
    
        return;
    }
    
    if (word == "PRIVMSG")
    {           
        HandlePrivmsg( nick.data(), address.data(), word1.data(), rest.data());
        return;
    }
    
    if (word == "PART")
    {
        if (nick.lower() != Settings::myNick.lower())
        {
            QString disp;
            disp.sprintf("%s [%s] has left the channel.", nick.data(), address.data());
            Output(Output::NOTICE, rest, disp);
            HandleEvent(Event::REMOVE_NICK, rest, nick);
        }
        return;
    }
    
    if (word == "KICK")
    {
        int space = rest.find(" ");
        QString kicked = rest.left(space);
        rest.remove(0, space+1);
        QString disp(600);
        if (kicked.lower() != Settings::myNick.lower()) {
            disp.sprintf("%s was kicked by %s (%s)", kicked.data(), nick.data(), rest.data());
            Output(Output::NOTICE, word1, disp);
            HandleEvent(Event::REMOVE_NICK, word1, kicked);
        }
        else
        {
            disp.sprintf("You were kicked off channel %s by %s (%s).  Attempting to rejoin...", \
                         word1.data(), nick.data(), rest.data());
            Output(Output::ERROR, word1, disp);
            QString out;
            out.sprintf("JOIN %s\r\n", word1.data());
            HandleEvent(Event::SOCKET_WRITE, out);
        }
        return;
    }
    
    if (word == "NICK")
    {
        if (nick.lower() == Settings::myNick.lower()) {
            HandleEvent(Event::CHANGE_MY_NICK, str);
        } else {
            HandleEvent(Event::CHANGE_NICK, nick, str);
        }
        return;
    }
    
    if (word == "QUIT")
    {
        HandleEvent(Event::SIGNOFF, nick, str);
        return;
    }
    
    if (word == "MODE")
    {
        HandleMode( nick.data(), str.data());
        return;
    }

    if (word == "TOPIC")
    {
        QString msg(660);
        msg.sprintf("%s has changed the channel topic to \"%s\"", nick.data(), rest.data());
        Output(Output::NOTICE, word1, msg);
        HandleEvent(Event::TOPIC, word1, rest);
        return;
    }

    if (word=="PONG")
    {
        QString out;
        out.sprintf("%s PING reply: ", dat);
        Output(Output::NOTICE, NULL, out);
        return;
    }

    if (word == "INVITE")
    {
        QString out(660);
        out.sprintf("%s (%s) invites you to join channel %s", nick.data(), address.data(), rest.data());
        Output(Output::INFO, NULL, out);
        return;
    }

    if (word == "ERROR")
    {
        Output(Output::ERROR, NULL, str);
        return;
    }

    if (word == "KILL")
    {
        int excl = rest.find('!');
        QString address = rest.left(excl);
        rest.remove(0, excl+1);
        int space = rest.find(' ');
        QString killer = rest.left(space);
        rest.remove(0, space+1);
        QString out(660);
        out.sprintf("Killed - %s by %s[%s] %s", word1.data(), killer.data(), address.data(), rest.data());
        Output(Output::NOTICE, NULL, out);
    }  
        
    // unlike numeric messages, there is no good default way to deal with words, so we
    // give a warning message
    warning("Client::ParseWord: Unknown \"%s\" %s %s", stat, svr, dat);
}


void Client::HandlePrivmsg(char *nick, char *, char *chan, char *ms)
{
    QString disp(512);
    char msg[512];
    memset(msg, 0, 512);
    strcpy(msg, ms);

    // First, check to see if the message is a CTCP message.
    // CTCP messages always begin and end in a CTRL-A (^A).
    if ((msg[0] == 0x01) && (msg[strlen(msg) - 1] == 0x01))
    {
// Trim off the first part and end part, eliminating the ^A
        msg[strlen(msg) - 1] = '\0';
        strcpy(msg, msg + 1);

        if (!strncasecmp(msg, "ACTION ", 7))
        {
            strcpy(msg, msg + 7);
            disp.sprintf("%s %s", nick, msg);
            if (QString(chan).lower() == Settings::myNick.lower())
                Output(Output::ACTION, nick, disp);
            else
                Output(Output::ACTION, chan, disp);
            return;
        }
        
        if (!strncasecmp(msg, "DCC ", 4))
        {
            char type[80], file[80], addr[80], port[80], size[80];
            strcpy(msg, msg + 4);
            memset(type, 0, 80);
            memset(file, 0, 80);
            memset(addr, 0, 80);
            memset(port, 0, 80);
            memset(size, 0, 80);
            sscanf(msg, "%[^ ] %[^ ] %[^ ] %[^ ] %[^\r\n]\r\n", type, file, addr,
		port, size);
            if (!strcasecmp(type, "SEND"))
            {
                QString str(600);
                DCCOffer *offer = new DCCOffer(nick, file, addr, port, size);
                dccOffers->append(offer);
                if (Settings::dccAutoGet)
                {
                    str.sprintf("DCC SEND request from %s (Auto-Getting)", nick);
                    Output(Output::INTERNAL_MESSAGE, NULL, str);
                    DCCGet(nick);
                }
                else
                {
                    str.sprintf("DCC SEND Request for file \"%s\" from %s (%s bytes).\nUse /DCC GET %s to retreive.",
                            file, nick, size, nick);
                    Output(Output::INTERNAL_MESSAGE, NULL, str);
                }
            }
            if (!strcasecmp(type, "CHAT"))
            {
                DCCOffer *offer = new DCCOffer(nick, file, addr, port, size, DCCOffer::CHAT);
                dccOffers->append(offer);
                QString str(600);
                str.sprintf("DCC CHAT Request from  %s.  Use /DCC CHAT %s to accept.", nick, nick);
                Output(Output::INTERNAL_MESSAGE, NULL, str);
            }
            return;
        } // end DCC
            
        if (!strncasecmp(msg, "VERSION", 7))
        {
            disp.sprintf("NOTICE %s :%cVERSION %s%c\r\n", nick, 0x01, Version(), 0x01);
            HandleEvent(Event::SOCKET_WRITE, disp);
            
            disp = "";
            if (chan[0] == '#')
            {
                disp.sprintf("%s requested CTCP VERSION from this channel.",  nick);
                Output(Output::NOTICE, chan, disp);
                return;
            }
            //else
            
            disp.sprintf("%s requested CTCP VERSION from you.", nick);
            Output( Output::NOTICE, NULL, disp);
            return;
        }

    
        if (!strncasecmp(msg, "PING ", 5))
        {
            strcpy(msg, msg + 5);
            disp.sprintf("NOTICE %s :%cPING %s%c\r\n", nick, 0x01,
                    msg, 0x01);
            HandleEvent(Event::SOCKET_WRITE, disp);
            if (chan[0] == '#')
            {
                disp.sprintf("%s requested CTCP PING from this channel.", nick);
                Output(Output::NOTICE, chan, disp);
                return;
            }
            
            disp.sprintf("%s requested CTCP PING from you.", nick);
            Output(Output::NOTICE,NULL, disp);
            return;
        }
        
        if (!strncasecmp(msg, "TIME", 4))
        {
            time_t t = time(NULL);
            QString timeStr;
            timeStr.sprintf("%d", ((unsigned int)t));
            disp.sprintf("NOTICE %s :%c%s%c\r\n", nick, 0x01,
                    timeStr.data(), 0x01);
            HandleEvent(Event::SOCKET_WRITE, disp);
            disp = "";
            if (chan[0] == '#')
            {
                disp.sprintf("%s requested CTCP TIME from this channel.", nick);
                Output(Output::NOTICE, chan, disp);
                return;
            }
            
            disp.sprintf("%s requested CTCP TIME from you.", nick);
            Output(Output::NOTICE, NULL, disp);
            return;
        }
        disp = "";
        if (chan[0] == '#')
        {
            disp.sprintf("%s requested CTCP UNKNOWN (%s) from this channel.", nick, msg);
            Output(Output::NOTICE, chan, disp);
            return;
        }
            
        disp.sprintf("%s requested CTCP UNKNOWN (%s) from you.", nick, msg);
        Output(Output::NOTICE,NULL, disp);
        return;
	   
        
    }
    else // its just a normal message
    {
        disp.sprintf("<%s> %s", nick, msg);
        if (QString(chan).lower() == Settings::myNick.lower())
        {// then we have a private message to us
            Output(Output::NORMAL, nick, disp);
        }
        else
            Output(Output::NORMAL, chan, disp);
           
   
    if (Settings::debug)
        debug("PRIVMSG: %s %s", chan,  disp.data());
    }
}


void Client::HandleMode( char *nick, char *dat)
{
    QString mode(dat);
    int space = mode.find(' ');
    QString chan = mode.left(space);
    mode.remove(0, space+1);
    if (mode.right(1) == " ")
        mode.remove(mode.length()-1, 1); // there seems to be a white space at the end..?

    QString out(600);
    out.sprintf("%s sets mode \"%s\"",nick, mode.data());
    if (chan.lower() == Settings::myNick.lower())
        Output(Output::NOTICE, NULL, out);
    else
    {
        Output(Output::NOTICE, chan, out);
        HandleEvent(Event::NICK_MODE, chan, mode);
    }
}

/*
void Client::HandleWho ( char *dat )
{
    char nick[12],  user[80], chan[80], address[80], server[80], name[200], flags[5];
    bzero(nick, 12);
    bzero(user, 80);
    bzero(chan, 80);
    bzero(address, 80);
    bzero(server, 80);
    bzero(name, 200);
    bzero(flags, 5);
    sscanf(dat, "%*[^ ] %[^ ] %[^ ] %[^ ] %[^ ] %[^ ] %[^ ] %*[^ ] %[^\r\n]\r\n",
           chan, user, address, server, nick, flags, name);
    sprintf(flags, flags+1); // we don't care if they are here or not
    QString out(512);
    out.sprintf("%s: %s%s [%s@%s (%s)] on %s", chan, flags, nick, user, address, name, server);
    Output(Output::INFO, NULL, out);
}
*/


// handles input from user
void Client::ParseUser (const char *input, const char *win)
{
    QString rest(input);
    QString command;
    int s = rest.find(' ');
    if (s != -1)
    {
        command = rest.left(s);
        rest.remove(0, s+1);
    }
    else
    {
        command = rest.copy();
        rest = "";
    }
    /*
    // now try to match it to an alias
    if (aliases)
    {
        for (unsigned int i=0; i<aliases->count(); i++)
        {
            Alias *a = aliases->at(i);
            QString c = a->Command();
            if (c[0] == '/')
                c.remove(0,1);
            QString act = a->Action();
            if (act[0] == '/')
                act.remove(0,1);
            if (c.lower() == QString(comm).lower())
            { // we have a match
                sprintf(msg, "%s %s\r\n", act.data(), rest);
                ParseUser( msg, win ); // recursive call
                return;
            }            
        }
    }
    */
    
    if (HandleBuiltInCommand ( command, rest, win ))
        return;
    
    // if we got here, are built in handlers, and aliases couldn't deal with it...just send it to the server as-is
    if (Settings::debug)
        debug("Could not find an alias or built in function, sending %s to the server", input);
    QString out(input);
    out += "\r\n";
    HandleEvent(Event::SOCKET_WRITE, out);
}

bool Client::HandleBuiltInCommand ( const char *command, const char *rest, const char *win)
{
    QString cmd = (QString(command)).lower();
    QString str((QString(rest)).length() + 512);
    char *msg = str.data(); // this is a safe way to get a char array
       
    if (cmd == "join" || cmd == "j")
    {
        QString tmp(rest);
        tmp = tmp.simplifyWhiteSpace();
        QString chans;
        int c = tmp.find(',');
        while (c != -1)
        {
            QString s = tmp.left(c);
            s = s.stripWhiteSpace();
            if (s[0] != '#')
                s.prepend("#");
            chans += s;
            chans += ",";
            tmp.remove(0, c+1);
            c = tmp.find(',');
        }
        tmp = tmp.stripWhiteSpace();
        if (tmp[0] != '#')
            tmp.prepend("#");
        chans += tmp;
        debug(chans);
        sprintf(msg,  "JOIN %s\r\n", chans.data());
        HandleEvent(Event::SOCKET_WRITE, msg);       
        return true;
    }

    if (cmd == "part" || cmd == "close")
    {
        QString str;
        if ((QString(rest)).isEmpty())
        {
            str = win;
        }
        else
            str = rest;
    
        if (str[0] != '#' && cmd == "part")
            str.prepend("#");
        HandleEvent(Event::CLOSE_WINDOW, str);
        return true;
    }

    if (cmd == "msg" || cmd == "m")
    {
        QString send(660), disp(660);
        char nick[160];
        memset(nick, 0, 160);
        
        sscanf(rest, "%[^ ] %[^\r\n]\r\n", nick, msg);
        send.sprintf("PRIVMSG %s :%s\r\n", nick, msg);
        disp.sprintf("[PRIVMSG] %s: %s", nick, msg);

        HandleEvent(Event::SOCKET_WRITE, send);
        Output (Output::PRIVMESSAGE, win, disp );
        return true;
    }

    if (cmd == "ctcp")
    {
        
        QString str(rest);
        QString nick, firstWord;
        int space = str.find(' ');
        if (space != -1)
        {
            nick = str.left(space);
            str.remove(0, space+1);
        }
        else
            return true;
        space = str.find(' ');
        if (space != -1)
        {
            firstWord = str.left(space);
            str.remove(0, space); // leave the trailing space, if any
        }
        else
        {
            firstWord = str;
            str = "";
        }
        // now make the first word upper case
        firstWord = firstWord.upper();
        if (firstWord == "PING")
        {
            sprintf((char *)rest, "%s\r\n", nick.data());
            cmd == "ping";
        }
        else
        {                   
            QString out;
            out.sprintf("PRIVMSG %s :%c%s%s%c\r\n", nick.data(), 0x01, firstWord.data(), str.data(), 0x01);
            HandleEvent(Event::SOCKET_WRITE, out);
            out = "";
            out .sprintf("[CTCP] %s: %s%s", nick.data(), firstWord.data(), str.data());    
            Output (Output::PRIVMESSAGE, win, out);
            return true;
        }
    }
    
    if (cmd == "ping")
    {
        char nick[160], send[660], disp[660];
        memset(nick, 0, 160);
        memset(send, 0, 660);
        memset(disp, 0, 660);
        
        sscanf(rest, "%[^\r\n]\r\n", nick);
        time_t pingTime = time(NULL);
        sprintf(send, "PRIVMSG %s :%cPING %u%c\r\n", nick, 0x01, (unsigned int)pingTime, 0x01);
        sprintf(disp, "[PING] %s", nick);

        HandleEvent(Event::SOCKET_WRITE, send);
        Output (Output::PRIVMESSAGE, win, disp );
        return true;
    }
        
    if (cmd == "notice")
    {
        char nick[160], send[660], disp[660];
        memset(nick, 0, 160);
        memset(send, 0, 660);
        memset(disp, 0, 660);
        
        sscanf(rest, "%[^ ] %[^\r\n]\r\n", nick, msg);
        sprintf(send, "NOTICE %s :%s\r\n", nick, msg);
        sprintf(disp, "[NOTICE] %s: %s", nick, msg);
        
        HandleEvent(Event::SOCKET_WRITE, send);
        Output(Output::PRIVMESSAGE, win, disp);
        return true;
    }
    
    if (cmd == "query")
    {
        char data1[512];
        char data2[512];
        memset(data1, 0, 512);
        memset(data2, 0, 512);
             
        sscanf(rest, "%[^ ] %[^\r\n]\r\n", data1, data2);
        HandleEvent(Event::QUERY, data1, data2);
        return true;
    }
   
    if (cmd == "notify")
    {
        QString str(rest);
        int s = str.find(' ');
        if (s != -1)
        {
            QString word = str.left(s);
            if (word.lower() == "add")
            {
                str.remove(0, s+1);
                sprintf(msg,  "Added %s to notification list.", str.data());
                Output(Output::INTERNAL_MESSAGE, NULL, msg );
                HandleEvent(Event::ADD_NOTIFY, str);
                return true;
            }
            if (word.lower() == "remove")
            {
                str.remove(0, s+1);
                sprintf(msg,  "Removed %s from notification list.", str.data());
                Output(Output::INTERNAL_MESSAGE, NULL, msg );
                HandleEvent(Event::REMOVE_NOTIFY, str);
                return true;
            }
        }
        sprintf(msg, "Notify: %s", Settings::notifyList.data());
        Output(Output::INTERNAL_MESSAGE, NULL, msg);
        return true;
    }
    
    if (cmd == "kick" || cmd == "k")
    {
        QString str(rest);
        QString firstWord;
        int space = str.find(' ');
        if (space == -1)
        {
            firstWord = str.copy();
            str = Settings::defaultKickReason.copy();
        }
        else
        {
            firstWord = str.left(space);
            str.remove(0, space+1);
        }
        if (firstWord.isEmpty()) // just /KICK
            return true;
        //  if (str.isEmpty())
        //  str = Settings::defaultKickReason.copy();
        if (firstWord[0] != '#') // /KICK nick reason
            sprintf(msg,  "KICK %s %s :%s\r\n", win, firstWord.data(), str.data());
        else // /KICK #chan nick reason
        {
            space = str.find(' ');
            QString nick = str.left(space);
            str.remove(0, space+1);
            sprintf(msg,  "KICK %s  %s :%s\r\n", firstWord.data(), nick.data(), str.data());
        }
        
        HandleEvent(Event::SOCKET_WRITE, msg);  
        return true;
    }

    if (cmd == "away")
    {
        sprintf(msg,  "AWAY :%s\r\n", rest);
        HandleEvent(Event::SOCKET_WRITE, msg);
        return true;
    }

    if (cmd == "op")
    {    
        sprintf(msg,  "MODE %s +o %s\r\n", win, rest);
        HandleEvent(Event::SOCKET_WRITE, msg);  
        return true;
    }
    
    if (cmd == "deop")
    {    
        sprintf(msg,  "MODE %s -o %s\r\n", win, rest);
        HandleEvent(Event::SOCKET_WRITE, msg);  
        return true;
    }
    
    if (cmd == "topic")
    {
        QString str(rest);
        int space = str.find(' ');
        QString firstWord = str.left(space);
        if (space > -1)
            str.remove(0, space+1);
        else
            str = "";
        if (str.isEmpty()) // just a plain /TOPIC
        {
            sprintf(msg,  "TOPIC %s\r\n", win);
        }            
        else
        {
            if (firstWord[0] == '#') //TOPIC #chan new topic
            {
                if (str.isEmpty()) //no topic, show the current
                    sprintf(msg, "TOPIC %s\r\n", firstWord.data());
                else
                    sprintf(msg, "TOPIC %s :%s\r\n", firstWord.data(), str.data());
            }
            else // TOPIC new topic
            {
                sprintf(msg,  "TOPIC %s :%s\r\n", win, rest);
            }
            
        }
        HandleEvent(Event::SOCKET_WRITE, msg);
        return true;
    }

    if (cmd == "me")
    {
        sprintf(msg,  "%s %s", Settings::myNick.data(), rest);
        Output( Output::ACTION, win, msg);
        memset(msg, 0, 512);
        sprintf(msg,  "PRIVMSG %s :%cACTION %s%c\r\n", win, 0x01, rest, 0x01);
        HandleEvent(Event::SOCKET_WRITE, msg);
        return true;
    }

    if (cmd == "quit")
    {
        HandleEvent(Event::QUIT, rest);
        return true;
    }

    if (cmd == "server")
    {
        HandleEvent(Event::CHANGE_SERVER, rest);
        return true;
    }

    if (cmd == "disconnect" )
    {
        HandleEvent(Event::DISCONNECT, rest);
        return true;
    }

    if (cmd == "connect")
    {
        HandleEvent(Event::CONNECT, rest);
        return true;
    }
    
    if (cmd == "dcc")
    {
        DoDCC(rest);
        return true;
    }

    if (cmd == "sv")
    {
        sprintf (msg, "PRIVMSG %s :%s\r\n", win, Version());
        HandleEvent(Event::SOCKET_WRITE, msg);
        QString out;
        out.sprintf("<%s> %s", Settings::myNick.data(), Version());
        Output(Output::OWN, win, out);
        return true;
    }
    if (cmd.left(2) == "kb") // the kick ban
    {
        QString str(rest);
        int space = str.find(' ');
        QString nick;
        QString reason = Settings::defaultKickReason.copy();
        if (space == -1)
                nick = str.copy();
        else
        {
            nick = str.left(space);
            str.remove(0, space+1);
            if (!str.isEmpty())
                reason = str.copy();
        }
        int type = BanRequest::NORMAL;
        if (cmd == "kbdomain")
            type = BanRequest::DOMAIN;
        else if (cmd == "kbhost")
            type = BanRequest::HOST;
        else if (cmd == "kbnick")
            type = BanRequest::NICK;
        kickBanList->append(new BanRequest(nick, win, reason, type));
        QString out;
        out.sprintf("USERHOST %s\r\n", nick.data());
        HandleEvent(Event::SOCKET_WRITE, out);
        return true;
    }
    
    if (cmd == "exec")
    {
        const char *recv = NULL;
        QString str(rest);
        int s = str.find(' ');
        QString word;
        if (s != -1)
        {
            word = str.left(s);
        }
        else
            word = str;
        if (!word.isEmpty())
        {
            if (word.lower() == "-o")
            {
                recv = win;
                str.remove(0, s+1);
            }
            s = str.find(' ');
            QString exec;
            if (s != -1)
            {
                exec = str.left(s);
                str.remove(0, s+1);
            }
            else
                exec = str;
            
            KProcess *proc = new KProcess();
            proc->setExecutable(exec);
            s = str.find(' ');
            while (s != -1)
            {
                *proc << str.left(s);
                exec += " ";
                exec += str.left(s);
                str.remove(0, s+1);
                s = str.find(' ');
            }
            if (!str.isEmpty())
            {
                *proc << str;
                exec += " ";
                exec += str;
            }
            
            procNames->append(new QString(exec.data()));
            processes->append(proc);
            connect(proc, SIGNAL(processExited(KProcess *)), SLOT(ProcessExited(KProcess *)));
            if (recv)
            {
                //connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), \
    //      recv, SLOT(ReceiveOutput(KProcess *, char *, int)));
                proc->start(KProcess::NotifyOnExit, KProcess::Stdout);
            }
            else
                proc->start(KProcess::NotifyOnExit);
        }
        else
        {
            if (procNames->count() != 0)
            {
                Output(Output::INTERNAL_MESSAGE, NULL, "Process List: ");
                for (unsigned int i=0; i<procNames->count(); i++)
                {
                    QString out;
                    QString nm = *(procNames->at(i));
                    out.sprintf("%d: %s", i, nm.data());
                    Output(Output::INTERNAL_MESSAGE, NULL, out);
                }
            }
            else
                Output(Output::INTERNAL_MESSAGE, NULL, "No processes running.");
        }
        return true;
    } // end exec
                            
                       
    // if we got here, no match
    return false;
}

void Client::ProcessExited(KProcess *proc)
{
    int i = processes->find(proc);
    if (i == -1)
    {
        warning("could not find process");
        return;
    }
    else
    {
        processes->remove(i); // remove it, thus deleting it
        procNames->remove(i);
    }
}

#include "client.moc"
