/* AmiTrack.c -- a program to keep a running list of available Amigas. */

#include "AmiTrackShared.h"
#include "TrackRexx.h"
#include "Amitrack.h"

#define MAX_PING_ERRORS         5

/* our GUI gadget IDs */
#define GAD_CLIENTLIST           10
#define GAD_PINGBUTTON           11 
#define GAD_COMMENTSTRING        12
#define GAD_SERVERSTRING         13
#define GAD_PORTSTRING           14
#define GAD_UPDATEBUTTON         15
#define GAD_UPDATEINTERVALSTRING 16
#define GAD_PINGINTERVALSTRING   17
#define GAD_MINSTEXT             18
#define GAD_MINSTEXT2            19
#define GAD_ACTIONCYCLE          20

/* overridable, compile-time defaults */
#define DEFAULT_AMITRACK_SERVER        "isys.ml.org"
#define DEFAULT_AMITRACK_PORT          18945
#define DEFAULT_AMITRACK_COMMENT       ""
#define DEFAULT_AMITRACK_CXPOPUP       "YES"
#define DEFAULT_AMITRACK_CXPRI         0
#define DEFAULT_AMITRACK_POPKEY        "lcommand shift a"
#define DEFAULT_AMITRACK_PINGDELAY     1
#define DEFAULT_AMITRACK_ENABLED       "YES"
#define DEFAULT_AMITRACK_REFRESHDELAY  5
#define DEFAULT_AMITRACK_ALLOWMULTIPLE "NO"
#define DEFAULT_AMITRACK_CONFIRMAPP    "YES"

struct Library * SocketBase = NULL;
struct timerequest *TimerIO  = NULL;
struct MsgPort	   *TimerMP  = NULL;
struct Message	   *TimerMSG = NULL;

UBYTE ** ttypes = NULL;
int currentAction = -1;
BOOL BConfirmAppLaunch;
BOOL BStartedFromWB = FALSE;
BOOL BSendGoodbye = FALSE;	/* TRUE if we think we have a live server, else FALSE */

int nPort, nNumActions, nLastEntryClicked=-1;
int nPingInterval, nRefreshInterval;
int nPingCount, nRefreshCount;
struct SocketStuff * UDPSocket = NULL;
struct TimerStuff  * Timer = NULL;
struct CxStuff * CX = NULL;
char * szCurrentHostname = NULL, * szCurrentComment = NULL;
char szMyHostName[100] = "";
ULONG ulMyIPAddress = 0L;
char szVersionString[] = "$VER: AmiTrack v1.41", * pcDisplayVersionString;
void BeAClient(char * szHostName, char * szComment, BOOL BOpenWindow, int cxPri, char * hotkey);
void Cleanup(void);

BOOL SendPacket(struct SocketStuff * sSocket, ULONG ulReqs, char * szComment);

BOOL GetSocketLib(void);
char * TimeStamp(void);
void InitActions(void);  /* NULLs all string pointers */
void FreeActions(void);  /* Frees all string pointers */

int main(int argc, char **argv);


/* Data structures for Gadtools GUI */
struct Library * WorkbenchBase = NULL;
struct Library * IconBase      = NULL;
struct Library * GraphicsBase  = NULL;
struct Library * IntuitionBase = NULL;
struct Library * GadToolsBase  = NULL;
struct Library * DiskFontBase  = NULL;
struct Library * CxBase        = NULL;

struct RexxHost * rexxHost = NULL;
struct WindowStuff * TrackWindow   = NULL;
struct Menu     *Menu     = NULL;
struct MenuItem *MenuItem = NULL;

struct List * DisplayList = NULL;	/* Our list of strings for the ListView */
struct ClientList * clientList = NULL;

char * ActionLabels[20], * Actions[20];

/* default GUI settings */
int nWinLeft		= 50;
int nWinTop		    = 100;
int nWinWidth		= 520;
int nWinHeight		= 200;
int nMinWinWidth	= 300;
int nMinWinHeight	= 120;

long lLastChangeAt = 0L;
BOOL BEnabled = TRUE, BAllowMultiple = FALSE;

#define P_ENABLED 100
#define P_CONFIRM 101
#define P_ABOUT   102
#define P_HIDE    103
#define P_QUIT    104

/* menus */
struct NewMenu nmMenus[] = {
    NM_TITLE, "Project", 	 NULL, 	0L, 	   	 NULL, NULL,
    NM_ITEM,  "Enabled",     "E",   CHECKIT,     NULL, (void *) P_ENABLED,
    NM_ITEM,  "Confirm",     "L", CHECKIT,     NULL, (void *) P_CONFIRM,    
    NM_ITEM,  "About",	 	 "?",	0L,		     NULL, (void *) P_ABOUT,
    NM_ITEM,  "Hide", 	     "H",	0L,		     NULL, (void *) P_HIDE,
    NM_ITEM,  NM_BARLABEL, 	 NULL, 	0L, 	   	 NULL, NULL,
    NM_ITEM,  "Quit",  	 	 "Q",  	0L, 	   	 NULL, (void *) P_QUIT,
    NM_END,   NULL, 		 NULL, 	NULL, 	   	 NULL, NULL
};



/* GUI functions */
struct WindowStuff * SetupTrackWindow(struct WindowStuff *);
void HandleIDCMP(struct WindowStuff *);
BOOL UpdateWindow(struct WindowStuff * win, BOOL BFree);
struct Node * AllocDisplayItem(char * szHostName, ULONG ulIPAddress, ULONG ulHostFieldLen, char * szComment);
void FreeDisplayList(struct List * list);
void SetConnectStatus(struct WindowStuff * win, BOOL BNewStatus);
void FreeDisplayItem(struct Node * current);
void AttachList(struct WindowStuff * win, BOOL BAttach);
void PatchLists(struct List * displayList, struct ClientList * cList, ULONG ulIPAddress, char * szReplaceMe, char * szNewComment, BOOL BDelete);
BOOL CreateTrackMenus(struct WindowStuff * win, BOOL BCreate);

/* ------------ Amiga GUI functions -------------------------- */

#define HSPACE               5
#define VSPACE               5
#define PING_BUTTON_TEXT     "_Ping"
#define UPDATE_BUTTON_TEXT   "_Refresh"
#define SERVER_STRING_TEXT   "_Server:"
#define PORT_STRING_TEXT     "P_ort:"
#define COMMENT_STRING_TEXT  "_Comment:"
#define EVERY_TEXT           "every"
#define MINS_TEXT1           "_minutes,"
#define MINS_TEXT2           "m_inutes."
#define ALL_BOTTOM_LINE_TEXT "Refresh every 888 minutes, Ping every 888 minutes."


/* To tear down, set BCreate==FALSE */
BOOL CreateTrackMenus(struct WindowStuff * win, BOOL BCreate)
{   
	UNLESS((win)&&(win->vi)) return(FALSE);
	
	UNLESS(BCreate) 
	{
		if (Menu) 
		{
			ResetMenuStrip(win->win,Menu); 
			FreeMenus(Menu); 
			Menu = NULL;
		}
		return(FALSE);
	}

	/* Create menus */
	UNLESS((Menu = CreateMenus(nmMenus, TAG_DONE)) &&
	       (LayoutMenus(Menu, win->vi, TAG_DONE)))
	       return(CreateTrackMenus(win, FALSE));

	SetMenuStrip(win->win, Menu);
	return(TRUE);
}

void InitActions()
{
  int i;
  
  for (i=0; i<(sizeof(Actions)/sizeof(char *)); i++)
    Actions[i] = ActionLabels[i] = NULL;
  nNumActions = 0;
}

void FreeActions()
{
  int i;
  
  for (i=0; i<(sizeof(Actions)/sizeof(char *)); i++)
  {
    if (Actions[i]) ReplaceAllocedString(&Actions[i], NULL);
    if (ActionLabels[i]) ReplaceAllocedString(&ActionLabels[i], NULL);
  }
  nNumActions = 0;
}

void ListClicked(int index)
{
  static time_t tLastTimeClicked = (time_t) -1;
  time_t tPreviousTimeClicked = tLastTimeClicked;
	
  tLastTimeClicked = time(NULL);
  		
  if  ((index == nLastEntryClicked) &&
      ((tLastTimeClicked-tPreviousTimeClicked) < 2)) DoAction(index, currentAction, BConfirmAppLaunch);
		
  nLastEntryClicked = index;
}

