/*
*  XmNap  A Motif napster client
*
*  Copyright (C) 2000 Mats Peterson
*
*  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; see the file COPYING.  If not, write to
*  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
*  Boston, MA 02111-1307, USA.
*
*  Please send any comments/bug reports to
*  [email protected]  (Mats Peterson)
*/

#include <Xm/Xm.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/Frame.h>
#include <Xm/Form.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/List.h>
#include <Xm/RowColumn.h>
#include <Xm/Protocols.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include "main.h"
#include "connect.h"
#include "command.h"
#include "chat.h"
#include "message.h"
#include "info.h"
#include "msgbox.h"
#include "input.h"
#include "util.h"
#ifdef USE_SOUND
#include "sound.h"
#endif

CHAN *channels = NULL;
PRIV *privs = NULL;
PING *pings = NULL;

static PRIV* PrivWindow(String nick);
static void ClosePriv(String nick);


static void ChatFocusCB(Widget w, XtPointer clientData, XtPointer callData)
{
   curWin = curFocus = XtParent(w);
   curWinType = 0;
   if (! strcmp((String)clientData, "privWin"))
       curWinType = 1;
}


static void TextFocusCB(Widget w, XtPointer clientData,
       XmAnyCallbackStruct *cbs) {
   XtVaSetValues(w, XmNcursorPositionVisible, True, NULL);
}


static void TextLosingFocusCB(Widget w, XtPointer clientData,
       XmTextVerifyCallbackStruct *cbs) {
   XtVaSetValues(w, XmNcursorPositionVisible, False, NULL);
}


static void ChatClearCB(Widget w, XtPointer clientData,
       XmPushButtonCallbackStruct *cbs)
{
   Widget text = (Widget)clientData;
   XmTextSetString(text, "");
}


static void InsertText(Widget w, String text)
{
   String s;
   XmTextPosition pos;

   if ((pos = XmTextGetLastPosition(w)))
       XmTextInsert(w, pos++, "\n");

   XmTextInsert(w, pos, text);
   pos += strlen(text);
   if (pos > 10000) {
       s = XmTextGetString(w);
       XmTextSetString(w, s + 1000);
       XtFree(s);
   }
   XmTextSetInsertionPosition(w, XmTextGetLastPosition(w));
}


static void ModifyVerifyCB(Widget w, XtPointer clientData,
       XmTextVerifyCallbackStruct *cbs)
{
   String text = cbs->text->ptr, p1, p2;
   String tmp;
   int msgType = curWinType ? MSG_CLIENT_PRIVMSG : MSG_CLIENT_PUBLIC;

   if (! text)
       return;

   if (strchr(text, '\n')) {
       tmp = XtMalloc(8192);
       cbs->doit = False;
       for (p1 = p2 = text; *p2; p2++) {
           if (*p2 == '\n') {
               *p2 = '\0';
               sprintf(tmp, "%s %s", (String)clientData, p1);
               if (SendMsg(msgType, tmp)) {
                   Disconnect(strerror(errno));
                   goto end;
               }
               if (msgType == MSG_CLIENT_PRIVMSG)
                   InsertText(XtNameToWidget(XtParent(w), "*privText"), p1);
               p1 = p2 + 1;
           }
       }
       sprintf(tmp, "%s %s", (String)clientData, p1);
       if (SendMsg(msgType, tmp)) {
           Disconnect(strerror(errno));
           goto end;
       }
       if (msgType == MSG_CLIENT_PRIVMSG)
           InsertText(XtNameToWidget(XtParent(w), "*privText"), p1);
   end:
       XtFree(tmp);
   }
}


