/* AmiTrackShared.c -- Shared functions between client and server */

#define __DICE_C

#define __stdargs

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <exec/types.h>
#include <exec/io.h>
#include <exec/lists.h>
#include <exec/memory.h>

#include <libraries/commodities.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/commodities_protos.h>

#include <errno.h>
#include <inetd.h>
#include <sys/types.h>

#include <proto/socket.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <netdb.h>

#include <intuition/intuition.h>

#include <pragmas/socket_pragmas.h>
#include <devices/timer.h>


#include "AmiTrackShared.h"

struct Library * TimerBase = NULL;
static ULONG trackFlags = 0L;

void TrackExit(char * szMessage, int nCode)
{
	if (nCode > 0) printf("Exiting, code %i: [%s]\n",nCode, szMessage);
	exit(nCode);
}

void SetTrackFlag(ULONG flag)
{
  trackFlags |= flag;
}

BOOL CheckTrackFlag(ULONG flag)
{
  BOOL ret = ((trackFlags & flag) != 0L);
  trackFlags &= ~flag;
  return(ret);
}

BOOL TrackFlagsSet()
{
  return(trackFlags != 0L);
}


/* Wait for incoming packets or other relevant events */
/* Returns a chord of event codes.  May exit if a CTRL-C is detected. */
void TrackWait(struct SocketStuff * sTCP, struct SocketStuff * sUDP, struct TimerStuff * Timer, struct WindowStuff * Window, struct MsgPort * msgport, struct CxStuff * cx)
{
	struct fd_set fsReadSet;
	int nMaxPort = -1;
	ULONG ulMask = 
	    (Window  ? (1L<<Window->win->UserPort->mp_SigBit) : 0L) | 
		(Timer   ? (1L<<Timer->TimerMP->mp_SigBit) : 0L) | 
		(msgport ? (1L<<msgport->mp_SigBit) : 0L) |
		(cx ? (1L<<cx->port->mp_SigBit) : 0L) |
		SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F;
		
	FD_ZERO(&fsReadSet);
	if (sUDP) {FD_SET(sUDP->fd, &fsReadSet); nMaxPort = sUDP->fd;}
	if (sTCP) {FD_SET(sTCP->fd, &fsReadSet); nMaxPort = (sTCP->fd > nMaxPort) ? sTCP->fd : nMaxPort;}
	
	/* Wait for Data to come in */
	/*         socket#     read        write   oob  timeout    other */
	if (SocketBase)
	  WaitSelect(nMaxPort+1, &fsReadSet,  NULL, NULL,    NULL, &ulMask);
	else 
	  ulMask = Wait(ulMask);
	
	if (ulMask & SIGBREAKF_CTRL_C) SetTrackFlag(CODE_QUIT);
	if (ulMask & SIGBREAKF_CTRL_D) SetTrackFlag(CODE_REFRESH);	
	if (ulMask & SIGBREAKF_CTRL_E) SetTrackFlag(CODE_HIDE);	
	if (ulMask & SIGBREAKF_CTRL_F) SetTrackFlag(CODE_SHOW);	
	if ((sUDP)&&(FD_ISSET(sUDP->fd, &fsReadSet))) SetTrackFlag(CODE_UDP_SOCKET);
	if ((sTCP)&&(FD_ISSET(sTCP->fd, &fsReadSet))) SetTrackFlag(CODE_TCP_SOCKET);
	if ((Timer)&&(ulMask & (1L<<Timer->TimerMP->mp_SigBit))) SetTrackFlag(CODE_TIMER_EXPIRED); 
	if ((Window)&&(ulMask & (1L<<Window->win->UserPort->mp_SigBit))) SetTrackFlag(CODE_WINDOW_EVENT);
	if ((msgport)&&(ulMask & (1L<<msgport->mp_SigBit))) SetTrackFlag(CODE_MSGPORT);
	if ((cx)&&(ulMask & (1L << cx->port->mp_SigBit)))
	{
	  CxMsg * msg;
	  
	  while (msg = (CxMsg *) GetMsg(cx->port))
	  {
	    ULONG msgid   = CxMsgID(msg);
	    ULONG msgtype = CxMsgType(msg);
	    ReplyMsg((struct Message *) msg);
	    
	    if (msgtype == CXM_COMMAND)
	    {
	      switch(msgid)
	      {
	        case CXCMD_DISABLE:   SetTrackFlag(CODE_DISABLE); break;
	        case CXCMD_ENABLE:    SetTrackFlag(CODE_ENABLE);  break;
	        case CXCMD_DISAPPEAR: SetTrackFlag(CODE_HIDE);    break;
	        case CXCMD_UNIQUE:    /* drop through to... */
	        case CXCMD_APPEAR:    SetTrackFlag(CODE_SHOW);    break;
	        case CXCMD_KILL:      SetTrackFlag(CODE_QUIT);    break;
	      }
	    }
	    else if (msgtype == CXM_IEVENT)
	    {
          switch(msgid)
          {
            case EVT_HOTKEY:      SetTrackFlag(CODE_SHOW);    break;
            default: printf("Warning:  I'm getting unknown CxEvents!\n");
          }
	    }
	    else printf("warning: I'm getting uncalled for CxMessages!\n");
	  }
	}
}



