/*
 * notificationserver.cpp
 * libmsn
 *
 * Created by Mark Rowe on Mon Mar 22 2004.
 * Refactored by Tiago Salem Herrmann on 08/2007.
 * Copyright (c) 2004 Mark Rowe. All rights reserved.
 * Copyright (c) 2007 Tiago Salem Herrmann. All rights reserved
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <msn/config.h>
#include <msn/notificationserver.h>
#include <msn/errorcodes.h>
#include <msn/externals.h>
#include <msn/md5.h>
#include <msn/util.h>
#include <msn/soap.h>
#include <algorithm>
#include <cctype>
#include <cassert>

#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#else
#include <io.h>
#endif

#include <stdio.h>
#include <string.h>
#include <map>

#include "xmlParser.h"

namespace MSN
{
    std::map<std::string, void (NotificationServerConnection::*)(std::vector<std::string> &)> NotificationServerConnection::commandHandlers;
    std::map<std::string, void (NotificationServerConnection::*)(std::vector<std::string> &, std::string, std::string)> NotificationServerConnection::messageHandlers;

    NotificationServerConnection::NotificationServerConnection(Passport username_, std::string password_, Callbacks & cb_) 
        : Connection(), auth(username_, password_), myPassport(username_), m_clientId(0), externalCallbacks(cb_), _connectionState(NS_DISCONNECTED), generatingLockkey(false), removingOIM(false), bplSetting('B')
    {
        msnobj.setCreator(username_);
        registerHandlers();
    }
    
    NotificationServerConnection::~NotificationServerConnection()
    {
        if (this->connectionState() != NS_DISCONNECTED)
            this->disconnect();
    }
    
    Connection *NotificationServerConnection::connectionWithSocket(void *sock)
    {
        this->assertConnectionStateIsNot(NS_DISCONNECTED);
        
        if (this->sock == sock)
            return this;

        std::vector<SwitchboardServerConnection *> & list = _switchboardConnections;
        std::vector<SwitchboardServerConnection *>::iterator i = list.begin();
      
        for (; i != list.end(); i++)
        {
            Connection *c = (*i)->connectionWithSocket(sock);
            if (c)
                return c;
        }

        std::vector<Soap *> & list2 = _SoapConnections;
        std::vector<Soap *>::iterator d = list2.begin();

        for (; d != list2.end(); d++)
        {
            if((*d)->sock == sock )
                return (*d);
        }


        return NULL;
    }
    
    SwitchboardServerConnection *NotificationServerConnection::switchboardWithOnlyUser(Passport username)
    {
        if (this->connectionState() >= NS_CONNECTED)
        {
            std::vector<SwitchboardServerConnection *> & list = _switchboardConnections;
            std::vector<SwitchboardServerConnection *>::iterator i = list.begin();
            
            for (; i != list.end(); i++)
            {
                if ((*i)->users.size() == 1 &&
                    *((*i)->users.begin()) == username)
                    return *i;
            }
        }
        return NULL;
    }
    
    const std::vector<SwitchboardServerConnection *> & NotificationServerConnection::switchboardConnections()
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        return _switchboardConnections;
    }
    
    void NotificationServerConnection::addSwitchboardConnection(SwitchboardServerConnection *c)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);        
        _switchboardConnections.push_back(c);
    }

    void NotificationServerConnection::addSoapConnection(Soap *s)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        _SoapConnections.push_back(s);
    }

    void NotificationServerConnection::removeSwitchboardConnection(SwitchboardServerConnection *c)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);        
        std::vector<SwitchboardServerConnection *>::iterator it;
        for(it = _switchboardConnections.begin(); it != _switchboardConnections.end(); it++)
        {
            if((*it) == c)
            {
                _switchboardConnections.erase(it);
                return;
            }
        }
    }

    void NotificationServerConnection::removeSoapConnection(Soap *s)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);       
        std::vector<Soap *>::iterator it;
        for(it = _SoapConnections.begin(); it != _SoapConnections.end(); it++)
        {
            if((*it) == s)
            {
                _SoapConnections.erase(it);
                return;
            }
        }
    }
   
    void NotificationServerConnection::addCallback(NotificationServerCallback callback,
                                                   int trid, void *data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTING);        
        this->callbacks[trid] = std::make_pair(callback, data);
    }
    
    void NotificationServerConnection::removeCallback(int trid)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTING);        
        this->callbacks.erase(trid);
    }
    
    void NotificationServerConnection::registerHandlers()
    {
        if (commandHandlers.size() == 0)
        {
            commandHandlers["OUT"] = &NotificationServerConnection::handle_OUT;
            commandHandlers["RML"] = &NotificationServerConnection::handle_RML;
            commandHandlers["BLP"] = &NotificationServerConnection::handle_BLP;
            commandHandlers["CHG"] = &NotificationServerConnection::handle_CHG;
            commandHandlers["CHL"] = &NotificationServerConnection::handle_CHL;
            commandHandlers["ILN"] = &NotificationServerConnection::handle_ILN;
            commandHandlers["NLN"] = &NotificationServerConnection::handle_NLN;
            commandHandlers["FLN"] = &NotificationServerConnection::handle_FLN;
            commandHandlers["MSG"] = &NotificationServerConnection::handle_MSG;
            commandHandlers["PRP"] = &NotificationServerConnection::handle_PRP;
            commandHandlers["UBX"] = &NotificationServerConnection::handle_UBX;
            commandHandlers["GCF"] = &NotificationServerConnection::handle_GCF;
            commandHandlers["ADL"] = &NotificationServerConnection::handle_ADL;
            commandHandlers["UBN"] = &NotificationServerConnection::handle_UBN;
            commandHandlers["FQY"] = &NotificationServerConnection::handle_FQY;
        }

        if (messageHandlers.size() == 0)
        {
                messageHandlers["text/x-msmsgsinitialemailnotification"] = &NotificationServerConnection::message_initial_email_notification;
                messageHandlers["text/x-msmsgsinitialmdatanotification"] = &NotificationServerConnection::message_initialmdatanotification;
                messageHandlers["text/x-msmsgsemailnotification"]        = &NotificationServerConnection::message_email_notification;
                messageHandlers["text/x-msmsgsprofile"]                  = &NotificationServerConnection::message_msmsgsprofile;
                messageHandlers["text/x-msmsgsoimnotification"]          = &NotificationServerConnection::message_oimnotification;
        }
    }

    void NotificationServerConnection::dispatchCommand(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);        
        std::map<std::string, void (NotificationServerConnection::*)(std::vector<std::string> &)>::iterator i = commandHandlers.find(args[0]);
        if (i != commandHandlers.end())
            (this->*commandHandlers[args[0]])(args);
    }

    void NotificationServerConnection::disconnectNS()
    {
        std::ostringstream buf_;
        buf_ << "OUT\r\n";
        if (this->write(buf_) != buf_.str().size())
            return;
        disconnect();
    }

    void NotificationServerConnection::handle_OUT(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);        
        if (args.size() > 1)
        {
            if (args[1] == "OTH")
            {
                this->myNotificationServer()->externalCallbacks.showError(this, "You have logged onto MSN twice at once. Your MSN session will now terminate.");
            }
            else if (args[1] == "SSD")
            {
                this->myNotificationServer()->externalCallbacks.showError(this, "This MSN server is going down for maintenance. Your MSN session will now terminate.");
            } else {
                this->myNotificationServer()->externalCallbacks.showError(this, (std::string("The MSN server has terminated the connection with an unknown reason code. Please report this code: ") + 
                                      args[1]).c_str());
            }
        }
        this->disconnect();
    }
    
    void NotificationServerConnection::handle_RML(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        int msglen;
        std::string msg;

        if(args[2] != "OK" && args[2] != "OK")
            return; // TODO - raise an error

        msglen = decimalFromString(args[2]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);

        XMLNode rml_data = XMLNode::parseString( msg.c_str() );

        int nDomains = rml_data.nChildNode("d");
        for(int i=0; i<nDomains; i++)
        {
            XMLNode domain = rml_data.getChildNode("d", i);
            std::string domain_name = domain.getAttribute("n",0);
            int nContacts =  domain.nChildNode("c");
            for(int j=0; j< nContacts; j++)
            {
                XMLNode contact = domain.getChildNode("c",j);
                std::string contact_name = contact.getAttribute("n",0);
                MSN::ContactList list_number = (MSN::ContactList) decimalFromString(contact.getAttribute("l",0));
                // type is 1 for normal contact, 4 for mobile phone
                // TODO - use this value
                //int type = decimalFromString(contact.getAttribute("t",0));

                MSN::Passport passport(contact_name+"@"+domain_name);

                this->myNotificationServer()->externalCallbacks.removedListEntry(this, list_number, passport);
            }
        }
    }
    
    void NotificationServerConnection::handle_BLP(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        this->myNotificationServer()->externalCallbacks.gotBLP(this, args[3][0]);
    }

    void NotificationServerConnection::handle_CHG(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        this->myNotificationServer()->externalCallbacks.changedStatus(this, buddyStatusFromString(args[2]));
    }

    void NotificationServerConnection::handle_CHL(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        std::ostringstream buf_;
        buf_ << "QRY " << this->trID++ << " " << szClientID << " 32\r\n";
        if (write(buf_) != buf_.str().size())
            return;

        char b[33];
        memset(&b,0,33);
        DoMSNP11Challenge(args[2].c_str(),b);
        // send the md5
        std::string a(b);
        write(a, false);
    }

    void NotificationServerConnection::handle_ILN(std::vector<std::string> & args)
    {
        this->assertConnectionStateIs(NS_CONNECTED);
        if(args.size() > 7)
            this->myNotificationServer()->externalCallbacks.buddyChangedStatus(this, args[3], decodeURL(args[5]), buddyStatusFromString(args[2]), decimalFromString(args[6]), decodeURL(args[7]));
        else
            this->myNotificationServer()->externalCallbacks.buddyChangedStatus(this, args[3], decodeURL(args[5]), buddyStatusFromString(args[2]), decimalFromString(args[6]), "");
    }

    void NotificationServerConnection::handle_NLN(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        if(args.size() > 6)
            this->myNotificationServer()->externalCallbacks.buddyChangedStatus(this, args[2], decodeURL(args[4]), buddyStatusFromString(args[1]), decimalFromString(args[5]), decodeURL(args[6]));
        else
            this->myNotificationServer()->externalCallbacks.buddyChangedStatus(this, args[2], decodeURL(args[4]), buddyStatusFromString(args[1]), decimalFromString(args[5]), "");
    }
    
    void NotificationServerConnection::handle_FLN(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        this->myNotificationServer()->externalCallbacks.buddyOffline(this, args[1]);
    }    
 
    void NotificationServerConnection::handle_UBN(std::vector<std::string> & args)
    {
        // TODO - UBN is a way to exchange data through notification server
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

	switch(decimalFromString(args[2])) {
		case 1:
			// XML Data. Don't know what to do..
			break;
		case 2:
			// SIP INVITE
			break;
		case 3:
			// MSNP2P SLP data..
			break;
		case 5:
			// Closed a Chat Window?
			break;
		case 6:
			// Resynchronize..
			// <Service type="ab/membership">
			// reason 1 : added/deleted/blocked/whatever
			// reason 3 : added to favorites
			break;
		case 11:
			// Unknown "1 1 5 134546710 0" prior to SIP invite...
			// Unknown "1 3 5 234889074 0 3 0" after sending a 'busy' response
			// Unknown "2 3 5 234889074 0 3 0" before a tunneled SIP invite that never got through
			// WLM sends UUN 11 : "4 1 $callid $email 3"
			// to itself followed by UUN 11 : "2 3 5 234889074 0 3 0"
			// send to the peer after it receives the ACK for
			// a decline it did on an inbound invite.
			//
			// the 134546710 or 234889074 seems to be the value of "AppVersion" from the registry..
			// WLM sends UUN 11 : "5 1 1" after acking the 200 OK... it apparently means
			// "Supports VGA resolution" or something like that...
			break;
		case 12:
			// Tunneled SIP INVITE
			break;
	}

	std::cout << this->readBuffer << std::endl;

        int msglen;
        std::string msg;
        
        msglen = decimalFromString(args[3]);
	msg = this->readBuffer.substr(0, msglen);
	this->readBuffer = this->readBuffer.substr(msglen);
    }

    void NotificationServerConnection::handle_FQY(std::vector<std::string> & args)
    {
        // TODO - I dont know what it means yet
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        int msglen;
        std::string msg;
        msglen = decimalFromString(args[2]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);
    }

    void NotificationServerConnection::callback_URL(std::vector<std::string> & args, int trid, void *data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        char b[33];
        MSN::hotmailInfo info;
        info.rru = args[2];
        info.url = args[3];
        info.id = args[4];
        info.sl = toStr(time(NULL) - decimalFromString(login_time));
        info.MSPAuth = MSPAuth;
        info.sid = sid;
        info.kv = kv;

        // calculate creds
        std::string creds_tmp = MSPAuth + info.sl + this->auth.password;
        memset(&b,0,33);

        md5_state_t state;
        md5_byte_t digest[16];
        int di;

        md5_init(&state);
        md5_append(&state, (const md5_byte_t *)creds_tmp.c_str(), creds_tmp.size());
        md5_finish(&state, digest);
        // convert to string
        for (di = 0; di < 16; ++di)
            sprintf(&b[2*di], "%02x", digest[di]);

        std::string creds(b);
        info.creds = creds;

#ifdef LIBMSN_INBOX_URL_ENABLED
        this->myNotificationServer()->externalCallbacks.gotInboxUrl(this, info);
#endif
    }
    
    void NotificationServerConnection::handle_MSG(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        int msglen;
        std::string msg;
        std::string mime;
        std::string body;
        size_t tmp;
        
        msglen = decimalFromString(args[3]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);
        
        body = msg.substr(msg.find("\r\n\r\n") + 4);
        mime = msg.substr(0, msg.size() - body.size());  
        
        std::string contentType;
        Message::Headers headers = Message::Headers(mime);
        contentType = headers["Content-Type"];
        
        if ((tmp = contentType.find("; charset")) != std::string::npos)
            contentType = contentType.substr(0, tmp);
        
        std::map<std::string, void (NotificationServerConnection::*)(std::vector<std::string> &, std::string, std::string)>::iterator i = messageHandlers.find(contentType);
        if (i != messageHandlers.end())
            (this->*(messageHandlers[contentType]))(args, mime, body);
    }
    
    void NotificationServerConnection::message_initial_email_notification(std::vector<std::string> & args, std::string mime, std::string body)
    {
        std::string unreadInbox;
        std::string unreadFolder;
        int unreadInboxCount = 0, unreadFolderCount = 0;
        
        // Initial email notifications body is a set of MIME headers        
        Message::Headers headers = Message::Headers(body);
        
        unreadInbox = headers["Inbox-Unread"];
        unreadFolder = headers["Folders-Unread"];
        if (! unreadInbox.empty())
            unreadInboxCount = decimalFromString(unreadInbox);
        
        if (! unreadFolder.empty())
            unreadFolderCount = decimalFromString(unreadFolder);
        
//        this->myNotificationServer()->externalCallbacks.gotInitialEmailNotification(this, unreadInboxCount, unreadFolderCount);
    }

    void NotificationServerConnection::message_email_notification(std::vector<std::string> & args, std::string mime, std::string body)
    {
        // New email notifications body is a set of MIME headers
        Message::Headers headers = Message::Headers(body);
        
        std::string from = headers["From-Addr"];
        std::string subject = headers["Subject"];
        
        this->myNotificationServer()->externalCallbacks.gotNewEmailNotification(this, from, subject);
    }

    void NotificationServerConnection::message_msmsgsprofile(std::vector<std::string> & args, std::string mime, std::string body)
    {
        direct_connection=false;
        Message::Headers headers = Message::Headers(mime);
        server_reported_ip = headers["ClientIP"];
        server_reported_port = headers["ClientPort"];
        login_time = headers["LoginTime"];
        MSPAuth = headers["MSPAuth"];
        sid = headers["sid"];
        kv = headers["kv"];

        if (login_time.empty()) //IN MSNP9  there is no logintime it seems, so set it manualy
        {
            time_t actualTime;
            std::stringstream os;
            time(&actualTime);
            os << actualTime;
            login_time = os.str();
        }

        this->myNotificationServer()->externalCallbacks.gotNewConnection(this);
        // TODO - test portability to windows and mac, probably solved by ifdefs,
        // or with an external callback to user application

        // search on local machine the ip reported by the server
    /*  int s = socket (PF_INET, SOCK_STREAM, 0);
        for (int i=1;;i++)
        {
            struct ifreq ifr;
            struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
            char *ip;

            ifr.ifr_ifindex = i;
            if (ioctl (s, SIOCGIFNAME, &ifr) < 0)
              break;

            if (ioctl (s, SIOCGIFADDR, &ifr) < 0)
              continue;

            ip = inet_ntoa (sin->sin_addr);
            std::string ip2(ip);
            if(ip2==server_reported_ip)
                direct_connection=true;
        }
        close (s);
    */  
    }

    void NotificationServerConnection::message_initialmdatanotification(std::vector<std::string> & args, std::string mime, std::string body)
    {
        Message::Headers headers = Message::Headers(body);

        std::string maildata = headers["Mail-Data"];
        XMLNode domTree = XMLNode::parseString( maildata.c_str() );

        //Mail-Data: <MD><E><I>2</I><IU>1</IU><O>1</O><OU>0</OU></E><Q><QTM>409600</QTM><QNM>204800</QNM></Q></MD>
        //Mail-Data: <MD><E><I>3</I><IU>2</IU><O>2</O><OU>0</OU></E><Q><QTM>409600</QTM><QNM>204800</QNM></Q></MD>
        //Mail-Data: <MD><E><I>3</I><IU>1</IU><O>2</O><OU>0</OU></E><Q><QTM>409600</QTM><QNM>204800</QNM></Q></MD>
        //empty inbox
        //Mail-Data: <MD><E><I>0</I><IU>0</IU><O>5</O><OU>0</OU></E><Q><QTM>409600</QTM><QNM>204800</QNM></Q></MD>
        int emails = domTree.nChildNode("E");

        if (emails)
        {
            XMLNode curr_element = domTree.getChildNode("E",0);
            int inbox_msgs = decimalFromString(curr_element.getChildNode("I").getText());
            int inbox_unread = decimalFromString(curr_element.getChildNode("IU").getText());
            int other_folders = decimalFromString(curr_element.getChildNode("O").getText());
            int other_folders_unread = decimalFromString(curr_element.getChildNode("OU").getText());

            this->myNotificationServer()->externalCallbacks. gotInitialEmailNotification(this, inbox_msgs, inbox_unread, other_folders, other_folders_unread);
        }
        // try to get OIM information
        message_oimnotification(args, mime, body);
    }

    void NotificationServerConnection::message_oimnotification(std::vector<std::string> & args, std::string mime, std::string body)
    {
        Message::Headers headers = Message::Headers(body);

        std::string maildata = headers["Mail-Data"];

        if(maildata == "too-large")
        {
            // more than 25 OIM's
            // request OIM list through soap
            Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

            soapConnection->getMailData();
            return;
        }
        // process maildata
        gotMailData(maildata);
    }

    void NotificationServerConnection::gotSoapMailData(Soap & soapConnection, std::string maildata)
    {
        gotMailData(maildata);
    }

    void NotificationServerConnection::gotMailData(std::string maildata)
    {
        std::vector<eachOIM> messages;
        XMLNode domTree = XMLNode::parseString( maildata.c_str() );

        int oims = domTree.nChildNode("M");

        if (oims)
        {
            for(int i=0;i<oims;i++)
            {
                eachOIM temp_oim;
                XMLNode curr_element = domTree.getChildNode("M",i);
                temp_oim.from = curr_element.getChildNode("E").getText();
                temp_oim.id = curr_element.getChildNode("I").getText();
                temp_oim.fromFN = curr_element.getChildNode("N").getText();

                std::vector<std::string> friendlyName;
                // if we do not have '?', it is just the email 
                if(temp_oim.fromFN.find("?") != std::string::npos)
                {
                    friendlyName=splitString(temp_oim.fromFN,"?");
                    // TODO - handle the encoding (friendlyName[1])
                    if(friendlyName[2]=="B")
                    {
                        temp_oim.fromFN=b64_decode(friendlyName[3].c_str());
                    }
                    if(friendlyName[2]=="Q")
                    {
                        // Quoted-Printable, is similar to URL encoding, 
                        // but uses "=" instead of "%".
                        std::string change = friendlyName[3];
                        // changes the = by %
                        for(unsigned int a=0;a<change.length();a++)
                            if(change[a]=='=') change[a]='%';

                        temp_oim.fromFN=decodeURL(change);
                    }
                }
                messages.push_back(temp_oim);
            }
            this->myNotificationServer()->externalCallbacks.gotOIMList(this, messages);
        }
        domTree.deleteNodeContent('Y');
    }
 
    void NotificationServerConnection::handle_RNG(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        SwitchboardServerConnection::AuthData auth = SwitchboardServerConnection::AuthData(this->auth.username,
                                                                                           args[1],
                                                                                           args[4]);
        SwitchboardServerConnection *newSBconn = new SwitchboardServerConnection(auth, *this);
        this->addSwitchboardConnection(newSBconn);  
        std::pair<std::string, int> server_address = splitServerAddress(args[2]);  
        newSBconn->connect(server_address.first, server_address.second);        
    }
    
    void NotificationServerConnection::handle_PRP(std::vector<std::string> & args)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        if(this->_connectionState == NS_SYNCHRONISING)
        {
            this->myNotificationServer()->externalCallbacks.gotFriendlyName(this, decodeURL(args[3]));
            this->myDisplayName = decodeURL(args[3]);
            this->myNotificationServer()->externalCallbacks.connectionReady(this);
            // the initial process ends here
            this->setConnectionState(NS_CONNECTED);
            return;
        }
        if ( args[2] == "MFN") //when you set manually your FriendlyName
        {
                this->myNotificationServer()->externalCallbacks.gotFriendlyName(this, decodeURL(args[3]));
            this->myDisplayName = decodeURL(args[3]);

        }// TODO - Implement other PRP commands: MBE WWE
    }

    void NotificationServerConnection::handle_GCF(std::vector<std::string> & args)
    {
        int msglen;
        std::string msg;
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        // we do not use it so far. throw it away
        msglen = decimalFromString(args[2]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);
    }

    void NotificationServerConnection::handle_ADL(std::vector<std::string> & args)
    {
        int msglen;
        std::string msg;
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        if(args[2] =="OK" && (this->_connectionState == NS_SYNCHRONISING))
        {
            if(adl_packets.empty())
            {
                // no more adl packets to send!
                // now you need the change the nickname and set the status
                // to complete the initial connection process
                // it is not possible to use setFriendlyName
                // because we cannot send soap requests at this time
                std::ostringstream buf_;
                if(this->myDisplayName.empty())
                this->myDisplayName = myPassport;
                if(server_email_verified != "0")
                {
                    // our email is verified
                    buf_ << "PRP " << this->trID++ << " MFN "  << encodeURL(this->myDisplayName) << "\r\n";
                    write(buf_);
                }
                else
                {
                    // not verified, so we cant change our displayName
                    this->myNotificationServer()->externalCallbacks.connectionReady(this);
                    // the initial process ends here
                    this->setConnectionState(NS_CONNECTED);
                }
                return;
            }
            else
            {
                // send each adl packet at a time
                std::string adl_payload = adl_packets.front();
                adl_packets.pop_front();
                std::ostringstream buf_;
                buf_ << "ADL " << this->trID++ << " " << adl_payload.length() << "\r\n";
                buf_ << adl_payload;
                if (write(buf_) != buf_.str().size())
                    return;
            }
        }
        // I reach here when ADL has payload
        msglen = decimalFromString(args[2]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);
        // ADL 0 70
        // <ml><d n="domain.com"><c n="foo.bar" t="1" l="8" f="fName" /></d></ml>
        // 

        XMLNode adl_data = XMLNode::parseString( msg.c_str() );

        int nDomains = adl_data.nChildNode("d");
        for(int i=0; i<nDomains; i++)
        {
            XMLNode domain = adl_data.getChildNode("d",i);
            std::string domain_name = domain.getAttribute("n",0);
            int nContacts =  domain.nChildNode("c");
            for(int j=0; j< nContacts; j++)
            {
                XMLNode contact = domain.getChildNode("c",j);
                std::string contact_name = contact.getAttribute("n",0);
                std::string fname = contact.getAttribute("f",0);
                MSN::ContactList list_number = (MSN::ContactList) decimalFromString(contact.getAttribute("l",0));
                // type is 1 for wlm contacts, 4 for mobile phone, 32 for yahoo? (or any kind of email contact)
                int type = decimalFromString(contact.getAttribute("t",0));
                if(type == 32)
                    return;

                MSN::Passport passport(contact_name+"@"+domain_name);

                this->myNotificationServer()->externalCallbacks.addedListEntry(this, list_number, passport, fname);
            }
        }
    }

    void NotificationServerConnection::handle_UBX(std::vector<std::string> & args)
    {
        int msglen;
        personalInfo pInfo;
        std::string msg,media,psm;
        MSN::Passport fromPassport = args[1];
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        msglen = decimalFromString(args[3]);
        msg = this->readBuffer.substr(0, msglen);
        this->readBuffer = this->readBuffer.substr(msglen);

        // some buggy clients send no data in UBX command
        if( msg.length() < 10 ) return;

        XMLNode ubx_data = XMLNode::parseString( msg.c_str() );

        const char *a = ubx_data.getChildNode("PSM").getText();
        if(a)
        {
            psm = a;
            pInfo.PSM = psm;
        }

        const char *m = ubx_data.getChildNode("CurrentMedia").getText();
        if(m)
        {
            media = m;
            // the splitString will drop the first NULL position.
            // we need it to keep the order of the following fields.
            std::vector<std::string> media1 = splitString(media, "\\0");
            if(media1.size()>=4) // at least 4 fields. Type, Enabled, format, data
            { // if we have some field, so ...
                int i=0;
                if (media.find("\\0")==0)
                { // if starts with \0 there is no
                  // App field. It is optional.
                    pInfo.mediaApp = "";
                }
                else
                {
                    pInfo.mediaApp = media1[i++];
                }
                pInfo.mediaType = media1[i++];
                pInfo.mediaIsEnabled = decimalFromString(media1[i++]);

                if(pInfo.mediaIsEnabled)
                {
                    pInfo.mediaFormat = media1[i++];
                    for(unsigned int b=i; b < media1.size(); b++)
                    {
                        pInfo.mediaLines.push_back(media1[i++]);
                    }
                }
            }
        }
        this->myNotificationServer()->externalCallbacks.buddyChangedPersonalInfo(this, fromPassport, pInfo);
    }
 
    void NotificationServerConnection::setState(BuddyStatus state, uint clientID)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        std::ostringstream buf_;
        std::string xml;

        if(msnobj.getMSNObjectXMLByType(3,xml))
                buf_ << "CHG " << this->trID++ << " " << 
                buddyStatusToString(state) << " "<< unsignedToStr(clientID) << " " << encodeURL(xml) << "\r\n";
        else
                buf_ << "CHG " << this->trID++ << " " << buddyStatusToString(state) << " " << unsignedToStr(clientID) << "\r\n";
        write(buf_);
    }
    
    void NotificationServerConnection::setBLP(char setting)
    {
        if (setting != 'A' || setting != 'B')
            return;

        if(this->_connectionState == NS_CONNECTED)
        {
            std::ostringstream buf_;
            this->bplSetting = setting;
            buf_ << "BLP " << this->trID++ << " " << setting << "L\r\n";
            write(buf_);
        }
        else
        {
            this->bplSetting = setting;
        }
    }

    void NotificationServerConnection::setFriendlyName(std::string friendlyName, bool updateServer) throw (std::runtime_error)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        if(friendlyName.empty())
            return;
        if (friendlyName.size() > 387)
            throw std::runtime_error("Friendly name too long!");

        if(updateServer)
        {
            // update nickname on server
            Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
            soapConnection->changeDisplayName(friendlyName);
        }
        else
        {
            this->myDisplayName = friendlyName;
            std::ostringstream buf_;
            buf_ << "PRP " << this->trID++ << " MFN "  << encodeURL(friendlyName) << "\r\n";
            write(buf_);
        }
    }

    void NotificationServerConnection::setPersonalStatus(personalInfo pInfo)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        std::string tempMedia;
        XMLNode data = XMLNode::createXMLTopNode("Data");
        XMLNode PSM = XMLNode::createXMLTopNode("PSM");
        XMLNode CurrentMedia = XMLNode::createXMLTopNode("CurrentMedia");
        XMLNode MachineGuid = XMLNode::createXMLTopNode("MachineGuid");

        PSM.addText( pInfo.PSM.c_str() );
        if(pInfo.mediaIsEnabled)
        {
            tempMedia = pInfo.mediaApp +"\\0"+ 
                    pInfo.mediaType+"\\0"+
                    toStr(pInfo.mediaIsEnabled)+"\\0"+
                    pInfo.mediaFormat +"\\0";

            std::vector<std::string>::iterator i = pInfo.mediaLines.begin();
            for(;i!=pInfo.mediaLines.end();i++)
            {
                tempMedia+=(*i);
                tempMedia+="\\0";
            }
        }
        CurrentMedia.addText( tempMedia.c_str() );

        data.addChild(PSM);
        data.addChild(CurrentMedia);

        char *payload1=data.createXMLString(false);

        std::string payload(payload1);
        free(payload1);
        std::ostringstream buf_;  
        buf_ << "UUX " << this->trID++ << " " << payload.length() << "\r\n";
        buf_ << payload;
        write(buf_);
    }

    void NotificationServerConnection::blockContact(Passport buddyName)
    {
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->removeContactFromList(buddyName,LST_AL);

        soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->addContactToList(buddyName,LST_BL);
    }

    void NotificationServerConnection::unblockContact(Passport buddyName)
    {
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->removeContactFromList(buddyName,LST_BL);

        soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->addContactToList(buddyName,LST_AL);
    }

    void NotificationServerConnection::addToAddressBook(Passport buddyName, std::string displayName)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->addContactToAddressBook(buddyName, displayName);
    }

    void NotificationServerConnection::enableContactOnAddressBook(std::string contactId, std::string passport)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->enableContactOnAddressBook(contactId, passport, this->myDisplayName);
    }

    void NotificationServerConnection::disableContactOnAddressBook(std::string contactId, std::string passport)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->disableContactFromAddressBook(contactId,passport);
    }

    void NotificationServerConnection::delFromAddressBook(std::string contactId, std::string passport)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        std::vector<std::string> passport2 = splitString(passport, "@");
        std::string user = passport2[0];
        std::string domain = passport2[1];

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->delContactFromAddressBook(contactId,passport);
    }

    void NotificationServerConnection::addToList(MSN::ContactList list, Passport buddyName)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->addContactToList(buddyName,list);
    }
    
    void NotificationServerConnection::removeFromList(MSN::ContactList list, Passport buddyName)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->removeContactFromList(buddyName,list);
    }
    
    void NotificationServerConnection::addToGroup(std::string groupId, std::string contactId)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->addContactToGroup(groupId, contactId);
    }
    
    void NotificationServerConnection::removeFromGroup(std::string groupId, std::string contactId)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->delContactFromGroup(groupId, contactId);
    }
    
    void NotificationServerConnection::addGroup(std::string groupName)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->addGroup(groupName);
    }
    
    void NotificationServerConnection::removeGroup(std::string groupID)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->delGroup(groupID);
    }
    
    void NotificationServerConnection::renameGroup(std::string groupID, std::string newGroupName)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->renameGroup(groupID, newGroupName);
    }
    
    void NotificationServerConnection::synchronizeContactList(std::string lastChange)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        this->assertConnectionStateIsNot(NS_SYNCHRONISING);

        // we are synchronizing through soap requests now
        this->setConnectionState(NS_SYNCHRONISING);

        listInfo = new ListSyncInfo(lastChange);
        if(!listInfo)
            return; // TODO - raise an error

        if(!lastChange.length()) lastChange = "0";

        listInfo->lastChange = lastChange;

        Soap *soapConnection;
        soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->getLists(listInfo);
    }

    void NotificationServerConnection::gotLists(Soap &soapConnection)
    {
        if(!listInfo)
            return; // TODO - raise an error

        Soap *soapConnection1;
        soapConnection1 = new Soap(*this, this->sitesToAuthList);

        // ask for Address book
        soapConnection1->getAddressBook(this->listInfo);
    }

    void NotificationServerConnection::gotAddressBook(Soap &soapConnection)
    {
        // TODO - sorry, I dont have choice. I need this here because the initial setFriendlyName
        // is called by handle_ADL(), which does not have access to info variable.
        this->myDisplayName = listInfo->myDisplayName;

        std::ostringstream buf_;

        // TODO - see what is the user choice: BL or AL
        // A value of 'AL' indicates that users that are neither on the client's Allow List or Buddy List will be allowed to see the client's online status and open a switchboard session with the client. A value of 'BL' indicates that these users will see the client as offline and will not be allowed to open a switchboard session.
        // http://msnpiki.msnfanatic.com/index.php/Command:BLP

        buf_ << "BLP " << this->trID << " " << this->bplSetting << "L\r\n";
        if (write(buf_) != buf_.str().size())
            return;

        this->addCallback(&NotificationServerConnection::callback_initialBPL, this->trID++, (void *)NULL);
    }
    
    void NotificationServerConnection::callback_initialBPL(std::vector<std::string> & args, int trid, void *data)
    {
        this->assertConnectionStateIs(NS_SYNCHRONISING);
        this->removeCallback(trid);

        this->myNotificationServer()->externalCallbacks.gotBuddyListInfo(this, this->listInfo);
        delete this->listInfo;
    }

    void NotificationServerConnection::completeConnection(std::map<std::string, int > & allContacts, void *info)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);

        // FIXME - handle the errors
        std::map<std::string, std::vector<std::string> > domains;

        std::string tempADL;

        // contains which number is. (i.e. 1 for FL, 2 for AL, 4 for BL) or the sum
        std::map<std::string, int> tempList;

        std::map<std::string,int>::iterator i;
        for (i = allContacts.begin(); i != allContacts.end(); i++)
        {
             std::vector<std::string> parts = splitString((*i).first,"@");
             if(tempList[(*i).first]==0)
                domains[parts[1]].push_back(parts[0]);

             // it does not allow LST_AL and LST_BL, as the server will refuse with a 241 error
             int privacyListMask = MSN::LST_AL | MSN::LST_BL;
             if ( ((*i).second & privacyListMask) == privacyListMask )
                tempList[(*i).first] = (*i).second & ~MSN::LST_AL;
             else
                tempList[(*i).first] = (*i).second;
        }

        // deleting buddy information
        std::map<std::string, MSN::Buddy *>::iterator d = listInfo->contactList.begin();
        for(d; d != listInfo->contactList.end(); d++)
        {
            delete (*d).second;
        }

        // adding domains and users to xml:
        // The max payload of ADL command is about 7500 bytes.
        // so the code below should do that.
        // not using xmlParser due to the complex algorithm
        // TODO - What to do when there are no contacts?
        std::map<std::string, std::vector<std::string> >::iterator cur = domains.begin();

        tempADL+= "<ml l=\"1\">";
        // for each domain
        for(; cur != domains.end(); cur++)
        {
            tempADL += "<d n=\"" + (*cur).first + "\">"; 
            // for each user
            while(domains[(*cur).first].size()!=0)
            {
                std::string a((*cur).second[0]+"@"+(*cur).first);
                tempADL += "<c n=\"" + (*cur).second[0] + "\" l=\""+toStr(tempList[a]) +"\" t=\"1\"/>";
                (*cur).second.erase((*cur).second.begin());
                if(tempADL.length()>7400)
                {
                    // it means that there are more contacts from this domain
                    if(domains[(*cur).first].size()!=0)
                    {
                        tempADL += "</d></ml>";
                        adl_packets.push_back(tempADL);
                        tempADL="";
                        tempADL += "<ml l=\"1\">";
                        tempADL += "<d n=\"" + (*cur).first + "\">";
                        continue;
                    }
                }
            }
            tempADL += "</d>";
        }
        tempADL += "</ml>";
        adl_packets.push_back(tempADL);

        // send the first one
        std::string adl_payload = adl_packets.front();
        adl_packets.pop_front();

        std::ostringstream buf_;
        buf_ << "ADL " << this->trID++ << " " << adl_payload.length() << "\r\n";
        buf_ << adl_payload;
        if (write(buf_) != buf_.str().size())
            return;
    }
 

    void NotificationServerConnection::sendPing()
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        std::string a("PNG\r\n");
        write(a);
    }

    void NotificationServerConnection::requestSwitchboardConnection(const void *tag)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        SwitchboardServerConnection::AuthData *auth = new SwitchboardServerConnection::AuthData(this->auth.username, tag);
        std::ostringstream buf_;
        buf_ << "XFR " << this->trID << " SB\r\n";
        if (write(buf_) != buf_.str().size())
            return;

        this->addCallback(&NotificationServerConnection::callback_TransferToSwitchboard, this->trID++, (void *)auth);
    }
    
    template <class _Tp>
    class _sameUserName
    {
        Buddy buddy;
public:
        _sameUserName(const _Tp &__u) : buddy(__u) {};
        bool operator()(const _Tp &__x) { return __x.userName == buddy.userName; }
    };
    
    void NotificationServerConnection::socketConnectionCompleted()
    {
        this->assertConnectionStateIs(NS_CONNECTING);
        this->setConnectionState(NS_CONNECTED);
        
        Connection::socketConnectionCompleted();
        
        // If an error occurs in Connection::socketConnectionCompleted, we
        // will be disconnected before we get here.
        if (this->connectionState() != NS_DISCONNECTED)
        {
            this->myNotificationServer()->externalCallbacks.unregisterSocket(this->sock);
            this->myNotificationServer()->externalCallbacks.registerSocket(this->sock, 1, 0, false);
        }
    }
    
    void NotificationServerConnection::connect(const std::string & hostname, unsigned int port)
    {
        this->assertConnectionStateIs(NS_DISCONNECTED);
        connectinfo *info = new connectinfo(this->auth.username, this->auth.password);
        this->info = info;
        
        if ((this->sock = this->myNotificationServer()->externalCallbacks.connectToServer(hostname, port, &this->connected)) == NULL)
        {
            this->myNotificationServer()->externalCallbacks.showError(this, "Could not connect to MSN server");
            this->myNotificationServer()->externalCallbacks.closingConnection(this);
            return;
        }
        this->setConnectionState(NS_CONNECTING);
        this->myNotificationServer()->externalCallbacks.registerSocket(this->sock, 0, 1, false);
        if (this->connected)
            this->socketConnectionCompleted();
        
        std::ostringstream buf_;
        buf_ << "VER " << this->trID << " MSNP15 CVR0\r\n";
        if (this->write(buf_) != buf_.str().size())
            return;
        
        this->addCallback(&NotificationServerConnection::callback_NegotiateCVR, this->trID++, (void *)info);
    }
    
    void NotificationServerConnection::connect(const std::string & hostname, unsigned int port, const Passport & username, const std::string & password)
    {
        this->auth.username = username;
        this->auth.password = password;
        this->connect(hostname, port);
    }
    
    void NotificationServerConnection::disconnect()
    {
        this->assertConnectionStateIsNot(NS_DISCONNECTED);

        std::vector<SwitchboardServerConnection *> list = _switchboardConnections;
        std::vector<SwitchboardServerConnection *>::iterator i = list.begin();
        for (; i != list.end(); ++i)
        {
            delete *i;
        }
        std::vector<Soap *> list2 = _SoapConnections;
        std::vector<Soap *>::iterator d = list2.begin();

        for (; d != list2.end(); ++d)
        {
             delete *d;
        }

        this->callbacks.clear();
        this->sitesToAuthList.erase(sitesToAuthList.begin(), sitesToAuthList.end());
        SentQueuedOIMs.erase(SentQueuedOIMs.begin(), SentQueuedOIMs.end());
        this->setConnectionState(NS_DISCONNECTED);
        this->myNotificationServer()->externalCallbacks.closingConnection(this);
        Connection::disconnect();
    }
    
    void NotificationServerConnection::disconnectForTransfer()
    {
        this->assertConnectionStateIsNot(NS_DISCONNECTED);
        this->myNotificationServer()->externalCallbacks.unregisterSocket(this->sock);
        this->myNotificationServer()->externalCallbacks.closeSocket(this->sock);
        this->setConnectionState(NS_DISCONNECTED);
    }
    
    void NotificationServerConnection::handleIncomingData()
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        while (this->isWholeLineAvailable())
        {
            std::vector<std::string> args = this->getLine();
            if(!args.size()) continue;
            if (args[0] == "MSG" || args[0] == "NOT" || 
                args[0] == "IPG" || args[0] == "GCF" || 
                args[0] == "UBX" || args[0] == "ADL" ||
                args[0] == "RML")
            {
                int dataLength;
                if (args[0] == "MSG" || args[0] == "UBX")
                    dataLength = decimalFromString(args[3]);
                else if(args[0] == "GCF" || args[0] == "ADL" || args[0] == "RML")
                    dataLength = decimalFromString(args[2]);
                else
                    dataLength = decimalFromString(args[1]);

                if (this->readBuffer.find("\r\n") + 2 + dataLength > this->readBuffer.size())
                    return;
            }
            this->readBuffer = this->readBuffer.substr(this->readBuffer.find("\r\n") + 2);
            int trid = 0;
            
            if (args.size() >= 6 && args[0] == "XFR" && args[2] == "NS")
            {
                // XFR TrID NS NotificationServerIP:Port 0 ThisServerIP:Port
                //  0    1  2             3              4        5    
                this->callbacks.clear(); // delete the callback data
                
                this->disconnectForTransfer();
                
                std::pair<std::string, int> server_address = splitServerAddress(args[3]);
                this->connect(server_address.first, server_address.second);
                return;
            }
            
            if (args.size() >= 7 && args[0] == "RNG")
            {
                // RNG SessionID SwitchboardServerIP:Port CKI AuthString InvitingUser InvitingDisplayName
                // 0      1                 2              3       4           5             6
                this->handle_RNG(args);
                return;
            }

            if (args.size() >= 2 && args[0] == "QNG")
            {
                // QNG seconds
                //  0   1
                
                // ping response, ignore
                return;
            }
            
            if ((args.size() >= 3 && args[0] == "LST" ) ||
                (args.size() >= 2 && (args[0] == "GTC" )) ||
                (args.size() >= 3 && (args[0] == "BPR" || args[0] == "LSG" ))
           )
            {
                // LST N=UserName F=FriendlyName C=GUID param groupID
                //  0      1          2          3      4      5
                //
                // or
                // (GTC|BLP) [TrID] [ListVersion] Setting
                //     0        1        2          4
                
                if (this->synctrid)
                {
                    trid = this->synctrid;
                }
                else
                {
                    trid = decimalFromString(args[1]);
                }
            } 
            else if (args.size() > 1)
            {
                try
                {
                    trid = decimalFromString(args[1]);
                }
                catch (...)
                {
                }
            }
            
            if (!this->callbacks.empty() && trid >= 0)
            {
                if (this->callbacks.find(trid) != this->callbacks.end())
                {
                    (this->*(this->callbacks[trid].first))(args, trid, this->callbacks[trid].second);
                    continue;
                }
            }
            
            if (isdigit(args[0][0]))
                this->showError(decimalFromString(args[0]));
            else
                this->dispatchCommand(args);
        }
    }
    
    void NotificationServerConnection::callback_NegotiateCVR(std::vector<std::string> & args, int trid, void *data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        connectinfo * info = (connectinfo *) data;
        this->removeCallback(trid);
        if (args.size() >= 3 && args[0] != "VER" || args[2] != "MSNP15") // if either *differs*...
        {
            this->myNotificationServer()->externalCallbacks.showError(NULL, "Protocol negotiation failed");
            this->disconnect();
            return;
        }

        std::ostringstream buf_;
        buf_ << "CVR " << this->trID << " 0x0409 winnt 5.1 i386 MSG80BETA 8.1.0178.00 MSMSGS " << info->username << "\r\n";
        if (this->write(buf_) != buf_.str().size())
            return;
        this->addCallback(&NotificationServerConnection::callback_RequestUSR, this->trID++, (void *) data);
    }
    
    void NotificationServerConnection::callback_TransferToSwitchboard(std::vector<std::string> & args, int trid, void *data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        SwitchboardServerConnection::AuthData *auth = static_cast<SwitchboardServerConnection::AuthData *>(data);
        this->removeCallback(trid);
        
        if (args[0] != "XFR")
        {
            this->showError(decimalFromString(args[0]));
            this->disconnect();
            delete auth;
            return;
        }
        
        auth->cookie = args[5];
        auth->sessionID = "";
        
        SwitchboardServerConnection *newconn = new SwitchboardServerConnection(*auth, *this);
        
        this->addSwitchboardConnection(newconn);
        std::pair<std::string, int> server_address = splitServerAddress(args[3]);
        newconn->connect(server_address.first, server_address.second);
        
        delete auth;
    }
    
    void NotificationServerConnection::callback_RequestUSR(std::vector<std::string> & args, int trid, void *data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        connectinfo *info = (connectinfo *)data;
        this->removeCallback(trid);
        
        if (args.size() > 1 && args[0] != "CVR") // if*differs*...
        {
            this->myNotificationServer()->externalCallbacks.showError(NULL, "Protocol negotiation failed");
            this->disconnect();
            return;
        }
        
        std::ostringstream buf_;
        buf_ << "USR " << this->trID << " SSO I " << info->username << "\r\n";
        if (this->write(buf_) != buf_.str().size())
            return;
        
        this->addCallback(&NotificationServerConnection::callback_PassportAuthentication, this->trID++, (void *) data);
    }    
    
    void NotificationServerConnection::callback_PassportAuthentication(std::vector<std::string> & args, int trid, void * data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        connectinfo * info;
        
        info=(connectinfo *)data;
        this->removeCallback(trid);

        if (isdigit(args[0][0]))
        {
            this->showError(decimalFromString(args[0]));
            this->disconnect();
            return;
        }
        
        if (args.size() >= 4 && args[4].empty()) {
            this->disconnect();
            return;
        }
        
        this->myNotificationServer()->externalCallbacks.getSecureHTTPProxy();

        Soap *soapConnection = new Soap(*this);

        this->mdi = args[5];
        soapConnection->setMBI(args[4]);
        soapConnection->getTickets(info->username,info->password,args[4]);
        delete info;
        info=NULL;
    }

    void NotificationServerConnection::gotTickets(Soap & soapConnection, std::vector<MSN::Soap::sitesToAuth> sitesToAuthList)
    {
        std::ostringstream buf_;
        this->sitesToAuthList = sitesToAuthList;
        std::string token = sitesToAuthList[1].BinarySecurityToken;
        std::string binarysecret = sitesToAuthList[1].BinarySecret;
        this->token = token;

        buf_ << "USR " << this->trID << " SSO S " <<  token << " " << mdi_encrypt(binarysecret, mdi) << "\r\n";
        if (this->write(buf_) != buf_.str().size())
            return;
        this->addCallback(&NotificationServerConnection::callback_AuthenticationComplete, this->trID++, NULL);
    }
    
    void NotificationServerConnection::callback_AuthenticationComplete(std::vector<std::string> & args, int trid, void * data)
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        this->removeCallback(trid);

        if (isdigit(args[0][0]))
        {
            this->showError(decimalFromString(args[0]));
            this->disconnect();
            return;
        }
        server_email_verified = args[4];
    }   

    void NotificationServerConnection::get_oim(std::string id, bool markAsRead)
    {
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

        soapConnection->getOIM(id,markAsRead);
    }

    void NotificationServerConnection::delete_oim(std::string id)
    {
        if(this->removingOIM)
        {
            DeletedQueuedOIMs.push_back(id);
            return;
        }
        this->removingOIM = true;
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        soapConnection->deleteOIM(id);
    }

    void NotificationServerConnection::send_oim(Soap::OIM oim)
    {
        // do not generate two lockkeys at the same time
        if(this->generatingLockkey)
        {
            SentQueuedOIMs.push_back(oim);
            return;
        }
        Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
        SentQueuedOIMs.push_back(oim);
        this->generatingLockkey=true;
        soapConnection->generateLockkey(oim);
    }

    void NotificationServerConnection::gotOIM(Soap & soapConnection, bool success, std::string id, std::string message)
    {
        this->myNotificationServer()->externalCallbacks.gotOIM(this, success, id, message);
    }

    void NotificationServerConnection::gotOIMLockkey(Soap & soapConnection, std::string lockkey)
    {
        this->lockkey = lockkey;
        this->generatingLockkey = false;
        if(this->lockkey.empty())
        {
            std::vector<Soap::OIM>::iterator i = SentQueuedOIMs.begin();
            for(; i != SentQueuedOIMs.end(); i++)
            {
                this->myNotificationServer()->externalCallbacks.gotOIMSendConfirmation(this, false, (*i).id);
            }
            SentQueuedOIMs.erase(SentQueuedOIMs.begin(), SentQueuedOIMs.end());
            return;
        }
        sendQueuedOIMs();
    }

    void NotificationServerConnection::gotOIMDeleteConfirmation(Soap & soapConnection, std::string id, bool deleted)
    {
        this->myNotificationServer()->externalCallbacks.gotOIMDeleteConfirmation(this, deleted, id);
        if(this->DeletedQueuedOIMs.empty())
        {
            removingOIM = false;
            return;
        }
        else
        {
            Soap *soapConnection = new Soap(*this, this->sitesToAuthList);
            soapConnection->deleteOIM(DeletedQueuedOIMs.back());
            DeletedQueuedOIMs.pop_back();
        }
    }

    void NotificationServerConnection::gotOIMSendConfirmation(Soap & soapConnection, int id, bool sent)
    {
        if(!sent)
            this->lockkey.clear();

        this->myNotificationServer()->externalCallbacks.gotOIMSendConfirmation(this, sent, id);
    }

    void NotificationServerConnection::sendQueuedOIMs()
    {
        std::vector<Soap::OIM>::iterator i = SentQueuedOIMs.begin();
        for(; i != SentQueuedOIMs.end(); i++)
        {
            Soap *soapConnection = new Soap(*this, this->sitesToAuthList);

            soapConnection->sendOIM((*i), this->lockkey);
        }
        SentQueuedOIMs.erase(SentQueuedOIMs.begin(), SentQueuedOIMs.end());
    }

    bool NotificationServerConnection::change_DisplayPicture(std::string filename)
    {
        msnobj.delMSNObjectByType(3);
        if(!filename.empty())
            msnobj.addMSNObject(filename,3);

        return true;
    }

    void NotificationServerConnection::gotChangeDisplayNameConfirmation(Soap & soapConnection, std::string displayName, bool changed)
    {
        if(changed)
        {
            this->myDisplayName = displayName;
            // server update OK, now change to the current session
            std::ostringstream buf_;
            buf_ << "PRP " << this->trID++ << " MFN "  << encodeURL(displayName) << "\r\n";
            write(buf_);
        }
        // TODO - raise an error if not changed
    }

    void NotificationServerConnection::gotAddContactToGroupConfirmation(Soap & soapConnection, bool added, std::string newVersion, std::string groupId, std::string contactId)
    {
        this->myNotificationServer()->externalCallbacks.addedContactToGroup(this, added, groupId, contactId);
    }

    void NotificationServerConnection::gotDelContactFromGroupConfirmation(Soap & soapConnection, bool removed, std::string newVersion, std::string groupId, std::string contactId)
    {
        this->myNotificationServer()->externalCallbacks.removedContactFromGroup(this, removed, groupId, contactId);
    }

    void NotificationServerConnection::gotAddGroupConfirmation(Soap & soapConnection, bool added, std::string newVersion, std::string groupName, std::string groupId)
    {
        this->myNotificationServer()->externalCallbacks.addedGroup(this, added, groupName, groupId);
    }

    void NotificationServerConnection::gotDelGroupConfirmation(Soap & soapConnection, bool removed, std::string newVersion, std::string groupId)
    {
        this->myNotificationServer()->externalCallbacks.removedGroup(this, removed, groupId);
    }
    void NotificationServerConnection::gotRenameGroupConfirmation(Soap & soapConnection, bool renamed, std::string newVersion, std::string newGroupName, std::string groupId)
    {
        this->myNotificationServer()->externalCallbacks.renamedGroup(this, renamed, newGroupName, groupId);
    }

    void NotificationServerConnection::gotAddContactToAddressBookConfirmation(Soap & soapConnection, bool added, std::string newVersion, std::string passport, std::string displayName, std::string guid)
    {
        this->myNotificationServer()->externalCallbacks.addedContactToAddressBook(this, added, passport, displayName, guid);
        if(added)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];

            // TODO - use xmlParser
            std::string payload3("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\"2\" t=\"1\"/></d></ml>");
            std::ostringstream buf_3;
            buf_3 << "ADL " << this->trID++ << " " << payload3.length() << "\r\n";
            buf_3 << payload3;
            write(buf_3);

            std::string payload2("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\"1\" t=\"1\"/></d></ml>");
            std::ostringstream buf_2;
            buf_2 << "ADL " << this->trID++ << " " << payload2.length() << "\r\n";
            buf_2 << payload2;
            write(buf_2);

            // the official client sends FQY
            std::string payload4("<ml l=\"2\"><d n=\"" + domain  + "\"><c n=\"" + user+ "\"/></d></ml>");
            std::ostringstream buf_4;
            buf_4 << "FQY " << this->trID++ << " " << payload4.length() << "\r\n";
            buf_4 << payload4;
            write(buf_4);
        }
    }

    void NotificationServerConnection::gotDelContactFromAddressBookConfirmation(Soap & soapConnection, bool removed, std::string newVersion, std::string contactId, std::string passport)
    {
        this->myNotificationServer()->externalCallbacks.removedContactFromAddressBook(this, removed, contactId, passport);
        if(removed)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];

            // TODO - use xmlParser
            std::string payload2("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\"1\" t=\"1\"/></d></ml>");
            std::ostringstream buf_2;
            buf_2 << "RML " << this->trID++ << " " << payload2.length() << "\r\n";
            buf_2 << payload2;
            write(buf_2);

        }
    }

    void NotificationServerConnection::gotEnableContactOnAddressBookConfirmation(Soap & soapConnection, bool disabled, std::string newVersion, std::string contactId, std::string passport)
    {
        this->myNotificationServer()->externalCallbacks.enabledContactOnAddressBook(this, disabled, contactId, passport);
        if(disabled)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];

            // TODO - use xmlParser
            std::string payload3("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\"1\" t=\"1\"/></d></ml>");
            std::ostringstream buf_3;
            buf_3 << "ADL " << this->trID++ << " " << payload3.length() << "\r\n";
            buf_3 << payload3;
            write(buf_3);
        }
    }

    void NotificationServerConnection::gotDisableContactOnAddressBookConfirmation(Soap & soapConnection, bool disabled, std::string newVersion, std::string contactId, std::string passport)
    {
        this->myNotificationServer()->externalCallbacks.disabledContactOnAddressBook(this, disabled, contactId);
        if(disabled)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];

            // TODO - use xmlParser
            std::string payload2("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\"1\" t=\"1\"/></d></ml>");
            std::ostringstream buf_2;
            buf_2 << "RML " << this->trID++ << " " << payload2.length() << "\r\n";
            buf_2 << payload2;
            write(buf_2);
        }
    }

    void NotificationServerConnection::gotAddContactToListConfirmation(Soap & soapConnection, bool added, std::string newVersion, std::string passport, MSN::ContactList list)
    {
        if(added)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];

            // TODO - use xmlParser
            std::string payload2("<ml><d n=\"" + domain  + "\"><c n=\"" + user+ "\" l=\""+toStr(list)+"\" t=\"1\"/></d></ml>");
            std::ostringstream buf_2;
            buf_2 << "ADL " << this->trID++ << " " << payload2.length() << "\r\n";
            buf_2 << payload2;
            write(buf_2);
            this->myNotificationServer()->externalCallbacks.addedListEntry(this, list, passport, "");
        }
    }
    void NotificationServerConnection::gotDelContactFromListConfirmation(Soap & soapConnection, bool deleted, std::string newVersion, std::string passport, MSN::ContactList list)
    {
        if(deleted)
        {
            std::vector<std::string> passport2 = splitString(passport, "@");
            std::string user = passport2[0];
            std::string domain = passport2[1];
            // TODO - use XMLParser
            std::string payload("<ml><d n=\""+ domain + "\"><c n=\"" + user + "\" l=\""+toStr(list)+"\" t=\"1\"/></d></ml>");

            std::ostringstream buf_;  
            buf_ << "RML " << this->trID++ << " " << payload.length() << "\r\n";
            buf_ << payload;
            write(buf_);
            this->myNotificationServer()->externalCallbacks.removedListEntry(this, list, passport);
        }
    }

    void NotificationServerConnection::setCapabilities(uint m_clientId)
    {
        this->m_clientId = m_clientId;
    }

    void NotificationServerConnection::getInboxUrl()
    {
        this->assertConnectionStateIsAtLeast(NS_CONNECTED);
        std::ostringstream buf_;  
        buf_ << "URL " << this->trID << " INBOX\r\n";
        write(buf_);
        this->addCallback(&NotificationServerConnection::callback_URL, this->trID++, NULL);
    }
}