void Ping(String nick)
{
   PING *newPing, *ping, *prevPing = NULL;

   for (ping = pings; ping; ping = ping->next) {
       if (! strcasecmp(ping->nick, nick))
           break;
       prevPing = ping;
   }
   if (ping) {
       if (prevPing) {
           prevPing->next = ping->next;
       } else {
           pings = pings->next;
       }
       XtFree(ping->nick);
       XtFree((char*)ping);
   }
   newPing = XtNew(PING);
   newPing->nick = XtNewString(nick);
   newPing->start = time(NULL);
   newPing->next = NULL;
   if (! pings)
       pings = newPing;
   else {
       for (ping = pings; ping->next; ping = ping->next);
       ping->next = newPing;
   }
   if (SendMsg(MSG_CLIENT_PING, newPing->nick))
       Disconnect(strerror(errno));
}


static String GetPingTime(String nick)
{
   String pingStr;
   PING *ping, *prevPing = NULL;
   int pingTime;

   for (ping = pings; ping; ping = ping->next) {
       if (! strcasecmp(nick, ping->nick))
           break;
       prevPing = ping;
   }
   if (! ping)
       return NULL;

   if (prevPing) {
       prevPing->next = ping->next;
   } else {
       pings = pings->next;
   }

   pingTime = time(NULL) - ping->start;
   pingStr = XtMalloc(80);
   if (pingTime != 1) {
       sprintf(pingStr, "%d seconds", pingTime);
   } else {
       sprintf(pingStr, "%d second", pingTime);
   }

   XtFree(ping->nick);
   XtFree((char*)ping);
   return pingStr;
}


void RcvGlobal(int type, String data)
{
   Widget textBox = NULL;
   String nick, text, ptr, tmp = XtMalloc(8192);
   CHAN *c;
   PRIV *p;
   String pingStr;

   ptr = strtok(data, " ");
   nick = XtNewString(ptr);
   if ((ptr = strtok(NULL, "\0")))
       text = XtNewString(ptr);
   else
       text = XtNewString("");

   if ((type == MSG_CLIENT_PRIVMSG)) {
       if ((p = FindPrivByNick(nick)))
           textBox = p->text;
   } else {
       if (curWinType) {
           if ((p = FindPrivByWin(curWin)))
               textBox = p->text;
       } else {
           if ((c = FindChanByWin(curWin)))
               textBox = c->text;
       }
   }

   if (! textBox) {
       switch (type) {
           case MSG_CLIENT_PRIVMSG:
               p = PrivWindow(nick);
               sprintf(tmp, "> %s", text);
               InsertText(p->text, tmp);
#ifdef USE_SOUND
               if (strcmp(nick, userInfo.userName))
                   PlaySound(sound[PRIVMSG_SOUND]);
#endif
               sprintf(tmp, "Private message from %s. Accept?", nick);
               if (YesNoMsg(tmp, "OK")) {
                   XtPopup(p->w, XtGrabNone);
                   p->visible = True;
               } else
                   ClosePriv(nick);
               goto end;

           case MSG_SERVER_WALLOP:
               sprintf(tmp, "OPERATOR MESSAGE from %s: %s",
                       nick, text);
               ShowMiscInfo(tmp, 0);
               goto end;

           case MSG_SERVER_ANNOUNCE:
               sprintf(tmp, "GLOBAL MESSAGE from %s: %s",
                       nick, text);
               ShowMiscInfo(tmp, 0);
               goto end;

           case MSG_SERVER_PING:
               strcpy(tmp, nick);
               if (SendMsg(MSG_CLIENT_PONG, tmp))
                   Disconnect(strerror(errno));
               goto end;

           case MSG_SERVER_PONG:
               if (! (pingStr = GetPingTime(nick)))
                   goto end;
               sprintf(tmp, "PONG received from %s: %s",
                       nick, pingStr);
               ShowMiscInfo(tmp, 0);
               XtFree(pingStr);
               goto end;
       }
   }

   switch (type) {
       case MSG_CLIENT_PRIVMSG:
           sprintf(tmp, "> %s", text);
           InsertText(textBox, tmp);
#ifdef USE_SOUND
           if (p->visible && strcmp(nick, userInfo.userName))
               PlaySound(sound[PRIVMSG_SOUND]);
#endif
           break;

       case MSG_SERVER_WALLOP:
           sprintf(tmp, "*** OPERATOR MESSAGE from %s: %s",
                   nick, text);
           InsertText(textBox, tmp);
           break;

       case MSG_SERVER_ANNOUNCE:
           sprintf(tmp, "*** GLOBAL MESSAGE from %s: %s",
                   nick, text);
           InsertText(textBox, tmp);
           break;

       case MSG_SERVER_PING:
           sprintf(tmp, "*** %s PING", nick);
           InsertText(textBox, tmp);
           strcpy(tmp, nick);
           if (SendMsg(MSG_CLIENT_PONG, tmp))
               Disconnect(strerror(errno));
           break;

       case MSG_SERVER_PONG:
           if (! (pingStr = GetPingTime(nick)))
               goto end;
           sprintf(tmp, "*** PONG received from %s: %s",
                   nick, pingStr);
           InsertText(textBox, tmp);
           XtFree(pingStr);
           break;
   }

end:
   XtFree(tmp);
   XtFree(nick);
   XtFree(text);
}