/* If clientList is NULL, sets up and returns a ClientList.
   If clientList is non-NULL, frees the ClientList and returns NULL. */
struct ClientList * SetupClientList(struct ClientList * ClientList)
{	
	if (ClientList)
	{
		struct Client * current;		
		while(current = (struct Client *)RemHead(&ClientList->list)) FreeClient(current);
		FreeMem(ClientList, sizeof(struct ClientList));
		return(NULL);
	}
	else
	{
		UNLESS(ClientList = AllocMem(sizeof(struct ClientList),MEMF_CLEAR)) return(NULL);
		NewList(&ClientList->list);
		return(ClientList);
	}
}






/* if ts is NULL, allocates and returns all the TimerStuff you need (or NULL on failure) */
/* If ts is non-NULL, frees everything in ts, and returns NULL. */
struct TimerStuff * SetupTimer(struct TimerStuff * ts)
{
	#ifdef __DICE_C
	/* DICE seems to have a bug in it that makes it require this...
	   otherwise tv_secs and tv_micro aren't recognized!  :( */
	struct timeval {
		ULONG tv_secs;
		ULONG tv_micro;
	};
	#endif
		
	if (ts)
	{
		/* Deallocate everything! */
		if (ts->TimerIO)
		{
			if (ts->BDevOpen) 
			{
				if (!(CheckIO((struct IORequest *)ts->TimerIO)))
        			{
        				AbortIO((struct IORequest *)ts->TimerIO);   /* Ask device to abort any pending requests */
					WaitIO((struct IORequest *)ts->TimerIO);    /* proceed when ready */
				}
		        	CloseDevice((struct IORequest *) ts->TimerIO);
		        }
        		DeleteExtIO((struct IORequest *) ts->TimerIO);
        	}
	        if (ts->TimerMP) DeletePort(ts->TimerMP);
	        FreeMem(ts, sizeof(struct TimerStuff));
	        return(NULL);
	}
	else
	{
		/* Set up the timer.device */
		UNLESS(ts = AllocMem(sizeof(struct TimerStuff), MEMF_CLEAR)) return(NULL);
		UNLESS((ts->TimerMP   = CreatePort(0,0))
	            && (ts->TimerIO   = (struct timerequest *) CreateExtIO(ts->TimerMP, (sizeof (struct timerequest))))
	            && (ts->BDevOpen  = (0 == OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)ts->TimerIO,0))))
	            	return(SetupTimer(ts));
		
		TimerBase = (struct Library *) ts->TimerIO->tr_node.io_Device;
		ts->TimerIO->tr_node.io_Message.mn_ReplyPort = ts->TimerMP;
		ts->TimerIO->tr_node.io_Command  = TR_ADDREQUEST;
		ts->TimerIO->tr_node.io_Flags 	 = 0;
		ts->TimerIO->tr_node.io_Error 	 = 0;
		ts->TimerIO->tr_time.tv_secs  	 = 0;
		ts->TimerIO->tr_time.tv_micro 	 = 0; 	
		return(ts);
	}
}