char * GetDisplayListItem(int index)
{
	struct Node * current;
	
	if (DisplayList == NULL) return("(No_Display_List)");
	
	current = DisplayList->lh_Head;
	if (index >= 0)
	{
		while (current->ln_Succ)
		{
			if (index == 0) return(current->ln_Name);
			current = current->ln_Succ; index--;
			if (current == NULL) return("(No_More_Entries)");
		}
	}
	return("(Bad_Index)");
}

int GetTrackHostByName(char * name)
{
  struct Node * current;
  int i=0;
  if (clientList == NULL) return(-1);
  
  current = clientList->list.lh_Head;
  while(current->ln_Succ)
  {
    struct Client * c = (struct Client *) current;
    
    if (strcmp(c->hostname, name) == 0) return(i);
    current = current->ln_Succ;
    i++;
  }
  return(-1);
}

int GetTrackActionByName(char * name)
{
  int i;
  
  for (i=0;i<nNumActions;i++) if (strcmp(name, ActionLabels[i])==0) return(i);
  return(-1);
}

int DoAction(int index, int action, BOOL BConf)
{
  char * target = GetDisplayListItem(index), * pcTemp;
  char tempTarget[100];
  char temp[900];
  int result = -2;
  
  if (target == NULL) return(-1);
  if ((action >= nNumActions)||(action < 0)) return -1;
  
  strncpy(tempTarget, target, sizeof(tempTarget));
  if (pcTemp = strchr(tempTarget,' ')) *pcTemp = '\0';
  
  sprintf(temp, "Okay to connect to %s using %s?",tempTarget,ActionLabels[action]);
  if ((BConf == FALSE)||(MakeReq(NULL,temp,"Connect|Cancel")))
  { 
    sprintf(temp, Actions[action], tempTarget, tempTarget, tempTarget, tempTarget);
    if ((result = SystemTags(temp, SYS_Asynch, TRUE,
         SYS_Input, Open("NIL:",MODE_OLDFILE), SYS_Output, NULL)) == -1)
           MakeReq(NULL,"Error launching program!", NULL);
  }
  return(result);
}


int getActionCycleWidth(struct WindowStuff * win)
{	
  int i,len = 0;
  for (i=0; (ActionLabels[i] != NULL); i++)
  {
    int next = TextLength(&win->screen->RastPort, ActionLabels[i], strlen(ActionLabels[i]));
    if (next > len) len = next; 
  }
  if (len > 0) len += HSPACE*6; /* For the arrow icon! */
  if (len > (win->win->Width/2)) len = win->win->Width/2;  /* Not TOO wide! */
  return(len);
}
	

/* Updates/reupdates the window on creation or after a size change */
/* If BFree is TRUE, just deallocate anything that was allocated */
/* Returns the allocated/deallocated state of the window gadgets */
BOOL UpdateWindow(struct WindowStuff * win, BOOL BFree)
{
	struct NewGadget ng;
	struct Gadget * gad;
	int actionLabelLength;
	
	UNLESS((win)&&(win->screen)) return(FALSE);
	
	/* First deallocate */
	if ((win->win)&&(win->glist)) RemoveGList(win->win, win->glist, -1);
	if (win->glist)	{FreeGadgets(win->glist); win->glist = NULL;}

	/* Mark all gadgets as free */
	win->ListGadget    = win->PingButton    =
	win->UpdateButton  = win->ServerString  = 
	win->PortString    = win->CommentString =
	win->UpdateIntString = win->PingIntString =  NULL;

	if (Menu) CreateTrackMenus(win, FALSE);
	if (win->vi) {FreeVisualInfo(win->vi); win->vi = NULL;}
	if (BFree) return(FALSE);	/* If we're just freeing, that's all to do */

	if (win->win)
	{
		/* erase any gadget imagery */
		EraseRect(win->win->RPort,win->win->BorderLeft, win->win->BorderTop,
		  win->win->Width  - win->win->BorderRight - 1,
          win->win->Height - win->win->BorderBottom - 1);
		RefreshWindowFrame(win->win);   			
	}

	/* Make everything NULL, etc. */
	memset(&ng, 0, sizeof(ng));

	/* Now start allocating */
	UNLESS(win->vi = GetVisualInfo(win->screen,TAG_END)) return(FALSE);
	UNLESS(CreateTrackMenus(win, TRUE))                  return(FALSE);
	UNLESS(gad     = CreateContext(&win->glist))         return(FALSE);
	
	/* Allocate Server gadget */
	ng.ng_VisualInfo = win->vi;
	ng.ng_TextAttr   = &win->font;
	ng.ng_Height     = win->font.ta_YSize + VSPACE;
	ng.ng_GadgetText = SERVER_STRING_TEXT;
	ng.ng_LeftEdge   += win->screen->WBorLeft + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3);
	ng.ng_TopEdge    = win->screen->WBorTop + win->screen->RastPort.TxHeight + VSPACE;
	ng.ng_Width      = ((win->win->Width * 2)/3) - ng.ng_LeftEdge;
	ng.ng_GadgetID   = GAD_SERVERSTRING;
	ng.ng_Flags	     = PLACETEXT_LEFT;
	win->ServerString = gad = CreateGadget(STRING_KIND, gad, &ng, GTST_String, szCurrentHostname, GT_Underscore, '_', TAG_END);

	/* Allocate Port gadget */
	ng.ng_GadgetText = PORT_STRING_TEXT;
	ng.ng_LeftEdge   += ng.ng_Width + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3);
	ng.ng_Width      = win->win->Width - ng.ng_LeftEdge - win->screen->WBorRight - HSPACE;
	ng.ng_GadgetID   = GAD_PORTSTRING;
	ng.ng_Flags	     = PLACETEXT_LEFT;
	win->PortString = gad = CreateGadget(INTEGER_KIND, gad, &ng, GTIN_Number, nPort, GT_Underscore, '_', TAG_END);
	
	/* Calculate the width of the widest action label */
	actionLabelLength = getActionCycleWidth(win);
	
	/* Allocate Comment gadget */
	ng.ng_GadgetText = COMMENT_STRING_TEXT;
	ng.ng_LeftEdge   = win->screen->WBorLeft + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3);
	ng.ng_TopEdge    += ng.ng_Height + VSPACE;
	ng.ng_Width      = win->win->Width - ng.ng_LeftEdge - win->screen->WBorRight - HSPACE - actionLabelLength - (actionLabelLength ? 2*HSPACE : 0);
	ng.ng_GadgetID   = GAD_COMMENTSTRING;
	ng.ng_Flags	     = PLACETEXT_LEFT;
	win->CommentString = gad = CreateGadget(STRING_KIND, gad, &ng, GTST_String, szCurrentComment, GT_Underscore, '_', TAG_END);

    /* Allocate Actions cycle, if we have any actions! */
    if (actionLabelLength > 0)
    {
	  ng.ng_GadgetText = NULL;
	  ng.ng_LeftEdge   += ng.ng_Width + HSPACE;
	  ng.ng_Width      = actionLabelLength + HSPACE;
	  ng.ng_GadgetID   = GAD_ACTIONCYCLE;
	  win->ActionCycle = gad = CreateGadget(CYCLE_KIND, gad, &ng, GTCY_Labels, ActionLabels, GTCY_Active, currentAction, GT_Underscore, '_', TAG_END);
    }
    else win->ActionCycle = NULL;
    
	/* Allocate Update button */
	ng.ng_GadgetText = UPDATE_BUTTON_TEXT;
	ng.ng_TopEdge    += ng.ng_Height + VSPACE;
	ng.ng_LeftEdge   = win->screen->WBorLeft + HSPACE;
	ng.ng_Width      = TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + HSPACE;
	ng.ng_Flags	     = PLACETEXT_IN;
	ng.ng_GadgetID   = GAD_UPDATEBUTTON;
	win->UpdateButton = gad = CreateGadget(BUTTON_KIND, gad, &ng, GT_Underscore, '_', TAG_END);

	/* Allocate UpdateInterval string */
	ng.ng_GadgetText = EVERY_TEXT;
	ng.ng_LeftEdge   += ng.ng_Width + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3);
	ng.ng_Width      = TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + HSPACE;
	ng.ng_Flags	     = PLACETEXT_LEFT;
	ng.ng_GadgetID   = GAD_UPDATEINTERVALSTRING;
	win->UpdateIntString = gad = CreateGadget(INTEGER_KIND, gad, &ng, GTIN_Number, nRefreshInterval, GT_Underscore, '_', TAG_END);

	/* Allocate Mins text */
	ng.ng_GadgetText = MINS_TEXT1;
	ng.ng_Flags	 = PLACETEXT_LEFT;
	ng.ng_LeftEdge   += ng.ng_Width + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3); 
	ng.ng_Width      = 0;
	ng.ng_GadgetID   = GAD_MINSTEXT;
	gad = CreateGadget(TEXT_KIND, gad, &ng, GT_Underscore, '_', TAG_END);

	/* Allocate Ping button */
	ng.ng_GadgetText = PING_BUTTON_TEXT;
	ng.ng_Width      = TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + HSPACE;
	ng.ng_Flags	     = PLACETEXT_IN;
	ng.ng_GadgetID   = GAD_PINGBUTTON;
	win->PingButton  = gad = CreateGadget(BUTTON_KIND, gad, &ng, GT_Underscore, '_', TAG_END);

	/* Allocate PingInterval string */
	ng.ng_GadgetText = EVERY_TEXT;
	ng.ng_LeftEdge   += ng.ng_Width + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3);
	ng.ng_Width      = TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + HSPACE;
	ng.ng_Flags      = PLACETEXT_LEFT;
	ng.ng_GadgetID   = GAD_PINGINTERVALSTRING;
	win->PingIntString = gad = CreateGadget(INTEGER_KIND, gad, &ng, GTIN_Number, nPingInterval, GT_Underscore, '_', TAG_END);

	/* Allocate Mins2 text */
	ng.ng_GadgetText = MINS_TEXT2;
	ng.ng_Flags	     = PLACETEXT_LEFT;
	ng.ng_LeftEdge   += ng.ng_Width + TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + (HSPACE*3); 
	ng.ng_Width      = TextLength(&win->screen->RastPort, ng.ng_GadgetText, strlen(ng.ng_GadgetText)) + HSPACE;
	ng.ng_GadgetID   = GAD_MINSTEXT2;
	gad = CreateGadget(TEXT_KIND, gad, &ng, GT_Underscore, '_', TAG_END);
	
	/* Allocate ListView */
	ng.ng_GadgetText = NULL;
	ng.ng_TextAttr   = &win->fixedfont;
	ng.ng_LeftEdge   = win->screen->WBorLeft + HSPACE;
	ng.ng_TopEdge    += ng.ng_Height + VSPACE;
	ng.ng_Width	     = win->win->Width - ng.ng_LeftEdge - win->screen->WBorRight - HSPACE;
	ng.ng_Height     = win->win->Height - ng.ng_TopEdge - win->screen->WBorBottom - (VSPACE*2);
	ng.ng_GadgetID   = GAD_CLIENTLIST;
	win->ListGadget  = gad = CreateGadget(LISTVIEW_KIND, gad, &ng, GTLV_Labels, DisplayList, GTLV_ReadOnly, (ActionLabels[0] == NULL), TAG_END);

	/* Attach gadgets to window */	
	AddGList(win->win, win->glist, -1, -1, NULL);
	RefreshGList(win->glist, win->win, NULL, -1);
	GT_RefreshWindow(win->win, NULL);

	return(TRUE);
}