void RcvChannel(int type, String data)
{
   String topic, channel, nick, text, empty = "";
   String tmp = XtMalloc(8192);
   CHAN *c;

   channel = strtok(data, " ");

   c = FindChanByName(channel);

   if ((! c) || (! XtIsRealized(c->w)))
       goto end;

   switch (type) {
       case MSG_SERVER_PUBLIC:
           nick = strtok(NULL, " ");
           if (! (text = strtok(NULL, "\0")))
               text = empty;
           sprintf(tmp, "<%s> %s", nick, text);
           InsertText(c->text, tmp);
#ifdef USE_SOUND
           if (strcmp(nick, userInfo.userName))
               PlaySound(sound[CHANMSG_SOUND]);
#endif
           break;
       case MSG_SERVER_TOPIC:
           if ((topic = strtok(NULL, "\0")))
               sprintf(tmp, "%s  %s", channel, topic);
           else
               strcpy(tmp, channel);
           XtVaSetValues(c->w, XmNtitle, tmp, NULL);
           break;
       case MSG_CLIENT_EMOTE:
           nick = strtok(NULL, " ");
           if (! (text = strtok(NULL, "\"")))
               text = empty;
           sprintf(tmp, "* %s %s", nick, text);
           InsertText(c->text, tmp);
           break;
   }

end:
   XtFree(tmp);
}


static void ShowQuickInfo(CHAN *c, String nick)
{
   String values[3];
   enum {SHARED, LINK, END};
   ULIST *ul;
   char shared[20], link[20];

   for(ul = c->users; ul; ul = ul->next) {
       if (! strcmp(ul->nick, nick))
           break;
   }
   sprintf(shared, "%d", ul->shared);
   values[SHARED] = shared;
   sprintf(link, "%s", linkStr[ul->link]);
   values[LINK] = link;
   values[END] = NULL;
   ShowInfo("quickInfo", values, 15);
}


static void UserMenuCB(Widget w , XtPointer clientData,
       XmPopupHandlerCallbackStruct *cbs)
{
   XmString *items;
   String nick, reason, msg, tmp = XtMalloc(8192);
   CHAN *c;
   int itemCount;
   enum {MESSAGE, QINFO, WHOIS, BROWSE, PING, OP, DEOP,
         MUZZLE, UNMUZZLE, KICK, KILL, BAN};

   XtVaGetValues(XtParent(XtParent(XtParent(w))),
           XmNselectedItemCount, &itemCount, NULL);
   if (! itemCount)
       return;

   XtVaGetValues(XtParent(XtParent(XtParent(w))),
           XmNselectedItems, &items, NULL);
   XmStringGetLtoR(items[0], XmFONTLIST_DEFAULT_TAG, &nick);

   c = FindChanByWin(curWin);

   switch ((int)clientData) {
       case MESSAGE:
           msg = GetInput("Message", "", 40);
           if (! strlen(msg))
               goto end;
           sprintf(tmp, "/msg %s %s", nick, msg);
           break;

       case QINFO:
           ShowQuickInfo(c, nick);
           goto end;

       case WHOIS:
           sprintf(tmp, "/whois %s", nick);
           break;

       case BROWSE:
           sprintf(tmp, "/browse %s", nick);
           break;

       case PING:
           sprintf(tmp, "/ping %s", nick);
           break;

       case OP:
           sprintf(tmp, "/op %s %s", c->name, nick);
           break;

       case DEOP:
           sprintf(tmp, "/deop %s %s", c->name, nick);
           break;

       case MUZZLE:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/muzzle %s %s", nick, reason);
           break;

       case UNMUZZLE:
           sprintf(tmp, "/unmuzzle %s", nick);
           break;

       case KICK:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/kick %s %s %s", c->name, nick, reason);
           break;

       case KILL:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/kill %s %s", nick, reason);
           break;

       case BAN:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/ban %s %s", nick, reason);
           break;
   }

   CmdParse(tmp);
end:
   XtFree(nick);
   XtFree(tmp);
}