/* Removes the client entry with the hostname szHostName from the ClientList.  */
/* Returns TRUE if an item was removed, else FALSE. */
BOOL RemoveClient(struct ClientList * ClientList, char * szHostName)
{
	struct Client * client = (struct Client *) ClientList->list.lh_Head;
	
	while(client->node.ln_Succ)
	{		
		UNLESS(strcmp(szHostName, client->hostname))
		{
			/* found our victim! */
			Remove((struct Node *)client); FreeClient(client); ClientList->nListLength--;
			return(TRUE);
		}
		client = client->node.ln_Succ;
	}
	return(FALSE);
}



/* Frees the given client structure.  Safe to use even if
   not all fields were allocated */
void FreeClient(struct Client * client)
{		
	UNLESS(client) return;
	if (client->hostname) FreeMem(client->hostname, strlen(client->hostname)+1);
	if (client->comment)  FreeMem(client->comment,  strlen(client->comment)+1);
	FreeMem(client, sizeof(struct Client));
}		





/* Set/Reset timer.device to signal us in nSecs seconds + nMicros microseconds */
void SetTimer(struct TimerStuff * ts, int nSecs, int nMicros)
{
 	/* First make sure there is no previous timer pending */
        if (!(CheckIO((struct IORequest *) ts->TimerIO)))
        {
            AbortIO((struct IORequest *) ts->TimerIO);
            WaitIO((struct IORequest *) ts->TimerIO);
        }
               
        ts->TimerIO->tr_time.tv_secs  = nSecs;                                        
        ts->TimerIO->tr_time.tv_micro = nMicros;
        
        /* Start ze timer */
        SendIO((struct IORequest *)ts->TimerIO);
}



/* Find the client structure with the given hostname in the client list.
   Failing that, create one and insert it into the list.  Failing
   that, return NULL.  If szOptComment is non-NULL, it will be created/
   replaced in the returned Client. */
struct Client * GetClient(struct ClientList * ClientList, ULONG ulIPAddress, char * szHostName, char * szOptComment)
{
	struct Client * client = (struct Client *) ClientList->list.lh_Head;
	struct Client * prevClient = NULL;
	int nCompare;

	UNLESS(ulIPAddress) return(NULL);
	
	while (client->node.ln_Succ)
	{
		nCompare = strcmp(szHostName, client->hostname);
		if ((nCompare == 0)&&(ulIPAddress == client->ulIPAddress))
		{
			if (szOptComment) ReplaceAllocedString(&client->comment, szOptComment);
			return(client);
		}
		if (nCompare < 0) break;	/* Not in the list--insert it here */
		prevClient = client;
		client = (struct Client *)client->node.ln_Succ;
	}

	/* If we got here, we need to add the client to the list */
	UNLESS(client = NewClient(ulIPAddress, szHostName, szOptComment)) return(NULL);
	Insert((struct List *)ClientList, (struct Node *)client, (struct Node *)prevClient);  ClientList->nListLength++;
	return(client);
}



/* Allocate a new Client structure and initialize its fields correctly,
   or return NULL on failure. */
struct Client * NewClient(ULONG ulIPAddress, char * szHostName, char * szOptComment)
{
	struct Client * client = NULL;
	
	UNLESS(szOptComment) szOptComment = "";
	UNLESS((client 		           = AllocMem(sizeof(struct Client),  MEMF_CLEAR))
	    && (client->hostname       = AllocMem(strlen(szHostName)+1,   MEMF_ANY))
	    && (strcpy(client->hostname, szHostName))
	    && (client->comment        = AllocMem(strlen(szOptComment)+1, MEMF_ANY))
	    && (strcpy(client->comment,  szOptComment)))
	{	
		FreeClient(client);	/* cleanup any partial allocations */
		return(NULL);
	}
	client->ulIPAddress = ulIPAddress;
	client->tDateStamp  = time(NULL);
	return(client);
}