void AttachList(struct WindowStuff * win, BOOL BAttach)
{
	if ((win)&&(win->win)&&(win->ListGadget)) 
	{
		GT_SetGadgetAttrs(win->ListGadget, win->win, NULL, 
			GTLV_Labels, (((BAttach)&&(DisplayList)) 
			? DisplayList : ((struct List *)(~0))), TAG_END);
	}
}

/* Disables some gadgets if we aren't properly connected */
void SetConnectStatus(struct WindowStuff * win, BOOL BNewStatus)
{
    if (win)
    {
		if (win->PingButton)   GT_SetGadgetAttrs(win->PingButton,   win->win, NULL, GA_Disabled, !BNewStatus, TAG_END);
		if (win->UpdateButton) GT_SetGadgetAttrs(win->UpdateButton, win->win, NULL, GA_Disabled, !BNewStatus, TAG_END);
	}

	/* if disconnecting, dump the display list--it's no longer valid */
	if (BNewStatus == FALSE)
	{
	    AttachList(win,FALSE);
    	if (DisplayList) FreeDisplayList(DisplayList); 
    	DisplayList = NULL;
    }

	BSendGoodbye = BNewStatus;
}


int GetNumItems(struct List * list)
{
  int count = 0;
  struct Node * current = list->lh_Head;

  while(current->ln_Succ)
  {
    count++;
    current = current->ln_Succ;
  }
  return(count);
}

void SetCommentString(struct WindowStuff * win, char * newVal)			       
{
  char * szTemp = NULL;
			              	
  /* Grab and sanitize the new comment */
  ReplaceAllocedString(&szTemp,newVal);
  ReplaceAllocedString(&szCurrentComment, PastSpaces(RemoveUnprintableChars(szTemp)));
  ReplaceAllocedString(&szTemp, NULL);

  SetTrackFlag(CODE_PING);

  if (win) 
  {
    GT_SetGadgetAttrs(win->CommentString, win->win, NULL, GTST_String, szCurrentComment, TAG_END);
    AttachList(win,FALSE);
  }
  PatchLists(DisplayList, clientList, ulMyIPAddress, szMyHostName, szCurrentComment, FALSE);
  if (win) AttachList(win,TRUE);
}

/* Frees a malloced() and strduped() Null-terminated string vector */
void FreeStringArray(char ** array)
{
  char ** next = array, * This;
  
  while(This = *next) {free(This); next++;}
  free(array);
}

void SetServerString(struct WindowStuff * win, char * newVal)
{
  char * szTemp = NULL;
			              	
  /* Grab and sanitize the new server string */
  ReplaceAllocedString(&szTemp, newVal);
  ReplaceAllocedString(&szCurrentHostname, ToLower(PastSpaces(RemoveUnprintableChars(szTemp))));
  ReplaceAllocedString(&szTemp, NULL);
  if ((strlen(szMyHostName)>0)&&(strcmp(szCurrentHostname,"localhost")==0)) ReplaceAllocedString(&szCurrentHostname, szMyHostName); 
  if (win) GT_SetGadgetAttrs(win->ServerString, win->win, NULL, GTST_String, szCurrentHostname, TAG_END);
  SetTrackFlag(CODE_RECONNECT);
}


void SetPortNumber(struct WindowStuff * win, int nPort)
{
  if (nPort <= 0) nPort = DEFAULT_AMITRACK_PORT;
  if (win) GT_SetGadgetAttrs(win->PortString, win->win, NULL, GTIN_Number, nPort, TAG_END);
  SetTrackFlag(CODE_RECONNECT);
}


void SetUpdateInterval(struct WindowStuff * win, int nInterval)
{
  if (nInterval < 0) nInterval = 0;
  nRefreshCount = nRefreshInterval = nInterval;
  if (win) GT_SetGadgetAttrs(win->UpdateIntString, win->win, NULL, GTIN_Number, nInterval, TAG_END);
}


void SetPingInterval(struct WindowStuff * win, int nInterval)
{
  if (nInterval < 0) nInterval = 0;
  nPingCount = nPingInterval = nInterval;
  if (win) GT_SetGadgetAttrs(win->PingIntString, win->win, NULL, GTIN_Number, nInterval, TAG_END);
}

void SetCurrentAction(struct WindowStuff * win, char * name)
{
  int i;
  
  for (i=0; i<nNumActions; i++)
  {
    if (strcmp(name, ActionLabels[i]) == 0)
    {
      SetCurrentActionIndex(win, i);
      return;
    }
  }
}

void SetCurrentActionIndex(struct WindowStuff * win, int code)
{
  currentAction = (code >= nNumActions) ? nNumActions-1 : code; 
  if ((win)&&(currentAction >= 0)) 
    GT_SetGadgetAttrs(win->ActionCycle, win->win, NULL, 
       GTCY_Active, currentAction, 
       GTCY_Labels, ActionLabels,
       TAG_END);
}