static void ChnEntryCB(Widget w, XtPointer clientData,
       XmAnyCallbackStruct *cbs)
{
   String c, s, tmp = XtMalloc(8192);

   c = (String)clientData;
   s = XmTextFieldGetString(w);
   XmTextFieldSetString(w, "");

   if (s[0] == '/')
       CmdParse(s);
   else {
       sprintf(tmp, "%s %s", c, s);
       if (SendMsg(MSG_CLIENT_PUBLIC, tmp))
           Disconnect(strerror(errno));
   }
   XtFree(s);
   XtFree(tmp);
}


static void PartChannelCB(Widget w, XtPointer clientData,
       XmPushButtonCallbackStruct *cbs)
{
   PartChannel((String)clientData);
}


static void ChannelWindow(CHAN *c)
{
   Widget chanWin, chanForm, chanText, chanEntry, userList,
       userMenu, partBtn, clearBtn;
   Arg args[20];
   int n;
   Dimension w1, w2;

   chanWin = XtVaCreatePopupShell("chanWin",
           topLevelShellWidgetClass, topLevel,
           XmNtitle, c->name,
           XmNiconPixmap, napPix,
           XmNiconName, c->name,
           NULL);

   c->w = chanWin;

   chanForm = XtVaCreateManagedWidget("chanForm",
           xmFormWidgetClass, chanWin,
           XmNmarginWidth, 6,
           XmNmarginHeight, 6,
           NULL);

   partBtn = XtVaCreateManagedWidget("partBtn",
           xmPushButtonWidgetClass, chanForm,
           XmNleftAttachment, XmATTACH_FORM,
           XmNleftOffset, 20,
           XmNbottomAttachment, XmATTACH_FORM,
           XmNbottomOffset, 8,
           NULL);

   clearBtn = XtVaCreateManagedWidget("clearBtn",
           xmPushButtonWidgetClass, chanForm,
           XmNleftAttachment, XmATTACH_WIDGET,
           XmNleftWidget, partBtn,
           XmNleftOffset, 10,
           XmNbottomAttachment, XmATTACH_FORM,
           XmNbottomOffset, 8,
           NULL);

   XtVaGetValues(partBtn, XmNwidth, &w1, NULL);
   XtVaGetValues(clearBtn, XmNwidth, &w2, NULL);
   if (w1 > w2)
       XtVaSetValues(clearBtn, XmNwidth, w1, NULL);
   else
       XtVaSetValues(partBtn, XmNwidth, w2, NULL);

   chanEntry = XtVaCreateManagedWidget("chanEntry",
           xmTextFieldWidgetClass, chanForm,
           XmNleftAttachment, XmATTACH_FORM,
           XmNrightAttachment, XmATTACH_FORM,
           XmNbottomAttachment, XmATTACH_WIDGET,
           XmNbottomWidget, partBtn,
           XmNbottomOffset, 8,
           NULL);

   c->entry = chanEntry;

   n = 0;
   XtSetArg(args[n], XmNselectionPolicy, XmBROWSE_SELECT); n++;
   XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n++;
   XtSetArg(args[n], XmNlistSizePolicy, XmCONSTANT); n++;
   XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
   XtSetArg(args[n], XmNbottomWidget, chanEntry); n++;
   XtSetArg(args[n], XmNbottomOffset, 10); n++;
   userList = XmCreateScrolledList(chanForm, "userList", args, n);
   c->list = userList;
   XtManageChild(userList);

   n = 0;
   XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
   XtSetArg(args[n], XmNeditable, False); n++;
   XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
   XtSetArg(args[n], XmNscrollHorizontal, False); n++;
   XtSetArg(args[n], XmNwordWrap, True); n++;
   XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
   XtSetArg(args[n], XmNrightWidget, userList); n++;
   XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
   XtSetArg(args[n], XmNbottomWidget, chanEntry); n++;
   XtSetArg(args[n], XmNbottomOffset, 10); n++;
   chanText = XmCreateScrolledText(chanForm, "chanText", args, n);
   c->text = chanText;
   XtManageChild(chanText);

   userMenu = XmVaCreateSimplePopupMenu(userList, "userMenu",
           (XtCallbackProc)UserMenuCB,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmNpopupEnabled, XmPOPUP_AUTOMATIC,
           NULL);

   n = 0;
   XtSetArg(args[n], XmNinitialFocus, chanEntry); n++;
   XtSetValues(chanForm, args, n);

   XtAddCallback(chanForm, XmNfocusCallback,
           (XtCallbackProc)ChatFocusCB, (XtPointer)"chanWin"),

   XtAddCallback(chanEntry, XmNactivateCallback,
           (XtCallbackProc)ChnEntryCB, (XtPointer)c->name);

   XtAddCallback(chanEntry, XmNmodifyVerifyCallback,
           (XtCallbackProc)ModifyVerifyCB, (XtPointer)c->name);

   XtAddCallback(partBtn, XmNactivateCallback,
           (XtCallbackProc)PartChannelCB, (XtPointer)c->name);

   XtAddCallback(clearBtn, XmNactivateCallback,
           (XtCallbackProc)ChatClearCB, (XtPointer)chanText);

   XtAddCallback(chanText, XmNfocusCallback,
           (XtCallbackProc)TextFocusCB, NULL);
   XtAddCallback(chanText, XmNlosingFocusCallback,
           (XtCallbackProc)TextLosingFocusCB, NULL);
   XtAddCallback(chanEntry, XmNfocusCallback,
           (XtCallbackProc)TextFocusCB, NULL);
   XtAddCallback(chanEntry, XmNlosingFocusCallback,
           (XtCallbackProc)TextLosingFocusCB, NULL);

   XmAddWMProtocolCallback(chanWin,
           XmInternAtom(XtDisplay(chanWin), "WM_DELETE_WINDOW", False),
           (XtCallbackProc)PartChannelCB, (XtPointer)c->name);

   XtPopup(chanWin, XtGrabNone);
}


