#include <c.h>
#include <fcntl.h>
#include <libc.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmp.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>

#import "UDPLoadView.h"
#import "NLoadCommon.h"

@implementation UDPLoadView : LoadView

/*---------------------------------------------------------------------------
Besides creating the new frame, initialize variables to default settings.
With the addition of the RPC/UDP support, some new variables were added.
These variables, initial settings, and impact of the settings follow:

                  Initial
    Variable      Setting   Comment
    hostName      NULL      overridden later to host name,
    nErrors       MAX...    forces the view white if the first client/server
                            call fails; then records # consecutive errors,
                            turning the view white after MAXERRORS,
    udpSocket     -1        forces initial udpClient call to create socket,
    waitSeconds   WS..      default seconds to wait on remote server
                            (can be overridden by defaults data base)
-----------------------------------------------------------------------------*/

- initFrame:(const NXRect *) frameRect
{
	id result = [super initFrame:frameRect];
	
	update = getFloatDefault("RemoteUpdateSeconds");
	
	waitSecs = getIntDefault("HostsWaitSeconds");

	udpSocket = -1;
	
	return result;
}

/*---------------------------------------------------------------------------
This code is used for remote hosts that do not have RPC support.  A dedicated
load server must run on the remote hosts.  Most sites will never execute this
code.

If we do not have a UDP socket upon entry, we must go through the laborious
task of setting one up.  This requires that we create the socket, set it
up for no delay, and bind it.

Once we have a valid UDP socket, we send a tickler UDP packet to the remote
host.  We wait a short period of time for the remote host to respond.  We
expect the server to reply with a string of the form:
                "hostname load1 load5 load15 scale"
-----------------------------------------------------------------------------*/
- loadAverage:(long *)vector loadScale:(int *)scale
{
	char buf[BUFSIZ];
	char *bufPtr;

	int i;
	int nBytes;
	int newSocket;
	int temp;

	struct sockaddr_in clientSocket;
	struct hostent *hp;

	if (udpSocket < 0) {
		if ((hp = gethostbyname((char *) hostName)) == NULL) {
			(void) fprintf(stderr, "Unknown host '%s'.\n", hostName);
			return nil;
			}

		/* Set up the UDP server socket.
		 * Client and server both use udpServerPort.
		 */
		bzero((char *) &udpServerSocket, sizeof(udpServerSocket));
		udpServerSocket.sin_family = AF_INET;

		bcopy(hp->h_addr, &udpServerSocket.sin_addr.s_addr, hp->h_length);
		udpServerSocket.sin_port = htons(getIntDefault("udpServerPort"));

		/* Create the client's socket.  Set this socket for no delay so
		 * we can time out if the server is down or slow in responding.
		 * Bind the socket to appropriate protocol, host, and port.
		 */
		if ((newSocket = socket(AF_INET, SOCK_DGRAM, 0)) == CERROR) {
			(void) fprintf (stderr,"Can not open datagram socket.\n");
			return nil;
			}
	
		if (fcntl(newSocket, F_SETFL, FNDELAY) == CERROR) {
			(void) fprintf(stderr, "fcntl F_SETFL, FNDELAY error.\n");
			(void) close(newSocket);
			return nil;
			}
	
		bzero((char *) &clientSocket, sizeof(clientSocket)); /* zero out */
		clientSocket.sin_family = AF_INET;
		clientSocket.sin_addr.s_addr = htonl(INADDR_ANY);
		clientSocket.sin_port = htons(0);

		if (bind(newSocket, (struct sockaddr *) &clientSocket, sizeof(clientSocket)) == CERROR) {
			(void) fprintf (stderr, "Unable to bind the local address.\n");
			(void) close (newSocket);
			return nil;
			}

		udpSocket = newSocket; /* Success.  Finalize the deal! */
		}	

	/* Send a tickler character to the remote server to wake it up.  The
	 * server should return a string with the host name, load values, and
	 * scale value.
	 */
	if (sendto(udpSocket, "T", 1, 0, (struct sockaddr *) &udpServerSocket, sizeof(udpServerSocket)) == CERROR) {
		(void) close (udpSocket);
		udpSocket = -1;		/* Forces initialization at next entry. */
		return nil;
		}

	/* Patiently wait "waitSecs" seconds for the remote server to respond.  We
	 * throw away any packets we receive from servers that we don't expect to
	 * hear from.
	 */
	nBytes = 0;
	for (i = 0; i < waitSecs && nBytes <= 0 ; i++) {
		sleep (1);

		nBytes = recvfrom(udpSocket, buf, BUFSIZ-1, 0, NULL, &temp);

		if (nBytes > 0 && strncmp (hostName, buf, strlen(hostName)) != 0) nBytes = 0;
		}		

	if (nBytes <= 0) {
		(void) close(udpSocket);
		udpSocket = -1; /* Forces initialization at next entry. */
		return nil;
		}
      
	/* At this point we have a response from the server.  The server should
	 * have returned a string in the form "hostname load1 load5 load15 scale".
	 * Extract the values and return.
	 */
	buf[nBytes] = '\0';

	bufPtr = strtok (buf, " ");
	bufPtr = strtok (NULL, " "); /* Skip the slave's host name */

	for (i = 0; i < QUEUES; i++) {
		if (bufPtr == NULL) break;

		vector[i] = atoi(bufPtr);
		bufPtr = strtok (NULL, " ");	      
		}

	if (bufPtr == NULL) return nil;

	*scale = atoi (bufPtr);
	return self;
}

@end
