/***************************************************************************
                          icqmessage.cpp  -  description
                             -------------------
    begin                : Sun Mar 17 2002
    copyright            : (C) 2002 by Vladimir Shutoff
    email                : vovan@shutoff.ru
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <qtimer.h>
#include <qlayout.h>
#include <qtextcodec.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qimage.h>
#include <qregexp.h>

#include "toolbtn.h"
#include "log.h"

#include "icqmessage.h"
#include "icqclient.h"
#include "icq.h"
#include "core.h"
#include "xml.h"

using namespace std;
using namespace SIM;

static DataDef aimFileMessageData[] =
    {
        { "", DATA_ULONG, 1, 0 },				// Port
        { "", DATA_ULONG, 1, 0 },				// ID_L
        { "", DATA_ULONG, 1, 0 },				// ID_H
        { NULL, DATA_UNKNOWN, 0, 0 }
    };

AIMFileMessage::AIMFileMessage(Buffer *cfg)
        : FileMessage(MessageFile, cfg)
{
    load_data(aimFileMessageData, &data, cfg);
}

AIMFileMessage::~AIMFileMessage()
{
    free_data(aimFileMessageData, &data);
}

static DataDef icqFileMessageData[] =
    {
        { "ServerDescr", DATA_STRING, 1, 0 },
        { "", DATA_ULONG, 1, 0 },				// IP
        { "", DATA_ULONG, 1, 0 },				// Port
        { "", DATA_ULONG, 1, 0 },				// ID_L
        { "", DATA_ULONG, 1, 0 },				// ID_H
        { "", DATA_ULONG, 1, 0 },				// Cookie
        { "", DATA_ULONG, 1, 0 },				// Extended
        { NULL, DATA_UNKNOWN, 0, 0 }
    };

ICQFileMessage::ICQFileMessage(Buffer *cfg)
        : FileMessage(MessageICQFile, cfg)
{
    load_data(icqFileMessageData, &data, cfg);
}

ICQFileMessage::~ICQFileMessage()
{
    free_data(icqFileMessageData, &data);
}

QString ICQFileMessage::getDescription()
{
    QString serverText = getServerDescr();
    if (serverText.isEmpty())
        return FileMessage::getDescription();
    return serverText;
}

QCString ICQFileMessage::save()
{
    QCString s = FileMessage::save();
    QCString s1 = save_data(icqFileMessageData, &data);
    if (!s1.isEmpty()){
        if (!s.isEmpty())
            s += '\n';
        s += s1;
    }
    return s;
}

IcqContactsMessage::IcqContactsMessage(Buffer *cfg)
        : ContactsMessage(MessageICQContacts, cfg)
{
}

IcqContactsMessage::~IcqContactsMessage()
{
}

QString IcqContactsMessage::getContacts() const
{
    QCString serverText = getServerText();
    if (serverText.isEmpty())
        return ContactsMessage::getContacts();
    return serverText;  // this is wrong ... but I'm currently unsure what'ssaved in there
}

static DataDef icqAuthMessageData[] =
    {
        { "Charset", DATA_STRING, 1, 0 },
        { NULL, DATA_UNKNOWN, 0, 0 }
    };

ICQAuthMessage::ICQAuthMessage(unsigned type, unsigned baseType, Buffer *cfg)
        : AuthMessage(type, cfg)
{
    load_data(icqAuthMessageData, &data, cfg);
    m_baseType = baseType;
}

ICQAuthMessage::~ICQAuthMessage()
{
    free_data(icqAuthMessageData, &data);
}

QString ICQAuthMessage::getText() const
{
    QCString serverText = getServerText();
    if (serverText.isEmpty())
        return Message::getText();
    QString charset = getCharset();
    if (!charset.isEmpty()){
        QTextCodec *codec = QTextCodec::codecForName(charset);
        if (codec)
            return codec->toUnicode(serverText);
    }
    return Message::getText();
}

QCString ICQAuthMessage::save()
{
    QCString s = Message::save();
    QCString s1 = save_data(icqAuthMessageData, &data);
    if (!s1.isEmpty()){
        if (!s.isEmpty())
            s += '\n';
        s += s1;
    }
    return s;
}

static bool h2b(const char *&p, char &r)
{
    char c = *(p++);
    if ((c >= '0') && (c <= '9')){
        r = (char)(c - '0');
        return true;
    }
    if ((c >= 'A') && (c <= 'F')){
        r = (char)(c - 'A' + 10);
        return true;
    }
    return false;
}

static bool h2b(const char *&p, QCString &cap)
{
    char r1, r2;
    if (h2b(p, r1) && h2b(p, r2)){
        cap += (char)((r1 << 4) + r2);
        return true;
    }
    return false;
}

static bool parseFE(QCString str, QValueList<QCString> &l, unsigned n)
{
    int idx = str.find('\xFE');
    while(idx != -1) {
        l += str.left(idx);
        str = str.mid(idx+1);
        idx = str.find('\xFE');
    }
    l += str;
    for( unsigned i = l.count(); i < n; i++ )
        l += QCString();
    return true;
}

static Message *parseTextMessage(const QCString &str, const QCString &_pp, Contact *contact)
{
    if (str.isEmpty())
        return NULL;
    log(L_DEBUG, "Text message: %s %s", str.data(), _pp.data());
    if (_pp.length() == 38){
        QCString cap;
        const char *pp = _pp.data();
        if ((*(pp++) == '{') &&
                h2b(pp, cap) && h2b(pp, cap) && h2b(pp, cap) && h2b(pp, cap) &&
                (*(pp++) == '-') &&
                h2b(pp, cap) && h2b(pp, cap) &&
                (*(pp++) == '-') &&
                h2b(pp, cap) && h2b(pp, cap) &&
                (*(pp++) == '-') &&
                h2b(pp, cap) && h2b(pp, cap) &&
                (*(pp++) == '-') &&
                h2b(pp, cap) && h2b(pp, cap) && h2b(pp, cap) && h2b(pp, cap) &&
                h2b(pp, cap) && h2b(pp, cap) &&
                (*(pp++) == '}')){
            const char *unpack_cap = cap.data();
            if (!memcmp(unpack_cap, ICQClient::capabilities[CAP_RTF], sizeof(capability))){
                Message *msg = new Message(MessageGeneric);
                QString text;
                if (ICQClient::parseRTF(str, contact, text))
                    msg->setFlags(MESSAGE_RICHTEXT);
                log(L_DEBUG, "Msg: %s", str.data());
                msg->setText(text);
                return msg;
            }
            if (!memcmp(unpack_cap, ICQClient::capabilities[CAP_UTF], sizeof(capability))){
                Message *msg = new Message(MessageGeneric);
                msg->setText(QString::fromUtf8(str));
                return msg;
            }
        }
    }
    Message *m = new Message;
    m->setServerText(str);
    return m;
}

static Message *parseURLMessage(const QCString &str)
{
    QValueList<QCString> l;
    if (!parseFE(str, l, 2)){
        log(L_WARN, "Parse error URL message");
        return NULL;
    }
    UrlMessage *m = new UrlMessage;
    m->setServerText(l[0]);
    m->setUrl(l[1]);
    return m;
}

static Message *parseContactMessage(const QCString &str)
{
    QValueList<QCString> l;
    if (!parseFE(str, l, 2)){
        log(L_WARN, "Parse error contacts message");
        return NULL;
    }
    unsigned nContacts = l[0].toUInt();
    if (nContacts == 0){
        log(L_WARN, "No contacts found");
        return NULL;
    }
    QValueList<QCString> c;
    if (!parseFE(l[1], c, nContacts*2+1)){
        log(L_WARN, "Parse error contacts message");
        return NULL;
    }
    QCString serverText;
    for (unsigned i = 0; i < nContacts; i++){
        QCString screen = c[i*2];
        QCString alias  = c[i*2+1];
        if (!serverText.isEmpty())
            serverText += ';';
        if (screen.toULong()){
            serverText += "icq:";
            serverText += screen;
            serverText += '/';
            serverText += alias;
            serverText += ',';
            if (screen == alias){
                serverText += "ICQ ";
                serverText += screen;
            }else{
                serverText += alias;
                serverText += " (ICQ ";
                serverText += screen;
                serverText += ')';
            }
        }else{
            serverText += "aim:";
            serverText += screen;
            serverText += '/';
            serverText += alias;
            serverText += ',';
            if (screen == alias){
                serverText += "AIM ";
                serverText += screen;
            }else{
                serverText += alias;
                serverText += " (AIM ";
                serverText += screen;
                serverText += ')';
            }
        }
    }
    IcqContactsMessage *m = new IcqContactsMessage;
    m->setServerText(serverText);
    return m;
}

static Message *parseAuthRequest(const QCString &str)
{
    QValueList<QCString> l;
    if (!parseFE(str, l, 6)){
        log(L_WARN, "Parse error auth request message");
        return NULL;
    }
    ICQAuthMessage *m = new ICQAuthMessage(MessageICQAuthRequest, MessageAuthRequest);
    m->setServerText(l[4]);
    return m;
}

Message *ICQClient::parseExtendedMessage(const QString &screen, ICQBuffer &packet, MessageId &id, unsigned cookie)
{
    QCString header;
    packet >> header;
    ICQBuffer h(header.size());
    h.pack(header, header.size());
    h.incReadPos(16);
    unsigned short msg_type;
    h >> msg_type;
    QCString msgType;
    h.unpackStr32(msgType);
    QCString info;
    packet.unpackStr32(info);
    ICQBuffer b(info.size());
    b.pack(info, info.size());

    log(L_DEBUG, "Extended message %s [%04X] %u", msgType.data(), msg_type, info.size());

    int n = msgType.find("URL");
    if (n >= 0){
        QCString info;
        b.unpackStr32(info);
        return parseURLMessage(info);
    }
    if (msgType == "Request For Contacts"){
        QCString info;
        b.unpackStr32(info);
        ICQAuthMessage *m = new ICQAuthMessage(MessageContactRequest, MessageContactRequest);
        m->setServerText(info);
        return m;
    }
    if (msgType == "Contacts"){
        QCString p;
        b.unpackStr32(p);
        return parseContactMessage(p);
    }
    if (msgType == "Message"){
        QCString p;
        b.unpackStr32(p);
        unsigned long forecolor, backcolor;
        b >> forecolor >> backcolor;
        QCString cap_str;
        b.unpackStr32(cap_str);
        Contact *contact;
        ICQUserData *data = findContact(screen, NULL, false, contact);
        if (data == NULL) {
            data = findContact(screen, NULL, true, contact);
            if (data == NULL) {
               return NULL;
	    }
            contact->setFlags(contact->getFlags() | CONTACT_TEMP);
        }
        Message *msg = parseTextMessage(p, cap_str, contact);
        if (msg){
            if (forecolor != backcolor){
                msg->setForeground(forecolor >> 8);
                msg->setBackground(backcolor >> 8);
            }
        }
        return msg;
    }
    n = msgType.find("File");
    if (n >= 0){
        QCString fileDescr, fileName;
        b.unpackStr32(fileDescr);
        unsigned short port;
        b >> port;
        b.incReadPos(2);
        b >> fileName;
        unsigned long fileSize;
        b.unpack(fileSize);
        ICQFileMessage *m = new ICQFileMessage;
        m->setServerDescr(fileName);
        m->setServerText(fileDescr);
        m->setSize(fileSize);
        m->setPort(port);
        m->setFlags(MESSAGE_TEMP);
        m->setID_L(id.id_l);
        m->setID_H(id.id_h);
        m->setCookie(cookie);
        m->setExtended(true);
        return m;
    }
    if (msgType == "ICQSMS"){
        string p;
        b.unpackStr32(info);
        p = info;   // FIXME!!
        string::iterator s = p.begin();
        auto_ptr<XmlNode> top(XmlNode::parse(s, p.end()));
        if (top.get() == NULL){
            log(L_WARN, "Parse SMS XML error");
            return NULL;
        }
        if (msg_type == 0){
            if (top->getTag() != "sms_message"){
                log(L_WARN, "No sms_message tag in SMS message");
                return NULL;
            }
            XmlNode *n = top.get();
            if ((n == NULL) || !n->isBranch()){
                log(L_WARN, "Parse no branch");
                return NULL;
            }
            XmlBranch *sms_message = static_cast<XmlBranch*>(n);
            XmlLeaf *text = sms_message->getLeaf("text");
            if (text == NULL){
                log(L_WARN, "No <text> in SMS message");
                return NULL;
            }
            SMSMessage *m = new SMSMessage;
            XmlLeaf *sender = sms_message->getLeaf("sender");
            if (sender != NULL){
                m->setPhone(QString::fromUtf8(sender->getValue().c_str()));
                // string -> QString is ok here since phone doesn't contain non ascii chars
                Contact *contact = getContacts()->contactByPhone(sender->getValue().c_str());
                m->setContact(contact->id());
            }
            XmlLeaf *senders_network = sms_message->getLeaf("senders_network");
            if (senders_network != NULL)
                m->setNetwork(QString::fromUtf8(senders_network->getValue().c_str()));
            m->setText(QString::fromUtf8(text->getValue().c_str()));
            return m;
        }
    }
    if (msgType == "StatusMsgExt"){
        StatusMessage *m = new StatusMessage;
        return m;
    }
    log(L_WARN, "Unknown extended message type %s", msgType.data());
    return NULL;
}

Message *ICQClient::parseMessage(unsigned short type, const QString &screen, const QCString &p, ICQBuffer &packet, MessageId &id, unsigned cookie)
{
    if (screen.toULong() == 0x0A){
        QValueList<QCString> l;
        if (!parseFE(p, l, 6)){
            log(L_WARN, "Parse error web panel message");
            return NULL;
        }
        char SENDER_IP[] = "Sender IP:";
        QCString head = l[5].left(strlen(SENDER_IP));
        Message *msg = new Message((head == SENDER_IP) ? MessageWebPanel : MessageEmailPager);
        QString name = getContacts()->toUnicode(NULL, l[0]);
        QString mail = getContacts()->toUnicode(NULL, l[3]);
        msg->setServerText(l[5]);
        Contact *contact = getContacts()->contactByMail(mail, name);
        if (contact == NULL){
            delete msg;
            return NULL;
        }
        msg->setContact(contact->id());
        return msg;
    }
    log(L_DEBUG, "Parse message [type=%u]", type);
    Message *msg = NULL;
    switch (type){
    case ICQ_MSGxMSG:{
            unsigned long forecolor, backcolor;
            packet >> forecolor >> backcolor;
            QCString cap_str;
            packet.unpackStr32(cap_str);
            Contact *contact;
            ICQUserData *data = findContact(screen, NULL, false, contact);
            if (data == NULL) {
                data = findContact(screen, NULL, true, contact);
                if (data == NULL) {
                   return NULL;
                }
                contact->setFlags(contact->getFlags() | CONTACT_TEMP);
            }
            msg = parseTextMessage(p, cap_str, contact);
            if (msg == NULL)
                break;
            if (forecolor != backcolor){
                msg->setForeground(forecolor >> 8);
                msg->setBackground(backcolor >> 8);
            }
            break;
        }
    case ICQ_MSGxURL:
        msg = parseURLMessage(p);
        break;
    case ICQ_MSGxAUTHxREQUEST:
        msg = parseAuthRequest(p);
        break;
    case ICQ_MSGxAUTHxGRANTED:
        msg = new AuthMessage(MessageAuthGranted);
        break;
    case ICQ_MSGxAUTHxREFUSED:
        msg = new AuthMessage(MessageAuthRefused);
        break;
    case ICQ_MSGxADDEDxTOxLIST:
        msg = new AuthMessage(MessageAdded);
        break;
    case ICQ_MSGxCONTACTxLIST:
        msg = parseContactMessage(p);
        break;
    case ICQ_MSGxFILE:{
            ICQFileMessage *m = new ICQFileMessage;
            m->setServerText(p);
            unsigned short port;
            unsigned long  fileSize;
            QCString fileName;
            packet >> port;
            packet.incReadPos(2);
            packet >> fileName;
            packet.unpack(fileSize);
            m->setPort(port);
            m->setSize(fileSize);
            m->setServerDescr(fileName);
            msg = m;
            break;
        }
    case ICQ_MSGxEXT:
        msg = parseExtendedMessage(screen, packet, id, cookie);
        break;
    default:
        log(L_WARN, "Unknown message type %04X", type);
    }
    return msg;
}

static Message *createIcqFile(Buffer *cfg)
{
    return new ICQFileMessage(cfg);
}

#if 0
i18n("File", "%n files", 1);
#endif

static MessageDef defIcqFile =
    {
        NULL,
        NULL,
        MESSAGE_CHILD,
        "File",
        "%n files",
        createIcqFile,
        NULL,
        NULL
    };

#if 0
i18n("WWW-panel message", "%n WWW-panel messages", 1);
#endif

static Message *createWebPanel(Buffer *cfg)
{
    return new Message(MessageWebPanel, cfg);
}

static MessageDef defWebPanel =
    {
        NULL,
        NULL,
        MESSAGE_DEFAULT,
        "WWW-panel message",
        "%n WWW-panel messages",
        createWebPanel,
        NULL,
        NULL
    };

#if 0
i18n("Email pager message", "%n Email pager messages", 1);
#endif

static Message *createEmailPager(Buffer *cfg)
{
    return new Message(MessageEmailPager, cfg);
}

static MessageDef defEmailPager =
    {
        NULL,
        NULL,
        MESSAGE_DEFAULT,
        "Email pager message",
        "%n Email pager messages",
        createEmailPager,
        NULL,
        NULL
    };

#if 0
i18n("Request secure channel", "%n requests secure channel", 1);
#endif

static Message *createOpenSecure(Buffer *cfg)
{
    return new Message(MessageOpenSecure, cfg);
}

static MessageDef defOpenSecure =
    {
        NULL,
        NULL,
        MESSAGE_SENDONLY,
        "Request secure channel",
        "%n requests secure channel",
        createOpenSecure,
        NULL,
        NULL
    };

#if 0
i18n("Close secure channel", "%n times close secure channel", 1);
#endif

static Message *createCloseSecure(Buffer *cfg)
{
    return new Message(MessageCloseSecure, cfg);
}

static MessageDef defCloseSecure =
    {
        NULL,
        NULL,
        MESSAGE_SILENT | MESSAGE_SENDONLY,
        "Close secure channel",
        "%n times close secure channel",
        createCloseSecure,
        NULL,
        NULL
    };

#if 0
i18n("Warning", "%n warnings", 1);
#endif

static DataDef warningMessageData[] =
    {
        { "Anonymous", DATA_BOOL, 1, 0 },
        { "OldLevel", DATA_ULONG, 1, 0 },
        { "NewLevel", DATA_ULONG, 1, 0 },
        { NULL, DATA_UNKNOWN, 0, 0 }
    };

WarningMessage::WarningMessage(Buffer *cfg)
        : AuthMessage(MessageWarning, cfg)
{
    load_data(warningMessageData, &data, cfg);
}

QCString WarningMessage::save()
{
    QCString res = AuthMessage::save();
    if (!res.isEmpty())
        res += '\n';
    return res + save_data(warningMessageData, &data);
}

QString WarningMessage::presentation()
{
    return QString("Increase warning level from %1% to %2%")
           .arg(ICQClient::warnLevel(getOldLevel()))
           .arg(ICQClient::warnLevel(getNewLevel()));
}

static Message *createWarning(Buffer *cfg)
{
    return new WarningMessage(cfg);
}

static MessageDef defWarning =
    {
        NULL,
        NULL,
        MESSAGE_SENDONLY,
        "Warning",
        "%n warnings",
        createWarning,
        NULL,
        NULL
    };

static Message *createIcqAuthRequest(Buffer *cfg)
{
    return new ICQAuthMessage(MessageICQAuthRequest, MessageAuthRequest, cfg);
}

static MessageDef defIcqAuthRequest =
    {
        NULL,
        NULL,
        MESSAGE_CHILD,
        NULL,
        NULL,
        createIcqAuthRequest,
        NULL,
        NULL
    };

static Message *createIcqAuthGranted(Buffer *cfg)
{
    return new ICQAuthMessage(MessageICQAuthGranted, MessageAuthGranted, cfg);
}

static MessageDef defIcqAuthGranted =
    {
        NULL,
        NULL,
        MESSAGE_CHILD,
        NULL,
        NULL,
        createIcqAuthGranted,
        NULL,
        NULL
    };

static Message *createIcqAuthRefused(Buffer *cfg)
{
    return new ICQAuthMessage(MessageICQAuthRefused, MessageAuthRefused, cfg);
}

static MessageDef defIcqAuthRefused =
    {
        NULL,
        NULL,
        MESSAGE_CHILD,
        NULL,
        NULL,
        createIcqAuthRefused,
        NULL,
        NULL
    };

static Message *createContactRequest(Buffer *cfg)
{
    return new ICQAuthMessage(MessageContactRequest, MessageContactRequest, cfg);
}

#if 0
i18n("Contact request", "%n contact requests", 1);
#endif

static MessageDef defContactRequest =
    {
        NULL,
        NULL,
        MESSAGE_DEFAULT | MESSAGE_SYSTEM,
        "Contact request",
        "%n contact requests",
        createContactRequest,
        NULL,
        NULL
    };

static Message *createIcqContacts(Buffer *cfg)
{
    return new IcqContactsMessage(cfg);
}

static MessageDef defIcqContacts =
    {
        NULL,
        NULL,
        MESSAGE_CHILD,
        NULL,
        NULL,
        createIcqContacts,
        NULL,
        NULL
    };

void ICQPlugin::registerMessages()
{
    Command cmd;

    cmd->id			 = MessageICQContacts;
    cmd->text		 = "ICQContacts";
    cmd->icon		 = "contacts";
    cmd->param		 = &defIcqContacts;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageICQFile;
    cmd->text		= "ICQFile";
    cmd->icon		= "file";
    cmd->param		= &defIcqFile;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageContactRequest;
    cmd->text		= I18N_NOOP("Contact Request");
    cmd->icon		= "contacts";
    cmd->param		= &defContactRequest;
    EventCreateMessageType(cmd).process();

    cmd->id         = MessageICQAuthRequest;
    cmd->text       = "ICQAuthRequest";
    cmd->icon       = "auth";
    cmd->param      = &defIcqAuthRequest;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageICQAuthGranted;
    cmd->text		= "ICQAuthGranted";
    cmd->icon		= "auth";
    cmd->param		= &defIcqAuthGranted;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageICQAuthRefused;
    cmd->text		= "ICQAuthRefused";
    cmd->icon		= "auth";
    cmd->param		= &defIcqAuthRefused;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageWebPanel;
    cmd->text		= I18N_NOOP("Web panel");
    cmd->icon		= "web";
    cmd->param		= &defWebPanel;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageEmailPager;
    cmd->text		= I18N_NOOP("Email pager");
    cmd->icon		= "mailpager";
    cmd->param		= &defEmailPager;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageOpenSecure;
    cmd->text		= I18N_NOOP("Request secure channel");
    cmd->icon		= "encrypted";
    cmd->menu_grp	= 0x30F0;
    cmd->param		= &defOpenSecure;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageCloseSecure;
    cmd->text		= I18N_NOOP("Close secure channel");
    cmd->icon		= "encrypted";
    cmd->menu_grp	= 0x30F0;
    cmd->param		= &defCloseSecure;
    EventCreateMessageType(cmd).process();

    cmd->id			= MessageWarning;
    cmd->text		= I18N_NOOP("Warning");
    cmd->icon		= "error";
    cmd->menu_grp	= 0x30F2;
    cmd->param		= &defWarning;
    EventCreateMessageType(cmd).process();

    cmd->id			= CmdUrlInput;
    cmd->text		= I18N_NOOP("&URL");
    cmd->icon		= "empty";
    cmd->icon_on	= QString::null;
    cmd->bar_id		= ToolBarMsgEdit;
    cmd->bar_grp	= 0x1030;
    cmd->menu_id	= 0;
    cmd->menu_grp	= 0;
    cmd->flags		= BTN_EDIT | BTN_NO_BUTTON | COMMAND_CHECK_STATE;
    EventCommandCreate(cmd).process();
}

void ICQPlugin::unregisterMessages()
{
    EventRemoveMessageType(MessageICQUrl).process();
    EventRemoveMessageType(MessageICQContacts).process();
    EventRemoveMessageType(MessageICQ).process();
    EventRemoveMessageType(MessageICQFile).process();
    EventRemoveMessageType(MessageICQAuthRequest).process();
    EventRemoveMessageType(MessageICQAuthGranted).process();
    EventRemoveMessageType(MessageICQAuthRefused).process();
    EventRemoveMessageType(MessageContactRequest).process();
    EventRemoveMessageType(MessageWebPanel).process();
    EventRemoveMessageType(MessageEmailPager).process();
    EventRemoveMessageType(MessageOpenSecure).process();
    EventRemoveMessageType(MessageCloseSecure).process();
    EventRemoveMessageType(MessageWarning).process();

    EventCommandRemove(CmdUrlInput).process();
}

void ICQClient::packExtendedMessage(Message *msg, ICQBuffer &buf, ICQBuffer &msgBuf, ICQUserData *data)
{
    unsigned short port = 0;
    switch (msg->type()){
    case MessageICQFile:
        port = static_cast<ICQFileMessage*>(msg)->getPort();
    case MessageFile:
        buf.pack((char*)plugins[PLUGIN_FILE], sizeof(plugin));
        buf.packStr32("File");
        buf << 0x00000100L << 0x00010000L << 0x00000000L << (unsigned short)0 << (char)0;
        msgBuf.packStr32(getContacts()->fromUnicode(getContact(data), msg->getPlainText()));
        msgBuf << port << (unsigned short)0;
        msgBuf << getContacts()->fromUnicode(getContact(data), static_cast<FileMessage*>(msg)->getDescription());
        msgBuf.pack((unsigned long)(static_cast<FileMessage*>(msg)->getSize()));
        msgBuf << 0x00000000L;
        break;
    }
}

QString ICQClient::packContacts(ContactsMessage *msg, ICQUserData *, CONTACTS_MAP &c)
{
    QString contacts = msg->getContacts();
    QString newContacts;
    while (!contacts.isEmpty()){
        QString contact = getToken(contacts, ';');
        QString url = getToken(contact, ',');
        QString proto = getToken(url, ':');
        if (proto == "sim"){
            Contact *contact = getContacts()->contact(url.toULong());
            if (contact){
                ClientDataIterator it(contact->clientData);
                clientData *cdata;
                while ((cdata = ++it) != NULL){
                    Contact *cc = contact;
                    if (!isMyData(cdata, cc))
                        continue;
                    ICQUserData *d = (ICQUserData*)cdata;
                    QString screen = this->screen(d);
                    CONTACTS_MAP::iterator it = c.find(screen);
                    if (it == c.end()){
                        alias_group ci;
                        ci.alias = contact->getName();
                        ci.grp   = cc ? cc->getGroup() : 0;
                        c.insert(CONTACTS_MAP::value_type(screen, ci));
                        if (!newContacts.isEmpty())
                            newContacts += ';';
                        if (screen.toULong()){
                            newContacts += "icq:";
                            newContacts += screen;
                            newContacts += '/';
                            newContacts += contact->getName();
                            newContacts += ',';
                            if (contact->getName() == screen){
                                newContacts += "ICQ ";
                                newContacts += screen;
                            }else{
                                newContacts += contact->getName();
                                newContacts += " (ICQ ";
                                newContacts += screen;
                                newContacts += ')';
                            }
                        }else{
                            newContacts += "aim:";
                            newContacts += screen;
                            newContacts += '/';
                            newContacts += contact->getName();
                            newContacts += ',';
                            if (contact->getName() == screen){
                                newContacts += "AIM ";
                                newContacts += screen;
                            }else{
                                newContacts += contact->getName();
                                newContacts += " (AIM ";
                                newContacts += screen;
                                newContacts += ')';
                            }
                        }
                    }
                }
            }
        }
        if ((proto == "icq") || (proto == "aim")){
            QString screen = getToken(url, '/');
            if (url.isEmpty())
                url = screen;
            CONTACTS_MAP::iterator it = c.find(screen);
            if (it == c.end()){
                alias_group ci;
                ci.alias = url;
                ci.grp   = 0;
                c.insert(CONTACTS_MAP::value_type(screen, ci));
            }
        }
    }
    return newContacts;
}

void ICQClient::packMessage(ICQBuffer &b, Message *msg, ICQUserData *data, unsigned short &type, bool bDirect, unsigned short flags)
{
    ICQBuffer msgBuf, buf;
    QCString res;
    switch (msg->type()){
    case MessageUrl:
        res = getContacts()->fromUnicode(getContact(data), msg->getPlainText());
        res += '\xFE';
        res += getContacts()->fromUnicode(getContact(data), static_cast<UrlMessage*>(msg)->getUrl());
        type = ICQ_MSGxURL;
        break;
    case MessageContacts:{
            CONTACTS_MAP c;
            QString nc = packContacts(static_cast<ContactsMessage*>(msg), data, c);
            if (c.empty()){
                msg->setError(I18N_NOOP("No contacts for send"));
                return;
            }
            static_cast<ContactsMessage*>(msg)->setContacts(nc);
            res = QString::number(c.size());
            for (CONTACTS_MAP::iterator it = c.begin(); it != c.end(); ++it){
                res += '\xFE';
                res += getContacts()->fromUnicode(getContact(data), (*it).first.str());
                res += '\xFE';
                res += getContacts()->fromUnicode(getContact(data), (*it).second.alias);
            }
            res += '\xFE';
            type = ICQ_MSGxCONTACTxLIST;
            break;
        }
    case MessageICQFile:
        if (!static_cast<ICQFileMessage*>(msg)->getExtended()){
            res = getContacts()->fromUnicode(getContact(data), msg->getPlainText());
            type = ICQ_MSGxFILE;
            break;
        }
    case MessageFile:	// FALLTHROW
        type = ICQ_MSGxEXT;
        packExtendedMessage(msg, buf, msgBuf, data);
        break;
    case MessageOpenSecure:
        type = ICQ_MSGxSECURExOPEN;
        break;
    case MessageCloseSecure:
        type = ICQ_MSGxSECURExCLOSE;
        break;
    }
    if (flags == ICQ_TCPxMSG_NORMAL){
        if (msg->getFlags() & MESSAGE_URGENT)
            flags = ICQ_TCPxMSG_URGENT;
        if (msg->getFlags() & MESSAGE_LIST)
            flags = ICQ_TCPxMSG_LIST;
    }
    if (bDirect || (type == ICQ_MSGxEXT)){
        b.pack(type);
        b.pack(msgStatus());
        b.pack(flags);
    }else{
        b.pack(this->data.owner.Uin.toULong());
        b.pack((char)type);
        b.pack((char)0x01);
    }
    b << res;
    if (buf.size()){
        b.pack((unsigned short)buf.size());
        b.pack(buf.data(0), buf.size());
        b.pack32(msgBuf);
    }
}

void ICQClient::parsePluginPacket(ICQBuffer &b, unsigned plugin_type, ICQUserData *data, unsigned uin, bool bDirect)
{
    b.incReadPos(1);
    unsigned short type;
    b >> type;
    b.incReadPos(bDirect ? 1 : 4);
    QValueList<QCString> phonebook;
    QValueList<QCString> numbers;
    QValueList<QCString> phonedescr;
    Contact *contact = NULL;
    unsigned long state, time, size, nEntries;
    unsigned i;
    unsigned nActive;
    switch (type){
    case 0:
    case 1:
        b.unpack(time);
        b.unpack(size);
        b.incReadPos(4);
        b.unpack(nEntries);
        if (data)
            log(L_DEBUG, "Plugin info reply %lu %lu (%lu %lu) %lu %lu (%u)",
                data->Uin.toULong(), time, data->PluginInfoTime.toULong(),
                data->PluginStatusTime.toULong(), size, nEntries, plugin_type);
        switch (plugin_type){
        case PLUGIN_RANDOMxCHAT:{
                QCString name, topic, homepage;

                b.incReadPos(-12);
                b.unpackStr(name);
                b.unpackStr(topic);
                unsigned short age;
                char gender;
                unsigned short country;
                unsigned short language;
                b.unpack(age);
                b.unpack(gender);
                b.unpack(country);
                b.unpack(language);
                b.unpackStr(homepage);
                ICQUserData data;
                load_data(static_cast<ICQProtocol*>(protocol())->icqUserData, &data, NULL);
                data.Uin.asULong() = uin;
                data.Alias.str() = QString::fromUtf8(name);
                data.About.str() = QString::fromUtf8(topic);
                data.Age.asULong() = age;
                data.Gender.asULong() = gender;
                data.Country.asULong() = country;
                data.Language.asULong() = language;
                data.Homepage.str() = QString::fromUtf8(homepage);
// currently unhandled
//                Event e(EventRandomChatInfo, &data);
//                e.process();
                free_data(static_cast<ICQProtocol*>(protocol())->icqUserData, &data);
                break;
            }
        case PLUGIN_QUERYxSTATUS:
            if (data == NULL)
                break;
            if (!bDirect){
                b.incReadPos(5);
                b.unpack(nEntries);
            }
            log(L_DEBUG, "Status info answer %lu", nEntries);
        case PLUGIN_QUERYxINFO:
            if (data == NULL)
                break;
            if (nEntries > 0x80){
                log(L_DEBUG, "Bad entries value %lX", nEntries);
                break;
            }
            for (i = 0; i < nEntries; i++){
                plugin p;
                b.unpack((char*)p, sizeof(p));
                b.incReadPos(4);
                QCString name, descr;
                b.unpackStr32(name);
                b.unpackStr32(descr);
                b.incReadPos(4);
                unsigned plugin_index;
                for (plugin_index = 0; plugin_index < PLUGIN_NULL; plugin_index++)
                    if (memcmp(p, plugins[plugin_index], sizeof(p)) == 0)
                        break;
                if (plugin_index >= PLUGIN_NULL){
                    log(L_DEBUG, "Unknown plugin sign %s %s", name.data(), descr.data());
                    continue;
                }
                log(L_DEBUG, "Plugin %u %s %s", plugin_index, name.data(), descr.data());
                switch (plugin_index){
                case PLUGIN_PHONEBOOK:
                case PLUGIN_FOLLOWME:
                    if (plugin_type == PLUGIN_QUERYxINFO){
                        addPluginInfoRequest(uin, PLUGIN_PHONEBOOK);
                    }else{
                        addPluginInfoRequest(uin, PLUGIN_FOLLOWME);
                    }
                    break;
                case PLUGIN_PICTURE:
                    if (plugin_type == PLUGIN_QUERYxINFO) {
                        // when buddyID -> new avatar support, no need to ask for old picture plugin
                        if(data->buddyID.toULong() == 0 || data->buddyHash.toBinary().size() != 16) {
                            data->buddyID.asULong() = 0;
                            addPluginInfoRequest(uin, plugin_index);
                        }
                    }
                    break;
                case PLUGIN_FILESERVER:
                case PLUGIN_ICQPHONE:
                    if (plugin_type == PLUGIN_QUERYxSTATUS)
                        addPluginInfoRequest(uin, plugin_index);
                    break;
                }
            }
            if (plugin_type == PLUGIN_QUERYxINFO){
                data->PluginInfoFetchTime = data->PluginInfoTime;
            }else{
                data->PluginStatusFetchTime = data->PluginStatusTime;
            }
            break;
        case PLUGIN_PICTURE:
            if (data){
                b.incReadPos(-4);
                QCString pict;
                QByteArray ba;
                b.unpackStr32(pict);
                b.unpackStr32(ba);
                QImage img;
                QString fName = pictureFile(data);
                QFile f(fName);
                if (f.open(IO_WriteOnly | IO_Truncate)){
                    f.writeBlock(ba.data(), ba.size());
                    f.close();
                    img.load(fName);
                }else{
                    log(L_ERROR, "Can't create %s", (const char*)fName.local8Bit());
                }
                data->PictureWidth.asULong()  = img.width();
                data->PictureHeight.asULong() = img.height();
            }
            break;
        case PLUGIN_PHONEBOOK:
            if (data){
                QString phones;
                nActive = (unsigned)(-1);
                if (nEntries > 0x80){
                    log(L_DEBUG, "Bad entries value %lX", nEntries);
                    break;
                }
                for (i = 0; i < nEntries; i++){
                    QCString descr, area, phone, ext, country;
                    unsigned long active;
                    b.unpackStr32(descr);
                    b.unpackStr32(area);
                    b.unpackStr32(phone);
                    b.unpackStr32(ext);
                    b.unpackStr32(country);
                    numbers.push_back(phone);
                    QCString value;
                    for (const ext_info *e = getCountries(); e->szName; e++){
                        if (country == e->szName){
                            value = "+";
                            value += QString::number(e->nCode);
                            break;
                        }
                    }
                    if (!area.isEmpty()){
                        if (!value.isEmpty())
                            value += ' ';
                        value += '(';
                        value += area;
                        value += ')';
                    }
                    if (!value.isEmpty())
                        value += ' ';
                    value += phone;
                    if (!ext.isEmpty()){
                        value += " - ";
                        value += ext;
                    }
                    b.unpack(active);
                    if (active)
                        nActive = i;
                    phonebook.push_back(value);
                    phonedescr.push_back(descr);
                }
                for (i = 0; i < nEntries; i++){
                    unsigned long type;
                    QCString phone = phonebook[i];
                    QCString gateway;
                    b.incReadPos(4);
                    b.unpack(type);
                    b.unpackStr32(gateway);
                    b.incReadPos(16);
                    switch (type){
                    case 1:
                    case 2:
                        type = CELLULAR;
                        break;
                    case 3:
                        type = FAX;
                        break;
                    case 4:{
                            type = PAGER;
                            phone = numbers[i];
                            const pager_provider *p;
                            for (p = getProviders(); *p->szName; p++){
                                if (gateway == p->szName){
                                    phone += '@';
                                    phone += p->szGate;
                                    phone += '[';
                                    phone += p->szName;
                                    phone += ']';
                                    break;
                                }
                            }
                            if (*p->szName == 0){
                                phone += '@';
                                phone += gateway;
                            }
                            break;
                        }
                    default:
                        type = PHONE;
                    }
                    phone += ',';
                    phone += phonedescr[i];
                    phone += ',';
                    phone += QString::number(type);
                    if (i == nActive)
                        phone += ",1";
                    if (!phones.isEmpty())
                        phones += ';';
                    phones += QString::fromUtf8(phone);
                }
                data->PhoneBook.str() = phones;
                Contact *contact = NULL;
                findContact(data->Uin.toULong(), NULL, false, contact);
                if (contact){
                    setupContact(contact, data);
                    EventContact e(contact, EventContact::eChanged);
                    e.process();
                }
            }
        }
        break;
    case 2:
        if (data){
            if (bDirect)
                b.incReadPos(3);
            b.unpack(state);
            b.unpack(time);
            log(L_DEBUG, "Plugin status reply %u %lu %lu (%u)", uin, state, time, plugin_type);
            findContact(uin, NULL, false, contact);
            if (contact == NULL)
                break;
            switch (plugin_type){
            case PLUGIN_FILESERVER:
                if ((state != 0) != (data->SharedFiles.toBool() != 0)){
                    data->SharedFiles.asBool() = (state != 0);
                    EventContact e(contact, EventContact::eChanged);
                    e.process();
                }
                break;
            case PLUGIN_FOLLOWME:
                if (state != data->FollowMe.toULong()){
                    data->FollowMe.asULong() = state;
                    EventContact e(contact, EventContact::eChanged);
                    e.process();
                }
                break;
            case PLUGIN_ICQPHONE:
                if ((state != 0) != (data->ICQPhone.toULong() != 0)){
                    data->ICQPhone.asULong() = (state != 0);
                    EventContact e(contact, EventContact::eChanged);
                    e.process();
                }
                break;
            }
        }
        break;
    default:
        log(L_DEBUG, "Unknown plugin type answer %u %u (%u)", uin, type, plugin_type);
    }
}

static const char* plugin_name[] =
    {
        "Phone Book",				// PLUGIN_PHONEBOOK
        "Picture",					// PLUGIN_PICTURE
        "Shared Files Directory",	// PLUGIN_FILESERVER
        "Phone \"Follow Me\"",		// PLUGIN_FOLLOWME
        "ICQphone Status"			// PLUGIN_ICQPHONE
    };

static const char* plugin_descr[] =
    {
        "Phone Book / Phone \"Follow Me\"",		// PLUGIN_PHONEBOOK
        "Picture",								// PLUGIN_PICTURE
        "Shared Files Directory",				// PLUGIN_FILESERVER
        "Phone Book / Phone \"Follow Me\"",		// PLUGIN_FOLLOWME
        "ICQphone Status"						// PLUGIN_ICQPHONE
    };

void ICQClient::pluginAnswer(unsigned plugin_type, unsigned long uin, ICQBuffer &info)
{
    Contact *contact;
    ICQUserData *data = findContact(uin, NULL, false, contact);
    log(L_DEBUG, "Request about %u", plugin_type);
    ICQBuffer answer;
    unsigned long typeAnswer = 0;
    unsigned long nEntries = 0;
    unsigned long time = 0;
    switch (plugin_type){
    case PLUGIN_PHONEBOOK:{
            if (data && data->GrpId.toULong() && !contact->getIgnore()){
                ICQBuffer answer1;
                time = this->data.owner.PluginInfoTime.toULong();
                QString phones = getContacts()->owner()->getPhones();
                while (!phones.isEmpty()){
                    QString item = getToken(phones, ';', false);
                    unsigned long publish = 0;
                    QString phoneItem = getToken(item, '/', false);
                    if (item != "-")
                        publish = 1;
                    QString number = getToken(phoneItem, ',');
                    QString descr = getToken(phoneItem, ',');
                    unsigned long type = getToken(phoneItem, ',').toUInt();
                    unsigned long active = 0;
                    if (!phoneItem.isEmpty())
                        active = 1;
                    QString area;
                    QString phone;
                    QString ext;
                    QString country;
                    QString gateway;
                    if (type == PAGER){
                        phone = getToken(number, '@');
                        int n = number.find('[');
                        if (n >= 0){
                            getToken(number, '[');
                            gateway = getToken(number, ']');
                        }else{
                            gateway = number;
                        }
                    }else{
                        int n = number.find('(');
                        if (n >= 0){
                            country = getToken(number, '(');
                            area    = getToken(number, ')');
                            if (country[0] == '+')
                                country = country.mid(1);
                            unsigned code = country.toULong();
                            country = QString::null;
                            for (const ext_info *e = getCountries(); e->nCode; e++){
                                if (e->nCode == code){
                                    country = e->szName;
                                    break;
                                }
                            }
                        }
                        n = number.find(" - ");
                        if (n >= 0){
                            ext = number.mid(n + 3);
                            number = number.left(n);
                        }
                        phone = number;
                    }
                    answer.packStr32(descr.local8Bit());
                    answer.packStr32(area.local8Bit());
                    answer.packStr32(phone.local8Bit());
                    answer.packStr32(ext.local8Bit());
                    answer.packStr32(country.local8Bit());
                    answer.pack(active);

                    unsigned long len = gateway.length() + 24;
                    unsigned long sms_available = 0;
                    switch (type){
                    case PHONE:
                        type = 0;
                        break;
                    case FAX:
                        type = 3;
                        break;
                    case CELLULAR:
                        type = 2;
                        sms_available = 1;
                        break;
                    case PAGER:
                        type = 4;
                        break;
                    }
                    answer1.pack(len);
                    answer1.pack(type);
                    answer1.packStr32(gateway.local8Bit());
                    answer1.pack((unsigned long)0);
                    answer1.pack(sms_available);
                    answer1.pack((unsigned long)0);
                    answer1.pack(publish);
                    nEntries++;
                }
                answer.pack(answer1.data(0), answer1.size());
                typeAnswer = 0x00000003;
                break;
            }
        }
    case PLUGIN_PICTURE:{
            time = this->data.owner.PluginInfoTime.toULong();
            typeAnswer = 0x00000001;
            QString pictFile = getPicture();
            if (!pictFile.isEmpty()){
                QFile f(pictFile);
                if (f.open(IO_ReadOnly)){
                    QFileInfo fi(f);
                    pictFile = fi.fileName();
                    nEntries = pictFile.length();
                    answer.pack(pictFile.local8Bit(), pictFile.length());
                    unsigned long size = f.size();
                    answer.pack(size);
                    while (size > 0){
                        char buf[2048];
                        unsigned tail = sizeof(buf);
                        if (tail > size)
                            tail = size;
                        f.readBlock(buf, tail);
                        answer.pack(buf, tail);
                        size -= tail;
                    }
                }
            }
            break;
        }
    case PLUGIN_FOLLOWME:
        time = this->data.owner.PluginStatusTime.toULong();
        break;
    case PLUGIN_QUERYxINFO:
        time = this->data.owner.PluginInfoTime.toULong();
        typeAnswer = 0x00010002;
        if (!getPicture().isEmpty()){
            nEntries++;
            answer.pack((char*)plugins[PLUGIN_PICTURE], sizeof(plugin));
            answer.pack((unsigned short)0);
            answer.pack((unsigned short)1);
            answer.packStr32(plugin_name[PLUGIN_PICTURE]);
            answer.packStr32(plugin_descr[PLUGIN_PICTURE]);
            answer.pack((unsigned long)0);
        }
        if (!getContacts()->owner()->getPhones().isEmpty()){
            nEntries++;
            answer.pack((char*)plugins[PLUGIN_PHONEBOOK], sizeof(plugin));
            answer.pack((unsigned short)0);
            answer.pack((unsigned short)1);
            answer.packStr32(plugin_name[PLUGIN_PHONEBOOK]);
            answer.packStr32(plugin_descr[PLUGIN_PHONEBOOK]);
            answer.pack((unsigned long)0);
        }
        break;
    case PLUGIN_QUERYxSTATUS:
        time = this->data.owner.PluginStatusTime.toULong();
        typeAnswer = 0x00010000;
        nEntries++;
        answer.pack((char*)plugins[PLUGIN_FOLLOWME], sizeof(plugin));
        answer.pack((unsigned short)0);
        answer.pack((unsigned short)1);
        answer.packStr32(plugin_name[PLUGIN_FOLLOWME]);
        answer.packStr32(plugin_descr[PLUGIN_FOLLOWME]);
        answer.pack((unsigned long)0);
        if (this->data.owner.SharedFiles.toBool()){
            nEntries++;
            answer.pack((char*)plugins[PLUGIN_FILESERVER], sizeof(plugin));
            answer.pack((unsigned short)0);
            answer.pack((unsigned short)1);
            answer.packStr32(plugin_name[PLUGIN_FILESERVER]);
            answer.packStr32(plugin_descr[PLUGIN_FILESERVER]);
            answer.pack((unsigned long)0);
        }
        if (this->data.owner.ICQPhone.toULong()){
            nEntries++;
            answer.pack((char*)plugins[PLUGIN_ICQPHONE], sizeof(plugin));
            answer.pack((unsigned short)0);
            answer.pack((unsigned short)1);
            answer.packStr32(plugin_name[PLUGIN_ICQPHONE]);
            answer.packStr32(plugin_descr[PLUGIN_ICQPHONE]);
            answer.pack((unsigned long)0);
        }
        break;
    default:
        log(L_DEBUG, "Bad plugin type request %u", plugin_type);
    }
    unsigned long size = answer.size() + 8;
    info.pack((unsigned short)0);
    info.pack((unsigned short)1);
    switch (plugin_type){
    case PLUGIN_FOLLOWME:
        info.pack(this->data.owner.FollowMe.toULong());
        info.pack(time);
        info.pack((char)1);
        break;
    case PLUGIN_QUERYxSTATUS:
        info.pack((unsigned long)0);
        info.pack((unsigned long)0);
        info.pack((char)1);
    default:
        info.pack(time);
        info.pack(size);
        info.pack(typeAnswer);
        info.pack(nEntries);
        info.pack(answer.data(0), answer.size());
    }
}