void JoinChannel(String name)
{
   if (SendMsg(MSG_CLIENT_JOIN, name))
       Disconnect(strerror(errno));
}


void DoJoinChannel(String name)
{
   CHAN *ptr, *newChn;

   newChn = XtNew(CHAN);
   newChn->name = XtNewString(name);
   newChn->users = NULL;
   newChn->next = NULL;

   if (! channels)
       channels = newChn;
   else {
       for (ptr = channels; ptr->next; ptr = ptr->next);
       ptr->next = newChn;
   }

   ChannelWindow(newChn);
}


void PartChannel(String name)
{
   if (SendMsg(MSG_CLIENT_PART, name))
       Disconnect(strerror(errno));
}


void DoPartChannel(String name)
{
   CHAN *ptr, *prevPtr = NULL;
   String p;

   for (ptr = channels; ptr; ptr = ptr->next) {
       if (! strcmp(ptr->name, name))
           break;
       prevPtr = ptr;
   }
   if (! ptr) {
       ErrMsg("DoPartChannel: no such channel");
       return;
   }

   while (ptr->users) {
       XtFree(ptr->users->nick);
       p = (String)ptr->users;
       ptr->users = ptr->users->next;
       XtFree(p);
   }

   if (prevPtr) {
       prevPtr->next = ptr->next;
   } else {
       channels = channels->next;
   }

   DestroyWin(ptr->w);

   XtFree(ptr->name);
   XtFree((char*)ptr);
}