/* Returns any additional action flags that we want set */
void HandleIDCMP(struct WindowStuff * win)
{
	struct IntuiMessage *message;
	ULONG class, code, qual, ulItemCode, id;
	struct Gadget * gad;
	struct MenuItem * mItem;
	
	if (win == NULL) SetTrackFlag(CODE_QUIT);

	/* Examine pending messages */	
	while (message = (struct IntuiMessage *)GT_GetIMsg(win->win->UserPort))
	{
		class = message->Class;		/* extract needed info from message */
		code  = message->Code;
		qual  = message->Qualifier;
		gad   = (struct Gadget *) (message->IAddress);

		/* tell Intuition we got the message */
		GT_ReplyIMsg(message);

		/* see what events occured, take correct action */
		switch(class)
		{		
			case IDCMP_CLOSEWINDOW: 
				SetTrackFlag(CODE_HIDE);
				break;

      			case IDCMP_NEWSIZE:
      				UpdateWindow(win,FALSE);
			      	break;
      	
      			case IDCMP_INTUITICKS:
      				break;
      			
      			case IDCMP_MOUSEBUTTONS:
      				break;
      				
			case IDCMP_VANILLAKEY: 
				switch(code)
				{
					case 'p': case 'P': SetTrackFlag(CODE_PING);    break;
					case 'r': case 'R': SetTrackFlag(CODE_REFRESH); break;
					case 's': case 'S': ActivateGadget(win->ServerString, win->win, NULL);    break;
					case 'o': case 'O': ActivateGadget(win->PortString, win->win, NULL);      break;
					case 'c': case 'C': ActivateGadget(win->CommentString, win->win, NULL);   break;
					case 'm': case 'M': ActivateGadget(win->UpdateIntString, win->win, NULL); break;
					case 'i': case 'I': ActivateGadget(win->PingIntString, win->win, NULL);   break;
				}
				break;
				
			case IDCMP_GADGETUP:
                switch(gad ? gad->GadgetID : -1)
                {
                   case GAD_CLIENTLIST: ListClicked(code); break;
                   case GAD_PINGBUTTON: SetTrackFlag(CODE_PING); break;
			       case GAD_COMMENTSTRING: SetCommentString(TrackWindow, ((struct StringInfo*)win->CommentString->SpecialInfo)->Buffer); break;
			       case GAD_SERVERSTRING: SetServerString(TrackWindow, ((struct StringInfo*)win->ServerString->SpecialInfo)->Buffer); break;
			       case GAD_PORTSTRING: SetPortNumber(TrackWindow, ((struct StringInfo*)win->PortString->SpecialInfo)->LongInt); break;
                   case GAD_UPDATEBUTTON: SetTrackFlag(CODE_REFRESH); break;
			       case GAD_UPDATEINTERVALSTRING: SetUpdateInterval(TrackWindow, ((struct StringInfo*)win->UpdateIntString->SpecialInfo)->LongInt); break;
			       case GAD_PINGINTERVALSTRING: SetPingInterval(TrackWindow, ((struct StringInfo*)win->PingIntString->SpecialInfo)->LongInt); break;
			       case GAD_ACTIONCYCLE: SetCurrentActionIndex(TrackWindow,code); break;
			       default: printf("HandleICMP:  unknown gadget ID %i\n", id); break;
			    }
                break;

			case IDCMP_MENUPICK:
				while( code != MENUNULL ) 
				{
					char szMessage[150];
					mItem = ItemAddress( Menu, code );

					ulItemCode = (ULONG) GTMENUITEM_USERDATA(mItem);
					switch(ulItemCode)
					{
					    case P_CONFIRM:
					        BConfirmAppLaunch = BConfirmAppLaunch ? FALSE : TRUE;
					        break;
					        
					    case P_ENABLED:
					        SetTrackFlag(BEnabled ? CODE_DISABLE : CODE_ENABLE);
					        break;
					        
						case P_HIDE:
							SetTrackFlag(CODE_HIDE);
							break;
							
						case P_ABOUT:	    
							sprintf(szMessage,"%s\nby Jeremy Friesner\njfriesne@ucsd.edu\nCompiled: %s",
								pcDisplayVersionString,__DATE__);
							MakeReq(NULL,szMessage,"Not Bad");
							break;
							
						case P_QUIT:
						    SetTrackFlag(CODE_QUIT);
						    break;
					}

					code = mItem->NextSelect;
				}
				break;
			
			case IDCMP_REFRESHWINDOW:
				GT_BeginRefresh(win->win);
				GT_EndRefresh(win->win, TRUE);
				nWinWidth  = win->win->Width;
				nWinHeight = win->win->Height;
				nWinLeft   = win->win->LeftEdge;
				nWinTop    = win->win->TopEdge;
				break;
				       
			default:        
				/* printf("handleIDCMP: bad class %lu\n",class); */
				break;
		}
	}
}


/* Searches the given DisplayList and/or ClientList until it finds a 
   string beginning with szReplaceMe.  It then deletes that node and 
   replaces it with an entry composed of the params 
   
   If BDelete is TRUE, it just deletes the old entry without adding a new one.

*/
void PatchLists(struct List * displayList, struct ClientList * cList, ULONG ulIPAddress, char * szReplaceMe, char * szNewComment, BOOL BDelete)
{
	struct Node * current, * prev = NULL, * newNode;
	char * pcTemp, * pcSpace;
	int nLen = strlen(szReplaceMe);
	ULONG ulThisIPAddress;

	if (displayList)
	{
	 current = displayList->lh_Head;
	 while(current->ln_Succ)
	 {
		if (pcTemp = (char *)current->ln_Name)
		{
			ulThisIPAddress = (ULONG) *((ULONG *) (current->ln_Name-sizeof(ULONG)-sizeof(ULONG)));
			if (ulIPAddress == ulThisIPAddress)
			{
			    if (BDelete)
			    {
					Remove(current);
					FreeDisplayItem(current);
			    }
			    else
			    {
					/* Figure out the correct padding from the old one */
					UNLESS(pcSpace = strchr(pcTemp,' ')) break;
					while(*pcSpace == ' ') pcSpace++;
					if (newNode = AllocDisplayItem(szReplaceMe, ulIPAddress, ((int)(pcSpace-pcTemp))-1, szNewComment))
					{
						Remove(current);
						Insert(displayList, newNode, prev);
						FreeDisplayItem(current);
					}
				}
				break;
			}
		}
		prev = current;
		current = current->ln_Succ;
	 }
    }
    if (cList)
    {
      prev = NULL;
      current = cList->list.lh_Head;
      while(current->ln_Succ)
      {
        struct Client * c = (struct Client *) current;
        if (ulIPAddress == c->ulIPAddress)
        {
          if (BDelete)
          {
            Remove(current);
            FreeClient(c);
            cList->nListLength--;
            lLastChangeAt = time(NULL);
          }
          else
          {
            struct Client * newClient = NewClient(ulIPAddress, szReplaceMe, szNewComment);
            if (newClient)
            {
              Remove(current);
              Insert(&cList->list, (struct Node *) newClient, prev);
              lLastChangeAt = time(NULL);
            }
          }
          break;
        }
        prev = current;
        current = current->ln_Succ;
      }
    }
}