BOOL isEqual(struct Client * c1, struct Client * c2)
{
  return(
    (strcmp(c1->hostname, c2->hostname) == 0) &&
    (strcmp(c1->comment,  c2->comment)  == 0) &&
    (c1->ulIPAddress == c2->ulIPAddress));
}

/* Deallocate the old string, allocate the new string! */
/* Give it a pointer to the pointer to the old string, and a pointer to the new string */
/* Can also server to just deallocate the old, if the new string is NULL */
void ReplaceAllocedString(char ** szOldString, char * szNewString)
{
	char * pcTemp = NULL;

	UNLESS(szOldString) return;
	if (szNewString)
	{
		UNLESS(pcTemp = AllocMem(strlen(szNewString)+1, MEMF_ANY)) return;
		strcpy(pcTemp, szNewString);
	}
	if (*szOldString) FreeMem(*szOldString, strlen(*szOldString)+1);
	*szOldString = pcTemp;
	return;
}



/* If ss is NULL, allocates and returns a new SocketStuff structure.
   If ss is non-NULL, deallocates it and returns NULL. */
struct SocketStuff * SetupSocket(struct SocketStuff * ss, int nPort, LONG lSocketType)
{
	BOOL BBind = TRUE;
	
	/* If nPort is negative, use its absolute value and don't try to bind() it */
	if (nPort < 0)
	{
		nPort = -nPort;
		BBind = FALSE;
	}
	
	if (ss)
	{
		if (ss->fd >= 0) CloseSocket(ss->fd);
		FreeMem(ss, sizeof(struct SocketStuff));
		return(NULL);
	}
	else
	{
        int option = 1;
        
		UNLESS(ss = AllocMem(sizeof(struct SocketStuff), MEMF_CLEAR)) return(NULL);
		ss->lSocketType = lSocketType;

		if ((ss->fd = socket(AF_INET, ss->lSocketType, 0)) < 0) return(SetupSocket(ss,0,0));
        setsockopt(ss->fd, SOL_SOCKET, SO_REUSEADDR, (char *) &option, sizeof(option));
    	
    	bzero(&ss->saAddr, sizeof(struct sockaddr_in));
		ss->saAddr.sin_family 	   = AF_INET;
		ss->saAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	  	ss->saAddr.sin_port 	   = htons(nPort);

		if ((BBind)&&(bind(ss->fd, (struct sockaddr *) &ss->saAddr, sizeof(struct sockaddr_in)))) return(SetupSocket(ss,0,0));
		return(ss);	
	}
}




/* Downloads, allocates, and returns a ClientList if possible.
   Returns NULL on failure. */
struct ClientList * GetTrackList(char * szHostName, int nPortNum)
{
	struct ClientList * ClientList = NULL;
	struct SocketStuff * sSocket = NULL;
	struct hostent * hp;
	char * pcName, * pcComment;
	ULONG ulIPAddress;
	
	UNLESS(sSocket = SetupSocket(NULL, -nPortNum, SOCK_STREAM)) return(NULL);
	UNLESS(ClientList = SetupClientList(NULL)) 
	{
		SetupSocket(sSocket,0,0);
		return(NULL);
	}
	UNLESS(hp = gethostbyname(szHostName))
	{
		SetupSocket(sSocket,0,0);
		return(NULL);
	}
	bzero(&sSocket->saAddr, sizeof(struct sockaddr_in));
    	bcopy(hp->h_addr, (char *)&sSocket->saAddr.sin_addr.s_addr, hp->h_length);
    	sSocket->saAddr.sin_family = hp->h_addrtype;
    	sSocket->saAddr.sin_port   = htons(nPortNum);

	if (connect(sSocket->fd, (struct sockaddr *) &sSocket->saAddr, sizeof(struct sockaddr_in)) < 0)
	{
		SetupSocket(sSocket,0,0);
		SetupClientList(ClientList);
		return(NULL);
	}
	