void PartAllChannels(void)
{
   while (channels)
       DoPartChannel(channels->name);
}


static void PrivMenuCB(Widget w , XtPointer clientData,
       XmPopupHandlerCallbackStruct *cbs)
{
   String reason, tmp = XtMalloc(8192);
   PRIV *priv;
   Widget topLevel;
   enum {WHOIS, BROWSE, PING, MUZZLE, UNMUZZLE, KILL, BAN};

   for(topLevel = w; !XtIsTopLevelShell(topLevel);
       topLevel = XtParent(topLevel));
   priv = FindPrivByWin(topLevel);

   switch ((int)clientData) {
       case WHOIS:
           sprintf(tmp, "/whois %s", priv->nick);
           break;

       case BROWSE:
           sprintf(tmp, "/browse %s", priv->nick);
           break;

       case PING:
           sprintf(tmp, "/ping %s", priv->nick);
           break;

       case MUZZLE:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/muzzle %s %s", priv->nick, reason);
           break;

       case UNMUZZLE:
           sprintf(tmp, "/unmuzzle %s", priv->nick);
           break;

       case KILL:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/kill %s %s", priv->nick, reason);
           break;

       case BAN:
           reason = GetInput("Reason", "", 40);
           sprintf(tmp, "/ban %s %s", priv->nick, reason);
           break;
   }

   CmdParse(tmp);
   XtFree(tmp);
}


static void PrivEntryCB(Widget w, XtPointer clientData,
       XmAnyCallbackStruct *cbs)
{
   String nick, s, tmp;
   PRIV *p;

   nick = (String)clientData;

   s = XmTextFieldGetString(w);
   XmTextFieldSetString(w, "");

   if (s[0] == '/')
       CmdParse(s);
   else {
       tmp = (String)XtMalloc(strlen(nick) + strlen(s) + 4);
       sprintf(tmp, "%s %s", nick, s);
       if (SendMsg(MSG_CLIENT_PRIVMSG, tmp))
           Disconnect(strerror(errno));

       if (! (p = FindPrivByNick(nick))) {
           ErrMsg("PrivEntryCB: no such window");
       } else
           InsertText(p->text, s);
       XtFree(tmp);
   }
   XtFree(s);
}


static void ClosePriv(String nick)
{
   PRIV *ptr, *prevPtr = NULL;

   for (ptr = privs; ptr; ptr = ptr->next) {
       if (! strcmp(ptr->nick, nick))
           break;
       prevPtr = ptr;
   }

   if (! ptr) {
       ErrMsg("ClosePrivWindow: no such nick");
       return;
   }

   if (prevPtr) {
       prevPtr->next = ptr->next;
   } else {
       privs = privs->next;
   }

   DestroyWin(ptr->w);
   XtFree(ptr->nick);
   XtFree((char*)ptr);
}


void CloseAllPrivs(void)
{
   while (privs)
       ClosePriv(privs->nick);
}


static void ClosePrivCB(Widget w, XtPointer clientData,
       XmPushButtonCallbackStruct *cbs)
{
   ClosePriv((String)clientData);
}