struct CxStuff * SetupCxStuff(struct CxStuff * cx, int pri, char * hotkey)
{
    if (cx)
    {
       if (cx->name) FreeMem(cx->name, strlen(cx->name)+1);
       if (cx->broker) DeleteCxObjAll(cx->broker);
       if (cx->port)
       {
         /* deallocate cx */
         CxMsg * msg;
       
         while(msg = (CxMsg *)GetMsg(cx->port))
           ReplyMsg((struct Message *)msg);
         DeletePort(cx->port);
       }
       FreeMem(cx, sizeof(struct CxStuff));  
       
       return(NULL);
    }
    else
    {
       char name[200];
       CxObj * filter;
       
       /* allocate and return a new cx */
       UNLESS(cx = AllocMem(sizeof(struct CxStuff), MEMF_CLEAR)) return(NULL);
       UNLESS(cx->port = CreateMsgPort()) return(SetupCxStuff(cx,0,NULL));

       if (BAllowMultiple) sprintf(name,"AmiTrack %lu",time(NULL));
                      else strcpy(name,"AmiTrack");
                      
       UNLESS(cx->name = AllocMem(strlen(name)+1, MEMF_ANY)) return(SetupCxStuff(cx,0,NULL));
       strcpy(cx->name, name);
       
       cx->nb.nb_Version = NB_VERSION;
       cx->nb.nb_Name    = cx->name;
       cx->nb.nb_Title   = pcDisplayVersionString;
       cx->nb.nb_Descr   = "Lists other Amigas on the 'net";
       cx->nb.nb_Unique  = NBU_UNIQUE | NBU_NOTIFY;   
       cx->nb.nb_Flags   = COF_SHOW_HIDE;  /* We'll have a window available */
       cx->nb.nb_Pri     = pri;
       cx->nb.nb_Port    = cx->port;
       cx->nb.nb_ReservedChannel = 0;
    
       UNLESS(cx->broker = CxBroker(&cx->nb, NULL)) return(SetupCxStuff(cx,0,NULL));
       if ((hotkey)&&(filter=CxFilter(hotkey))) 
       {
         CxObj *sender;
         
         AttachCxObj(cx->broker, filter);
         if (sender = CxSender(cx->port, EVT_HOTKEY)) 
         {
           CxObj *translate;
           
           AttachCxObj(filter,sender);
           if (translate = CxTranslate(NULL)) AttachCxObj(filter, translate);
         }
       }
       
       ActivateCxObj(cx->broker, 1L); /* Makes us "active" -- receiving messages */
       if (BEnabled == FALSE) ActivateCxObj(cx->broker, 0L);  /* Now disable it */
       return(cx);    
    }
}

struct WindowStuff * SetupTrackWindow(struct WindowStuff * win)
{
	if (win)
	{
		/* Free the window */
		UpdateWindow(win,TRUE);	/* Free all the gadgets */
		if (win->fontdata) 	    CloseFont(win->fontdata);
		if (win->fixedfontdata)	CloseFont(win->fixedfontdata);
		if (win->win)    	    CloseWindow(win->win);
		if (win->screen) 	    UnlockPubScreen(NULL,win->screen);
		
		FreeMem(win,sizeof(struct WindowStuff));
		return(NULL);
	}
	else
	{
		UNLESS(win = AllocMem(sizeof(struct WindowStuff), MEMF_CLEAR)) return(NULL);
		
		/* Find the default public screen */

		UNLESS(win->screen = LockPubScreen(NULL)) return(SetupTrackWindow(win));

		AskFont(&win->screen->RastPort, &win->font);
 		UNLESS(win->fontdata = OpenDiskFont(&win->font)) return(SetupTrackWindow(win));

		nMinWinHeight = win->screen->WBorTop + win->screen->RastPort.TxHeight + VSPACE + 3*(win->font.ta_YSize + VSPACE + VSPACE) + win->screen->WBorBottom + VSPACE;
		nMinWinWidth  = win->screen->WBorLeft + TextLength(&win->screen->RastPort, ALL_BOTTOM_LINE_TEXT, strlen(ALL_BOTTOM_LINE_TEXT)) + (HSPACE * 25) + win->screen->WBorRight;
		
		if (nWinWidth  < nMinWinWidth)  nWinWidth = nMinWinWidth;
		if (nWinHeight < nMinWinHeight) nWinHeight= nMinWinHeight;
		
		/* Open the window */
		UNLESS(win->win = OpenWindowTags(NULL,
		 WA_Left,	        nWinLeft,
		 WA_Top,	        nWinTop,
		 WA_Width,	        nWinWidth,
		 WA_Height,	        nWinHeight,
		 WA_MinWidth,	    nMinWinWidth,
		 WA_MinHeight,	    nMinWinHeight,
		 WA_PubScreen,	    win->screen,
		 WA_PubScreenFallBack, TRUE,
		 WA_MaxWidth,	    -1,
		 WA_MaxHeight,	    -1,
		 WA_Title,	        pcDisplayVersionString, 
		 WA_CloseGadget,    TRUE,
		 WA_DepthGadget,    TRUE,
		 WA_SizeGadget,     TRUE,
		 WA_Activate,	    TRUE,
		 WA_DragBar,	    TRUE,
		 WA_SizeBBottom,    TRUE,
		 WA_Flags,          WFLG_NEWLOOKMENUS,
	     WA_IDCMP,          IDCMP_REFRESHWINDOW | IDCMP_CLOSEWINDOW | 
		 		            IDCMP_MENUPICK | IDCMP_NEWSIZE | 
		 		            IDCMP_VANILLAKEY | BUTTONIDCMP | LISTVIEWIDCMP |
		 		            CYCLEIDCMP | STRINGIDCMP,
		 TAG_DONE)) return(SetupTrackWindow(win));

		AskFont(win->win->RPort, &win->fixedfont);
 		UNLESS(win->fixedfontdata = OpenDiskFont(&win->fixedfont)) return(SetupTrackWindow(win));

		UNLESS(UpdateWindow(win,FALSE)) return(SetupTrackWindow(win));
		return(win);
	}
}


/* Creates a DisplayList from a ClientList.  
   In a DisplayList, each item is an ASCII string.
   There is a ULONG directly before the beginning of each string, 
   giving the string's length. */
struct List * CreateDisplayList(struct ClientList * oldlist)
{
	ULONG ulMaxHostnameLength = 0, ulTemp;
	struct Client * client;
	struct List * newlist;
	struct Node * newnode;
	
	UNLESS(oldlist) return(NULL);
	
	/* First allocate our new list header */
	UNLESS(newlist = AllocMem(sizeof(struct List), MEMF_CLEAR)) return(NULL);
	NewList(newlist);

	/* first find out the length of the longest hostname in the ClientList */
	client = (struct Client *) oldlist->list.lh_Head;
	while(client->node.ln_Succ)
	{
		ulTemp = strlen(client->hostname);
		if (ulTemp > ulMaxHostnameLength) ulMaxHostnameLength = ulTemp;
		client = (struct Client *)client->node.ln_Succ;
	}

	/* Now construct the DisplayList, with hostnames padded out appropriately */
	client = (struct Client *) oldlist->list.lh_Head;
	while(client->node.ln_Succ)
	{
		UNLESS(newnode = AllocDisplayItem(client->hostname, client->ulIPAddress, ulMaxHostnameLength, client->comment)) return(newlist);
		AddTail(newlist, newnode);
		client = (struct Client *)client->node.ln_Succ;
	}
	return(newlist);
}


struct Node * AllocDisplayItem(char * szHostName, ULONG ulIPAddress, ULONG ulHostFieldLen, char * szComment)
{
	struct Node * newnode;
	UBYTE * pubData;
	ULONG ulAllocLength = ulHostFieldLen + strlen(szComment) + 2;	/* 2 == space plus NUL */
	char * pcTemp, * pcTemp2;
	
	UNLESS(newnode = AllocMem(sizeof(struct Node), MEMF_ANY)) return(NULL);
	UNLESS(pubData = AllocMem(ulAllocLength + sizeof(ULONG) + sizeof(ULONG), MEMF_ANY))
	{
		FreeMem(newnode, sizeof(struct Node));
		return(NULL);
	}
	memcpy(pubData, &ulIPAddress,   sizeof(ULONG));	
	pubData += sizeof(ULONG);	/* Ignore the IPAddress ULONG from now on... */
	memcpy(pubData, &ulAllocLength, sizeof(ULONG));
	pubData += sizeof(ULONG);	/* Ignore the memalloced ULONG from now on... */
	memset(pubData, ' ', ulHostFieldLen+1);
	
	/* like strcpy but without copying the NUL terminal! */
	pcTemp = pubData; pcTemp2 = szHostName;
	while(*pcTemp2)
	{
		*pcTemp = *pcTemp2;
		pcTemp++; pcTemp2++;
	}	
	strcpy(&pubData[ulHostFieldLen+1], szComment);
	newnode->ln_Name = pubData;
	return(newnode);
}


void FreeDisplayItem(struct Node * victim)
{
	ULONG ulLen;

	memcpy(&ulLen, victim->ln_Name-sizeof(ULONG), sizeof(ULONG));
	FreeMem(victim->ln_Name-sizeof(ULONG)-sizeof(ULONG), ulLen+sizeof(ULONG)+sizeof(ULONG));
	FreeMem(victim,sizeof(struct Node));
}