	/* Now just read & record! */
	while(FillInfos(sSocket, &pcName, &ulIPAddress, &pcComment)) GetClient(ClientList, ulIPAddress, pcName, pcComment);

	SetupSocket(sSocket, 0, 0);
	return(ClientList);
}


/* Returns TRUE if *pSetName and *pSetComment are valid, else FALSE on error. */
BOOL FillInfos(struct SocketStuff * sSocket, char **pSetName, ULONG * ulIPAddress, char **pSetComment)
{
	static char szBuf[1000];
	ULONG nIndex = 0L;
	int nReadULONG = 0;
	char * pcIPBinary = (char *) ulIPAddress;
		
	*pSetName = szBuf;
	*pSetComment = NULL;
	*ulIPAddress = 0L;
	
	while(1)
	{
		if (recv(sSocket->fd, &szBuf[nIndex], 1, 0L) <= 0) return(FALSE);
		if (nReadULONG)	 /* we read the IP address as 4 binary bytes */
		{
			*pcIPBinary = szBuf[nIndex]; pcIPBinary++;
			UNLESS(--nReadULONG) return(TRUE);
		}
		else if (szBuf[nIndex] == '\0')
		{
		     if (*pSetComment) nReadULONG   = sizeof(ULONG);
		     	  	  else *pSetComment = &szBuf[nIndex+1];
		}
		
		if ((++nIndex) >= sizeof(szBuf)) return(FALSE);
	}
}


void ClearTrackScreen(void)
{
	printf("%c",12);
}

/* Server's output to stdout for the local user to see. */
void PrintClientList(struct ClientList * ClientList)
{
	struct Client * client = (struct Client *) ClientList->list.lh_Head;
	int nTemp = 0;
	time_t tTime = time(NULL);
	
	printf(" Num | Age | Hostname                  | Comment\n");
	  printf("-----+-----+---------------------------+----------------------\n");
	while (client->node.ln_Succ)
	{
		printf("#%03i | %03lu | %-25s | %s\n", 
			nTemp,tTime - client->tDateStamp, 
			client->hostname,client->comment);
		client = client->node.ln_Succ;
		nTemp++;
	}
	putchar('\n');
}


int MakeReq(char *sTitle, char *sText, char *sGadgets)
{
	struct EasyStruct myreq;
	int nResult;

	UNLESS(sTitle)   sTitle   = "AmiTrack Message";
	UNLESS(sText)    sText    = "Check this out!";
	UNLESS(sGadgets) sGadgets = "OK";

	myreq.es_TextFormat   = sText;
	myreq.es_Title        = sTitle;
	myreq.es_GadgetFormat = sGadgets;

	nResult = EasyRequest(NULL, &myreq, NULL, NULL, NULL);

	return(nResult);
}


/* Returns a pointer to the first non-space character in the string */
char * PastSpaces(char * pcString)
{
	while(1) 
	{
		if (*pcString == ' ') pcString++;
				 else return(pcString); 
	}
}


/* Chops off any trailing blanks and returns the string */
char * RemoveTrailingSpaces(char * pcString)
{
	char * pcTemp = strchr(pcString,'\0');
	
	pcTemp--;
	while((pcTemp > pcString)&&(*pcTemp == ' ')) pcTemp--;
	pcTemp++;
	
	*pcTemp = '\0';
	
	return(pcTemp);
}


/* Replaces all unprintable characters in pcString with ' ' */
/* Hopefully this will stop people from sending e.g. "talkbombs" */
/* Returns pcString. */
char * RemoveUnprintableChars(char * pcString)
{
	UBYTE * pcTemp = pcString;
	
	while(*pcTemp)
	{
		if (*pcTemp < ' ') *pcTemp = ' ';
		pcTemp++;
	}
	return(pcString);
}


/* Makes pcString lower case */
char * ToLower(char * pcString)
{
	char * pcTemp = pcString;
	
	while(*pcTemp) 
	{
		if ((*pcTemp >= 'A')&&(*pcTemp <= 'Z'))
			*pcTemp += 'a'-'A';
		pcTemp++;
	}
	return(pcString);
}