static PRIV* PrivWindow(String nick)
{
   Widget privWin, privForm, privText, privEntry, privMenu;
   Widget closeBtn, clearBtn;
   Arg args[20];
   int n;
   PRIV *ptr, *newPriv;
   Dimension w1, w2;

   newPriv = XtNew(PRIV);
   newPriv->nick = XtNewString(nick);
   newPriv->visible = False;
   newPriv->next = NULL;

   if (! privs)
       privs = newPriv;
   else {
       for (ptr = privs; ptr->next; ptr = ptr->next);
       ptr->next = newPriv;
   }

   privWin = XtVaCreatePopupShell("privWin",
           topLevelShellWidgetClass, topLevel,
           XmNtitle, newPriv->nick,
           XmNiconPixmap, napPix,
           XmNiconName, newPriv->nick,
           NULL);

   newPriv->w = privWin;

   privForm = XtVaCreateManagedWidget("privForm",
           xmFormWidgetClass, privWin,
           XmNmarginWidth, 6,
           XmNmarginHeight, 6,
           NULL);

   closeBtn = XtVaCreateManagedWidget("closeBtn",
           xmPushButtonWidgetClass, privForm,
           XmNleftAttachment, XmATTACH_FORM,
           XmNleftOffset, 20,
           XmNbottomAttachment, XmATTACH_FORM,
           XmNbottomOffset, 8,
           NULL);

   clearBtn = XtVaCreateManagedWidget("clearBtn",
           xmPushButtonWidgetClass, privForm,
           XmNleftAttachment, XmATTACH_WIDGET,
           XmNleftWidget, closeBtn,
           XmNleftOffset, 10,
           XmNbottomAttachment, XmATTACH_FORM,
           XmNbottomOffset, 8,
           NULL);

   XtVaGetValues(closeBtn, XmNwidth, &w1, NULL);
   XtVaGetValues(clearBtn, XmNwidth, &w2, NULL);
   if (w1 > w2)
       XtVaSetValues(clearBtn, XmNwidth, w1, NULL);
   else
       XtVaSetValues(closeBtn, XmNwidth, w2, NULL);

   privEntry = XtVaCreateManagedWidget("privEntry",
           xmTextFieldWidgetClass, privForm,
           XmNleftAttachment, XmATTACH_FORM,
           XmNrightAttachment, XmATTACH_FORM,
           XmNbottomAttachment, XmATTACH_WIDGET,
           XmNbottomWidget, closeBtn,
           XmNbottomOffset, 8,
           NULL);

   newPriv->entry = privEntry;

   n = 0;
   XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
   XtSetArg(args[n], XmNeditable, False); n++;
   XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
   XtSetArg(args[n], XmNscrollHorizontal, False); n++;
   XtSetArg(args[n], XmNwordWrap, True); n++;
   XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
   XtSetArg(args[n], XmNbottomWidget, privEntry); n++;
   XtSetArg(args[n], XmNbottomOffset, 10); n++;
   privText = XmCreateScrolledText(privForm, "privText", args, n);
   newPriv->text = privText;
   XtManageChild(privText);

   privMenu = XmVaCreateSimplePopupMenu(privText, "privMenu",
           (XtCallbackProc)PrivMenuCB,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmVaPUSHBUTTON, NULL, NULL, NULL, NULL,
           XmNpopupEnabled, XmPOPUP_AUTOMATIC,
           NULL);

   n = 0;
   XtSetArg(args[n], XmNinitialFocus, privEntry); n++;
   XtSetValues(privForm, args, n);

   XtAddCallback(privForm, XmNfocusCallback,
           (XtCallbackProc)ChatFocusCB, (XtPointer)"privWin");

   XtAddCallback(privEntry, XmNactivateCallback,
           (XtCallbackProc)PrivEntryCB, (XtPointer)newPriv->nick);

   XtAddCallback(privEntry, XmNmodifyVerifyCallback,
           (XtCallbackProc)ModifyVerifyCB, (XtPointer)newPriv->nick);

   XtAddCallback(closeBtn, XmNactivateCallback,
           (XtCallbackProc)ClosePrivCB, (XtPointer)newPriv->nick);

   XtAddCallback(clearBtn, XmNactivateCallback,
           (XtCallbackProc)ChatClearCB, (XtPointer)privText);

   XtAddCallback(privText, XmNfocusCallback,
           (XtCallbackProc)TextFocusCB, NULL);
   XtAddCallback(privText, XmNlosingFocusCallback,
           (XtCallbackProc)TextLosingFocusCB, NULL);
   XtAddCallback(privEntry, XmNfocusCallback,
           (XtCallbackProc)TextFocusCB, NULL);
   XtAddCallback(privEntry, XmNlosingFocusCallback,
           (XtCallbackProc)TextLosingFocusCB, NULL);

   XmAddWMProtocolCallback(privWin,
           XInternAtom(XtDisplay(privWin), "WM_DELETE_WINDOW", False),
           (XtCallbackProc)ClosePrivCB, (XtPointer)newPriv->nick);

   return newPriv;
}