/* Frees an allocated DisplayList. */
void FreeDisplayList(struct List * list)
{
	struct Node * current;
	
	UNLESS(list) return;	
	while(current = RemHead(list)) FreeDisplayItem(current);
	FreeMem(list, sizeof(struct List));
}


void debug(int n)
{
	printf("enter debug %i ... ",n); fflush(stdout);
	Delay(50);
	printf("continuing.\n"); fflush(stdout);
}

VOID wbmain(struct WBStartup *wbargv)
{    
	BStartedFromWB = TRUE;
	main(0,wbargv);
}


/* return TRUE iff the string represents the boolean value true */
BOOL ParseBool(char * string)
{
  char temp[100];
  
  strncpy(temp,string,sizeof(temp));
  temp[99]=0;
  
  ToLower(temp);
  
  return(
    (strlen(temp)==0)          ||  /* then it's just KEYWORD, so yes, right? */
    (strcmp("true",temp) == 0) ||
    (strcmp("yes",temp)  == 0) ||
    (strcmp("1",temp)    == 0));   /* Anyone who specifies 1 for true is a weenie! */
}


int main(int argc, char **argv)
{
    char * server, * comment, * cx_pop, * hotkey, * action;
    long pri, pingDelay, refDelay;
    char * fakeArgv[2];
    int i,j=0;
    
    /* fix bug when called from CLI with no args */
    if ((BStartedFromWB == FALSE)&&(argc==1))
    {
      /* fake an arg */
      argc = 2;
      argv = fakeArgv;
      
      fakeArgv[0] = argv[0];
      fakeArgv[1] = "\0";
    }
    if ((BStartedFromWB == FALSE)&&(argc>=2)&&(*argv[1]=='?'))
    {
      printf("Usage: AmiTrack SERVER/K,PORT/K/N,COMMENT/K,CX_POPUP/K,\n"
             "                CX_PRIORITY/K/N,CX_POPKEY/K,PINGDELAY/K/N,\n"
             "                REFRESHDELAY/K/N,ENABLE/K,ALLOWMULTIPLE/K\n"
             "                CONFIRMAPPLAUNCH/K,ACTION1/K,ACTION2/K,...\n"
             "                LEFT/K/N,TOP/K/N,WIDTH/K/N,HEIGHT/K/N\n");
      exit(5);
    }
    
    InitActions();
    
    atexit(Cleanup);
    
	UNLESS(IconBase = OpenLibrary("icon.library",33))
		TrackExit("Couldn't open icon.library 33+",RETURN_ERROR);
    
    /* Parse startup arguments */
    UNLESS(ttypes = ArgArrayInit(argc, argv))
        TrackExit("Couldn't read startup arguments", RETURN_ERROR);

    nWinLeft  = ArgInt(   ttypes, "LEFT",         nWinLeft);
    nWinTop	  = ArgInt(   ttypes, "TOP",          nWinTop);
    nWinWidth = ArgInt(   ttypes, "WIDTH",        nWinWidth);
    nWinHeight= ArgInt(   ttypes, "HEIGHT",       nWinHeight);
    server    = ArgString(ttypes, "SERVER",       DEFAULT_AMITRACK_SERVER);
    nPort     = ArgInt(   ttypes, "PORT",         DEFAULT_AMITRACK_PORT);
    comment   = ArgString(ttypes, "COMMENT",      DEFAULT_AMITRACK_COMMENT);
    cx_pop    = ArgString(ttypes, "CX_POPUP",     DEFAULT_AMITRACK_CXPOPUP);
    pri       = ArgInt(   ttypes, "CX_PRIORITY",  DEFAULT_AMITRACK_CXPRI);
    hotkey    = ArgString(ttypes, "CX_POPKEY",    DEFAULT_AMITRACK_POPKEY);
    pingDelay = ArgInt(   ttypes, "PINGDELAY",    DEFAULT_AMITRACK_PINGDELAY);
    refDelay  = ArgInt(   ttypes, "REFRESHDELAY", DEFAULT_AMITRACK_REFRESHDELAY);
    BAllowMultiple    = ParseBool(ArgString(ttypes, "ALLOWMULTIPLE",    DEFAULT_AMITRACK_ALLOWMULTIPLE));
    BEnabled          = ParseBool(ArgString(ttypes, "ENABLED",          DEFAULT_AMITRACK_ENABLED));
    BConfirmAppLaunch = ParseBool(ArgString(ttypes, "CONFIRMAPPLAUNCH", DEFAULT_AMITRACK_CONFIRMAPP));
    
    for (i=0; i<20; i++)
    {
      char temp[15];
      sprintf(temp,"ACTION%i",i);
      
      if (action = (ArgString(ttypes, temp, NULL)))
      {
        char * pcTemp = strchr(action,',');
        if (pcTemp)
        {
          *pcTemp = '\0';  /* temporarily separate the string */
          ReplaceAllocedString(&ActionLabels[j],action);
          ReplaceAllocedString(&Actions[j],pcTemp+1);
          *pcTemp = ',';
          j++;        
          currentAction = 0;
        }
      }
    }
    nNumActions = j;
    
    /* set globals */
    nPingInterval = nPingCount = pingDelay;
    nRefreshInterval = nRefreshCount = refDelay;

	pcDisplayVersionString = &szVersionString[6];
	BeAClient(server, PastSpaces(comment), ParseBool(cx_pop), pri, hotkey);

    /* Cleanup() automagically called here via atexit() */
}



/* Always called when AmiTrack exits, does any necessary cleanup */
void Cleanup(void)
{
    if (rexxHost) CloseDownARexxHost(rexxHost);
	if (DisplayList) FreeDisplayList(DisplayList);
    if (clientList) SetupClientList(clientList);
	if ((SocketBase)&&(UDPSocket))
	{
		if (BSendGoodbye) SendPacket(UDPSocket, REQ_BYE, NULL);
		UDPSocket = SetupSocket(UDPSocket,0,0);
	}
	if (Timer) SetupTimer(Timer);
    if (CX) SetupCxStuff(CX,0,NULL);
	if (ttypes) ArgArrayDone();    
	if (SocketBase)    CloseLibrary(SocketBase);
	if (TrackWindow)   SetupTrackWindow(TrackWindow);

    FreeActions();
	ReplaceAllocedString(&szCurrentHostname, NULL);
	ReplaceAllocedString(&szCurrentComment,  NULL);

	if (DiskFontBase)  CloseLibrary(DiskFontBase);
	if (GadToolsBase)  CloseLibrary(GadToolsBase);
	if (IntuitionBase) CloseLibrary(IntuitionBase);
	if (GraphicsBase)  CloseLibrary(GraphicsBase);
	if (WorkbenchBase) CloseLibrary(WorkbenchBase);
	if (IconBase)      CloseLibrary(IconBase);
	if (CxBase)        CloseLibrary(CxBase);	
}


BOOL ListsDifferent(struct ClientList * l1, struct ClientList * l2)
{
  struct Node * current1, * current2;
  
  if ((l1 == NULL)&&(l2 != NULL)) return(TRUE);
  if ((l2 == NULL)&&(l1 != NULL)) return(TRUE);
  if ((l1 == NULL)&&(l2 == NULL)) return(FALSE);
  if (l1->nListLength != l2->nListLength) return(TRUE);
  
  /* Okay, now check each entry for a diff */
  current1 = l1->list.lh_Head;
  current2 = l2->list.lh_Head;
  while(current1->ln_Succ)
  { 
    if (isEqual((struct Client *) current1, (struct Client *) current2) == FALSE) return(TRUE);
    current1 = current1->ln_Succ;
    current2 = current2->ln_Succ;
  }
  return(FALSE);
}

void DoRefresh(void)
{ 
	char szMessage[100];
    struct ClientList * test;
    
    UNLESS(GetSocketLib()) return;
    
	if (szCurrentHostname)
	{
		sprintf(szMessage,"Trying to get login list from %s.",szCurrentHostname);
		StatMessage(szMessage);
    
	    if (test = GetTrackList(szCurrentHostname, nPort))
		{
		    char szMessage[100];
		    
		    if (ListsDifferent(test, clientList)) lLastChangeAt = time(NULL);
		    AttachList(TrackWindow, FALSE);
		    if (DisplayList) FreeDisplayList(DisplayList);
		    DisplayList = CreateDisplayList(test);
		    AttachList(TrackWindow, TRUE);
		 
		    if (clientList) SetupClientList(clientList);  /* free the old client list */
		    clientList = test;
            SetConnectStatus(TrackWindow, TRUE);
            
            sprintf(szMessage,"Login list refreshed at %s",TimeStamp());
		    StatMessage(szMessage);
		}
		else 
		{		
		    StatMessage("Couldn't get login list from server.");
		    SetConnectStatus(TrackWindow, FALSE);
		}
	}
}

char * TimeStamp(void)
{
   static char szReturn[36];
   struct tm * time_tm = localtime(NULL);
 
   sprintf(szReturn,"%d:%02d:%02d%cm",
           time_tm->tm_hour > 12 ? (time_tm->tm_hour-12) : time_tm->tm_hour,
           time_tm->tm_min,time_tm->tm_sec,
           time_tm->tm_hour > 12 ? 'p' : 'a');
   return(szReturn);
}


/* Returns TRUE if AmiTCP is now running & available, else false. */
BOOL GetSocketLib()
{
  if (SocketBase) return(TRUE);
  if (SocketBase = OpenLibrary("bsdsocket.library", 2))
  {   
	struct hostent * localhostent;

	gethostname(szMyHostName, sizeof(szMyHostName));
	if ((szCurrentHostname)&&(strcmp(ToLower(PastSpaces(RemoveUnprintableChars(szCurrentHostname))),"localhost") == 0)) 
	{
	  ReplaceAllocedString(&szCurrentHostname,szMyHostName);
      if (TrackWindow)
      {
        /* update server field to reflect our IP address */
        GT_SetGadgetAttrs(TrackWindow->ServerString, TrackWindow->win, NULL, GTST_String, szCurrentHostname);
      }
	}
	if ((localhostent=gethostbyname(szMyHostName))&&(localhostent->h_addr_list[0]))
	  ulMyIPAddress = (ULONG) *((ULONG *)localhostent->h_addr_list[0]);
	else
	{
	  char szMessage[100];
	  sprintf(szMessage,"Couldn't get IP address for localhost (%s)",szMyHostName);
	  MakeReq(NULL,szMessage,"Oh Dear");
    }
    
    if (UDPSocket) UDPSocket = SetupSocket(UDPSocket,0,0);  /* clear old socket */
    UDPSocket = SetupSocket(NULL,0,SOCK_DGRAM);  /* Ok if returns NULL */
	if ((UDPSocket)&&(szCurrentHostname))
	{
		char szMessage[100];
		BOOL Conn;
	
		sprintf(szMessage,"Connecting to [%s], port %i",szCurrentHostname, nPort);
		StatMessage(szMessage);
	
		if ((Conn = ConnectToServer(TrackWindow, szCurrentHostname))&&(BEnabled))
			PingServer(TrackWindow, UDPSocket, szCurrentComment);		

        SetConnectStatus(TrackWindow, Conn);
	}
	return(TRUE);
  }
  StatMessage("Inactive.  No TCP stack available.");
  return(FALSE);
}


/* Act like a client.  Connect to szHostName, register yourself
   with this Amiga's IP address, and the given szComment. */
void BeAClient(char * szHostName, char * szComment,
               BOOL BOpenWindow, int cxPri, char * hotkey)
{
    lLastChangeAt = time(NULL);
    
	/* Allocate our own copies of the given strings */
	ReplaceAllocedString(&szCurrentHostname, szHostName);
	ReplaceAllocedString(&szCurrentComment,  szComment);
	 
	UNLESS(Timer = SetupTimer(NULL)) 
		TrackExit("Couldn't setup timer.device.",RETURN_ERROR);
	
	UNLESS(CxBase = OpenLibrary("commodities.library", 37L))
	    TrackExit("Couldn't open commodities.library", RETURN_ERROR);
	    				
	UNLESS(WorkbenchBase = OpenLibrary("workbench.library",37))
		TrackExit("Couldn't open icon.library 37+",RETURN_ERROR);

	UNLESS(GraphicsBase = OpenLibrary("graphics.library", 37))
		TrackExit("Couldn't open graphics.library 37+",RETURN_ERROR);

	UNLESS(IntuitionBase = OpenLibrary("intuition.library", 37))
		TrackExit("Couldn't open intuition.library 37+",RETURN_ERROR);

	UNLESS(GadToolsBase = OpenLibrary("gadtools.library", 37))
		TrackExit("Couldn't open gadtools.library 37+",RETURN_ERROR);

	UNLESS(DiskFontBase = OpenLibrary("diskfont.library", 37))
		TrackExit("Couldn't open diskfont.library 37+",RETURN_ERROR);

    UNLESS(CX = SetupCxStuff(NULL, cxPri, hotkey))
        TrackExit("Couldn't setup Commodities Broker.  (AmiTrack already running?)",RETURN_ERROR);

    rexxHost = SetupARexxHost("AMITRACK", NULL);
    
    if (BOpenWindow)
    {
	  UNLESS(TrackWindow = SetupTrackWindow(NULL))
		TrackExit("Couldn't open GUI", RETURN_ERROR);
    }
    
	SetConnectStatus(TrackWindow, FALSE);		/* Default to not connected yet */
    GetSocketLib();  /* Attempts the connection */
    
	SetTimer(Timer, 60, 0);
    SetMenuValues();

    if (BSendGoodbye) DoRefresh();
	
	while(1)
	{
	    if (TrackFlagsSet() == FALSE) 
	      TrackWait(NULL, NULL, Timer, TrackWindow, rexxHost ? rexxHost->port : NULL, CX);

		if (CheckTrackFlag(CODE_QUIT)) break;
		if (CheckTrackFlag(CODE_WINDOW_EVENT)) HandleIDCMP(TrackWindow);
		if (CheckTrackFlag(CODE_REFRESH)) DoRefresh();
		if (CheckTrackFlag(CODE_MSGPORT)) ARexxDispatch(rexxHost);
		if (CheckTrackFlag(CODE_ENABLE))
		{
          BEnabled = TRUE;
          if (GetSocketLib())
		    PingServer(TrackWindow, UDPSocket, szCurrentComment);		
          ActivateCxObj(CX->broker, 1L);
          StatMessage("AmiTrack client is enabled.");
		}
		if (CheckTrackFlag(CODE_DISABLE))
		{
		 if (BSendGoodbye)
		   SendPacket(UDPSocket, REQ_BYE, NULL);
         BSendGoodbye = FALSE;
         ActivateCxObj(CX->broker, 0L);
         StatMessage("AmiTrack client is disabled.");
		 BEnabled = FALSE;
         
         if (TrackWindow)
         {
         	AttachList(TrackWindow,FALSE);
         	PatchLists(DisplayList, clientList, ulMyIPAddress, szMyHostName, szCurrentComment, TRUE);
         	AttachList(TrackWindow,TRUE);
		 }
		}
		if (CheckTrackFlag(CODE_TIMER_EXPIRED))
		{
		   if (GetSocketLib()) 
		     DoPeriodicUpdates(TrackWindow, UDPSocket, Timer);
		}
		if (CheckTrackFlag(CODE_RECONNECT)&&(GetSocketLib()))
		{
			char szMessage[100];
            
			if (UDPSocket) 
			{
			  if (BSendGoodbye) SendPacket(UDPSocket, REQ_BYE, NULL);
			  UDPSocket = SetupSocket(UDPSocket,0,0);	
			}
			BSendGoodbye = FALSE;

			UDPSocket = SetupSocket(NULL,0,SOCK_DGRAM);

			sprintf(szMessage,"Connecting to [%s], port %i",szCurrentHostname, nPort);
			StatMessage(szMessage);
		
			if (ConnectToServer(TrackWindow, szCurrentHostname))
			{ 
				sprintf(szMessage,"Connected to %s:%i",szCurrentHostname,nPort);
				StatMessage(szMessage);
				if (BEnabled) PingServer(TrackWindow, UDPSocket, szCurrentComment);
				DoRefresh();
			}
			else
			{				
				 if (szCurrentHostname)
				 {
				 	sprintf(szMessage,"Couldn't connect to %s:%i",szCurrentHostname, nPort);
				 	StatMessage(szMessage);
				 }
				 SetConnectStatus(TrackWindow,FALSE);
			}
		}
		if (CheckTrackFlag(CODE_PING)) PingServer(TrackWindow, UDPSocket, szCurrentComment);
		if ((TrackWindow)&&(CheckTrackFlag(CODE_HIDE)))  TrackWindow = SetupTrackWindow(TrackWindow);
		if ((!TrackWindow)&&(CheckTrackFlag(CODE_SHOW))) 
		{
		  TrackWindow = SetupTrackWindow(NULL);        
		  StatMessage(NULL);  /* Show last message */
		}
        SetMenuValues();
	}
}