void UpdateUserList(int type, String data)
{
   String channel, nick;
   CHAN *ptr;
   ULIST *newUser, *userPtr, *prevPtr = NULL;
   XmString xms;
   char tmp[256];
   int n, shared, link;

   channel = strtok(data, " ");
   nick = strtok(NULL, " ");
   shared = atoi(strtok(NULL, " "));
   link = atoi(strtok(NULL, " "));

   if (! (ptr = FindChanByName(channel))) {
       ErrMsg("UpdateUserList: channel not found");
       return;
   }

   if ((type == MSG_SERVER_JOIN) ||
           (type == MSG_SERVER_CHANNEL_USER_LIST)) {
       newUser = XtNew(ULIST);
       newUser->nick = XtNewString(nick);
       newUser->shared = shared;
       newUser->link = link;

       if (! ptr->users) {
           ptr->users = newUser;
           newUser->next = NULL;
           n = 1;
       } else {
           for (userPtr = ptr->users, n = 1; userPtr;
                userPtr = userPtr->next, n++) {
               if (strcasecmp(userPtr->nick, nick) > 0)
                   break;
               prevPtr = userPtr;
           }

           if (prevPtr) {
               newUser->next = prevPtr->next;
               prevPtr->next = newUser;
           } else {
               newUser->next = ptr->users;
               ptr->users = newUser;
           }
       }

       xms = XmStringCreateLocalized(nick);
       XmListAddItems(ptr->list, &xms, 1, n);
       XmStringFree(xms);

       if (type == MSG_SERVER_JOIN) {
           sprintf(tmp, "*** %s has joined channel %s", nick, channel);
           InsertText(ptr->text, tmp);
#ifdef USE_SOUND
           PlaySound(sound[JOIN_SOUND]);
#endif
       }
   } else {
       for (userPtr = ptr->users; userPtr; userPtr = userPtr->next) {
           if (! strcmp(userPtr->nick, nick))
               break;
           prevPtr = userPtr;
       }
       if (! userPtr) {
           ErrMsg("UpdateUserList: no such nick");
           return;
       }

       if (prevPtr) {
           prevPtr->next = userPtr->next;
       } else {
           ptr->users = ptr->users->next;
       }

       XtFree(userPtr->nick);
       XtFree((char*)userPtr);

       xms = XmStringCreateLocalized(nick);
       XmListDeleteItems(ptr->list, &xms, 1);
       XmStringFree(xms);

       sprintf(tmp, "*** %s has left channel %s", nick, channel);
       InsertText(ptr->text, tmp);
#ifdef USE_SOUND
           PlaySound(sound[PART_SOUND]);
#endif
   }
}


void ChatActions(Widget w, XEvent *ev, String *params, int *numParams)
{
   CHAN* c;
   PRIV* p;
   Widget topLevel;

   for (topLevel = w; ! XtIsTopLevelShell(topLevel);
        topLevel = XtParent(topLevel));

   if (! strcmp(params[0], "partChan")) {
       c = FindChanByWin(topLevel);
       PartChannel(c->name);
   } else if (! strcmp(params[0], "clearChan")) {
       c = FindChanByWin(topLevel);
       XmTextSetString(c->text, "");
   } else if (! strcmp(params[0], "closePriv")) {
       p = FindPrivByWin(topLevel);
       ClosePriv(p->nick);
   } else if (! strcmp(params[0], "clearPriv")) {
       p = FindPrivByWin(topLevel);
       XmTextSetString(p->text, "");
   }
}