/* Handle the pings and updates and so on */
void DoPeriodicUpdates(struct WindowStuff * win, struct SocketStuff * sSocket, struct TimerStuff * timer)
{
	SetTimer(timer, 60, 0);  /* Do this first! */

	if ((nPingInterval > 0)&&((--nPingCount) <= 0))
	{
		if (BEnabled) PingServer(win, sSocket, szCurrentComment);
		nPingCount = nPingInterval;
	}
	if ((nRefreshInterval > 0)&&((--nRefreshCount) <= 0))
	{
		DoRefresh();
		nRefreshCount = nRefreshInterval;
	}
}
		

/* Returns FALSE on too many errors, TRUE otherwise */
BOOL PingServer(struct WindowStuff * win, struct SocketStuff * sSocket, char * szComment)
{
	static int nNumErrors;

/*    if (sSocket == NULL) return(TRUE); */ /* Why was this here? */
    UNLESS(GetSocketLib()) return(TRUE);
        		
	if (SendPacket(sSocket, REQ_COMMENT, szComment)) 
	{
 		SetConnectStatus(win, TRUE);
		if (nNumErrors >= MAX_PING_ERRORS) StatMessage("Server ping recovered!");
     	nNumErrors = 0;
	}
	else if (nNumErrors++ == MAX_PING_ERRORS)
	{	    	
		StatMessage("Error:  Server ping timeout");
		SetConnectStatus(win, FALSE);
		return(FALSE);
	}	     
	return(TRUE);
}


/* Sends a UDP packet to the server.  The first byte
   of the packet is a control byte, after that there
   is just the NULL-terminated string szComment. */
BOOL SendPacket(struct SocketStuff * sSocket, ULONG ulReqs, char * szComment)
{
	int nBufLen;
	char * pcBuf;
	BOOL BRet;

    UNLESS(GetSocketLib()) return(FALSE);

	UNLESS(szComment) szComment = "";
	nBufLen = sizeof(ULONG) + strlen(szComment) + 1;
	
	/* Allocate and fill out the transfer buffer */
	UNLESS(pcBuf = (char *)AllocMem(nBufLen, MEMF_ANY)) return(FALSE);
	memcpy(&pcBuf[0], &ulReqs, sizeof(ULONG));
	strcpy(&pcBuf[sizeof(ULONG)], szComment);
	BRet = (send(sSocket->fd, (UBYTE *)pcBuf, nBufLen, 0L) == nBufLen);
	FreeMem(pcBuf, nBufLen);
	return(BRet);
}



/* Used by the client.  Note that this connect() call probably 
   won't fail even if the server IS running on the host named
   szPeerName, because UDP sockets are connectionless, and connect()
   does nothing but affect an AmiTCP-internal setting that governs
   where send() will send to when used on this socket.  Rather,
   if something goes wrong, subsequent send() calls will return
   errors instead. */
BOOL ConnectToServer(struct WindowStuff * win, char * szPeerName)
{
	struct hostent * hp;
	char szMessage[100];

	UNLESS((szPeerName)&&(GetSocketLib()&&(UDPSocket))) return(FALSE);
	UNLESS(hp = gethostbyname(szPeerName))
	{
		sprintf(szMessage, "Name lookup failed for server [%s]",szPeerName);
		StatMessage(szMessage);
		return(FALSE);
	}
	bzero(&UDPSocket->saAddr, sizeof(struct sockaddr_in));
    	bcopy(hp->h_addr, (char *)&UDPSocket->saAddr.sin_addr, hp->h_length);
	UDPSocket->saAddr.sin_port   = htons(nPort);
	UDPSocket->saAddr.sin_family = hp->h_addrtype;

	UNLESS(connect(UDPSocket->fd, (struct sockaddr *) &UDPSocket->saAddr, sizeof(struct sockaddr_in)) >= 0)
	{
		sprintf(szMessage,"Connect to [%s] failed.",szPeerName);
		StatMessage(szMessage);
		return(FALSE);
	}
	return(TRUE);
}

void StatMessage(char * message)
{
	static char szMessage[100] = "AmiTrack ready.";
	
	if (message) strncpy(szMessage,message,sizeof(szMessage));
	szMessage[99] = 0;
		
	if ((TrackWindow)&&(TrackWindow->win)) 
	  SetWindowTitles(TrackWindow->win, szMessage, (char *)~0);
}

void SetMenuValues(void)
{
 struct Menu *currentMenu = Menu;
 struct MenuItem *currentItem;
 
 UNLESS(TrackWindow) return;
 ClearMenuStrip(TrackWindow->win);

 currentItem = currentMenu->FirstItem;	/* Enabled... */
 if (BEnabled) currentItem->Flags |= CHECKED;
          else currentItem->Flags &= ~(CHECKED); 	
 currentItem = currentItem->NextItem;   /* Confirm... */
 if (BConfirmAppLaunch) currentItem->Flags |= CHECKED;
                   else currentItem->Flags &= ~(CHECKED); 	 
 if (Actions[0]) currentItem->Flags |= ITEMENABLED;
                   else currentItem->Flags &= ~(ITEMENABLED); 	 

 ResetMenuStrip(TrackWindow->win, Menu);                 
 return;	
}

/* accepts a command string of the following format:

    "NAME,COMMAND"
    
   To insert, update, or delete an action.  e.g.
   
    "AmiPhone,sys:utilities/AmiPhone CONNECT=%s"
    
   Would add a command named AmiPhone to the cycle gadget.
   
   To delete an action, specify the name without a command.  e.g.
   
    "AmiPhone"
    
   Returns the index of the new (or removed) command, or -1 if 
   there was an error (out of memory or all 20 spots used)
   
*/ 
int UpdateActions(struct WindowStuff * win, char * string)
{
  int prevGadWidth;
  char name[75] = "", command[300] = "", *pcTemp;
  int index = -1, i;
  
  if (win) prevGadWidth = getActionCycleWidth(win);
  
  if (pcTemp = strchr(string, ',')) *pcTemp = '\0';
  strncpy(name, string, sizeof(name));
  if (pcTemp)
  {
    *pcTemp = ','; pcTemp++;
    strncpy(command, pcTemp, sizeof(command));
  }
  
  /* Check current index to see if the item is already present */
  for (i=0; ((ActionLabels[i])&&(i<20)); i++)
  {
    if (strcmp(name, ActionLabels[i]) == 0) 
    {
      index = i; 
      break;
    }
  }
  
  if (index >= 0)
  {
    /* Replace/Delete existing command */
    if (command[0]) ReplaceAllocedString(&Actions[index], command);
    else 
    {
      /* delete this entry and move the following entries back one! */
      ReplaceAllocedString(&ActionLabels[index], NULL);
      ReplaceAllocedString(&Actions[index], NULL);
      for (int i=index; i<19; i++)
      {
        ActionLabels[i] = ActionLabels[i+1];
        Actions[i]      = Actions[i+1];
      }
      Actions[19] = ActionLabels[19] = NULL;
      nNumActions--;
          
      /* Shrink cycle gadget if necessary */
      if ((win)&&(getActionCycleWidth(win) < prevGadWidth)) UpdateWindow(win, FALSE);
      SetCurrentActionIndex(win, currentAction);
      return(index);
    }
  }
  else
  {
    /* Add new command */
    if ((name[0])&&(command[0]))
    {
      for (i=0; i<20; i++)
      {
        if (ActionLabels[i] == NULL)
        {
          ReplaceAllocedString(&ActionLabels[i], name);
          ReplaceAllocedString(&Actions[i], command);
          nNumActions++;
          index = i;
          if (currentAction < 0) currentAction = 0;
          break;
        }
      }
    }
  }
  
  /* grow cycle gadget if necessary */
  if ((win)&&(getActionCycleWidth(win) > prevGadWidth)) UpdateWindow(win, FALSE);
  SetCurrentActionIndex(win, currentAction);
  return(index);
}
