//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// NodeUtil.cpp --  the CRAM node manager class' guts ////
//
// This is where the nitty-gritty of CRAM node management takes place.
//

#include "Node.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
using namespace cram;

//// Core Node manager operations

// Create a new service given a service name, a shell command, and an arg string

int Node :: create_service(char * name, char * command, char * arguments) {
	int child, argc = 0;
	char new_args[256], lportS[8], eportS[8], * argv[16];
	unsigned short lport = guess_port(0);
	unsigned short eport = guess_port(lport);
	if ((lport == 0) || (eport == 0)) {			// if we can't find a port to use
		mLog.log_msg(kLogError, "Cannot find any open ports");
		return CRAM_PROCESSING_ERROR;
	}
	sprintf(lportS, "%d", lport);
	sprintf(eportS, "%d", eport);
	argv[argc++] = command;						// set up the default command-line arguments
	argv[argc++] = name;
	argv[argc++] = lportS;
	argv[argc++] = eportS;
	if (arguments && (*arguments != '\0')) {	// copy the arg string into an argv[] array for execv
		strcpy(new_args, arguments);
		argv[argc++] = new_args;
		char * in_arg = new_args;
		unsigned arglen = strlen(new_args);
		for (unsigned i = 0; i < arglen; i++, in_arg++) {
			if ( *in_arg == ' ') {				// at every space, put a null and store ptr into argv
				*in_arg = '\0';
				argv[argc++] = in_arg + 1;
			}
		}
		argv[argc] = NULL;
	}
	mLog.log_msg("[Creating service \"%s\" as \"%s\" with_args \"%s\" (argc = %d)]", command, name, arguments, argc);
	char logStr[128] = "";
	for (int i = 0; i < argc; i++)
		sprintf( & logStr[strlen(logStr)], "%s ", argv[i]);
	mLog.log_msg(logStr);
//	for (int i = 0; i < argc; i++)
//		printf("\t\t%d - %s\n", i, argv[i]);
	child = fork();					// FORK
	if (child == 0) {					// if we're the child
	
		execvp(command, argv);			// exec the command, using $PATH to search for it
										// this doesn't return
		perror("Error exec'ing new command");
	} else {							// else, we're the parent
//		printf("\tFork command %s = pid %d\n", command, child);

		mLog.log_msg("Fork service %s command %s = pid %d", name, command, child);
										// Create and store the service record
										// record: name, port, type, sequence, heartrate, lastBeat, pid
		ServiceRecord * newService = new ServiceRecord(name, lport, eport, child);
		mServices.push_back(newService);
										// if I'm careful and ping newly created services
		if (mPingNewServices) {
			sleep(PING_NEW_SVC_DELAY);  // let it wake up, then ping it
			int reply = send_to_service(CRAM_PING, name);
			if (reply != CRAM_AOK) {
				printf("Error: ping responded %d\n", reply);
				return CRAM_PROCESSING_ERROR;
			}
		}
	}
	return CRAM_AOK;
}

// Get the log text of the named service

int Node :: get_service_log(char * name, char * dest, unsigned short maxChars) {
	ServiceRecord * svc = find_service(name);
	if (svc == NULL) {
		mLog.log_msg(kLogError, "Cannot find service named \"%s\"", name);
		return CRAM_NO_SERVICE_ERROR;
	}
	if (maxChars > CRAM_MAX_MSG)	// max packet size
		return CRAM_ARGUMENT_ERROR;
							// create a large buffer for the log
//	printf("Get %d bytes of log from service named: \"%s\"\n", maxChars, name);
							// copy a packet header into it
	CRAMPacketHeader * pkt = (CRAMPacketHeader *) dest;
	pkt->magic = CRAM_PACKET_MAGIC | CRAM_GET_LOG;
	pkt->size = CRAM_HEADER_SIZE;
	pkt->sequence = svc->mSequence;
	svc->incr_sequence();
	pkt->value = maxChars;
							// send the request to the client service
	send_packet(dest, pkt->size, pkt->size, svc->mLPort, true);
	return CRAM_AOK;
}

// Get the tail of the log text of the named service

int Node :: get_service_log_after(char * name, char * dest, unsigned pos) {
	ServiceRecord * svc = find_service(name);
	if (svc == NULL) {
		mLog.log_msg(kLogError, "Cannot find service named \"%s\"", name);
		return CRAM_NO_SERVICE_ERROR;
	}						// copy a packet header into it
	CRAMPacketHeader * pkt = (CRAMPacketHeader *) dest;
	pkt->magic = CRAM_PACKET_MAGIC | CRAM_GET_LOGAFTER;
	pkt->size = CRAM_HEADER_SIZE;
	pkt->value = pos;
	pkt->sequence = svc->mSequence;
	svc->incr_sequence();
							// send the request to the client service
	send_packet(dest, pkt->size, pkt->size, svc->mLPort, true);
	return CRAM_AOK;
}

// Get the options dictionary of the given service

int Node :: get_service_options(char * name, char * dest) {
	ServiceRecord * svc = find_service(name);
	if (svc == NULL) {
		mLog.log_msg(kLogError, "Cannot find service named \"%s\"", name);
		return CRAM_NO_SERVICE_ERROR;
	}						// copy a packet header into it
	CRAMPacketHeader * pkt = (CRAMPacketHeader *) dest;
	pkt->magic = CRAM_PACKET_MAGIC | CRAM_GET_OPTS;
	pkt->size = CRAM_HEADER_SIZE;
	pkt->sequence = svc->mSequence;
	svc->incr_sequence();
							// send the request to the client service
	send_packet(dest, pkt->size, pkt->size, svc->mLPort, true);
	return CRAM_AOK;
}

// Service list utilities

// Find the service with the given name

ServiceRecord * Node :: find_service(char * name) {
	list<ServiceRecord *>::iterator it;

	for (it = mServices.begin(); it != mServices.end(); ++it) {
		if (strcmp(name, ( * it)->mName) == 0)
			return ( * it);			// found it
	}
	printf("Cannot find service named \"%s\"\n", name);
	return NULL;
}

// Find the service with the given process id

ServiceRecord * Node :: find_service(int pid) {
	list<ServiceRecord *>::iterator it;

	for (it = mServices.begin(); it != mServices.end(); ++it) {
		if (( * it)->mPid == pid)
			return ( * it);			// found it
	}
	printf("Cannot find service numbered \"%d\"\n", pid);
	return NULL;
}

// Remove the service with the given name

void Node :: remove_service(char * name) {
	list<ServiceRecord *>::iterator it;

	for (it = mServices.begin(); it != mServices.end(); ++it)
		if ( ! strcmp(name, ( * it)->mName)) {
			mServices.erase(it);		// found it
			return;
		}
}

// Socket utilities

// Send a command to the named service (for the case that only the return value is of interest)

int Node :: send_to_service(unsigned command, char * name) {
	ServiceRecord * svc = find_service(name);
	if (svc == NULL)
		return CRAM_NO_SERVICE_ERROR;
	CRAMPacketHeader pkt;
	pkt.magic = CRAM_PACKET_MAGIC | command;
	pkt.size = CRAM_HEADER_SIZE;
	pkt.sequence = svc->mSequence;
	svc->incr_sequence();
	int answer = send_packet((char *) & pkt, pkt.size, pkt.size, svc->mLPort, true);
	if (answer != CRAM_AOK)
		return CRAM_PROCESSING_ERROR;
	return CRAM_AOK;
}

// Send the given command to the service named in the given packet; 
// get a single packet response (overwriting the argument)

int Node :: send_to_service(unsigned command, CRAMPacketHeader * pkt) {
	char name[CRAM_SMALL_MSG];
	int answer = scan_packet(pkt, "s", name);		// get the name form the packet
	if (answer != CRAM_AOK)
		return CRAM_ARGUMENT_ERROR;
	ServiceRecord * svc = find_service(name);		// find the named service in the mServices list
	if (svc == NULL)
		return CRAM_NO_SERVICE_ERROR;
	pkt->magic = CRAM_PACKET_MAGIC | command;		// set up the packet to send
	pkt->size = CRAM_HEADER_SIZE;
	pkt->sequence = 1;
	answer = send_packet((char *) pkt, pkt->size, pkt->size, svc->mLPort, true);	// send it
	if (pkt->value != CRAM_AOK)
		return CRAM_PROCESSING_ERROR;
	if (answer != CRAM_AOK) {
		pkt->value = CRAM_PROCESSING_ERROR;
		return CRAM_PROCESSING_ERROR;
	}
	return CRAM_AOK;
}

// Send a packet to the given port and get a response (if wait).
// Overwrite the given packet with the response.

int Node :: send_packet(char * pkt, unsigned send_size, unsigned recv_size, unsigned short port, bool wait) {
	int sock;
	struct sockaddr_in sname;
	int xfer;
	int nsize = sizeof(sname);

	try {
		sock = socket(PF_INET, SOCK_DGRAM, 0);
		if (sock < 0) {
			return CRAM_IO_ERROR;
		}						// Send
		SET_IP_ADDR( (& sname), port, INADDR_ANY);
						//
						// Shouldn't this be a multi-write loop using safe_send???
						//
//		xfer = sendto(sock, (void *) pkt, send_size, 0, (struct sockaddr *) & sname, sizeof(sname));
		xfer = safe_send(sock, pkt, send_size, (struct sockaddr *) & sname);
		if (xfer != (int) send_size) {
			mLog.log_msg(kLogError, "%s: safe_send in send_packet returned: %d", mName, xfer);
			perror("Error writing to socket");
			close(sock);
			return CRAM_IO_ERROR;
		}
		if ( ! wait)
			return CRAM_AOK;
								// Receive
		xfer = safe_recv(sock, pkt, recv_size, (struct sockaddr *) & sname, & nsize, CRAM_SELECT_TIME, 1);
	//	xfer = recvfrom(sock, (void *) pkt, recv_size, 0, (struct sockaddr *) & sname, & nsize);

		if (xfer < (ssize_t) CRAM_HEADER_SIZE) {	// expect at least a header packet back
			mLog.log_msg(kLogError, "%s: safe_recv in send_packet returned: %d", mName, xfer);
			perror("Error reading from socket");
			close(sock);
			return CRAM_IO_ERROR;
		}
	//	CRAMPacketHeader * resp = (CRAMPacketHeader *) pkt;
	//	printf("Send_packet received %d bytes; status = %d\n", xfer, resp->size);
	//	close(sock);
	} catch (int err) {
		mLog.log_msg(kLogError, "Exception in send_packet: %d\n", err);
		return CRAM_IO_ERROR;
	}
	return CRAM_AOK;
}

// Node operations

// Get the system statistics -- WARNING: KLUDJ -- I ought to get the source for "top" and do this better
//
// The output of "top -l 2 1 | tail -9" should look like:
/*
	Processes:  66 total, 3 running, 63 sleeping... 159 threads            17:47:40
	Load Avg:  1.41, 1.16, 1.06     CPU usage:  82.4% user, 17.6% sys, 0.0% idle
	SharedLibs: num =  116, resident = 37.1M code, 3.76M data, 10.1M LinkEdit
	MemRegions: num =  9852, resident =  195M + 17.8M private,  221M shared
	PhysMem:  93.7M wired,  205M active,  637M inactive,  936M used, 87.4M free
	VM: 6.29G + 86.7M   97312(5) pageins, 848(0) pageouts
*/

void Node :: get_statistics() {
	char line[90];
	FILE * output = popen ("top -l 2 1 | tail -9", "r");
//	sleep(2);
	fgets(line, 90, output);				// skip first line
	fgets(line, 90, output);				// 2nd line: read the "76.0% idle"
	printf("Stats: line = %s", line);
	char * str1 = strrchr(line, ',');		// find last ',' in line
	mStatistics.cpu = 100.0 - atof(str1 + 1);
	printf("Stats: CPU = %g\n", mStatistics.cpu);
	fgets(line, 90, output);				// skip a few lines
	fgets(line, 90, output);
	fgets(line, 90, output);				// 4th line: read the "450M used"
	printf("Stats: line = %s", line);
	str1 = (& line[strlen(line)]) - 1;
	while ( * str1-- != 'M') ;				// kludj to find space before 2nd-to-last 'M' in the line
	while ( * str1-- != 'M') ;
	while ( * str1-- != ' ') ;
	mStatistics.mem = atof(str1 + 1);
	printf("Stats: Mem = %g\n", mStatistics.mem);
	pclose(output);
}

// Get the system time in msec

int Node :: get_time() {
	struct timeval * now;
	gettimeofday(now, 0);
	unsigned int value = (unsigned int) ((now->tv_sec * 1000L) + (now->tv_usec / 1000L));
	return value;
}

// Answer an estimate of the node's MIPS performance (very rough)

#define dim1	4
#define dim2	(128 * 1000)
#define reps	16

short Node :: benchmark_MIPS() {				
	int i, j, k;						// counters
	int * arrs[dim1], value;				// large arrays
	time_t tim = time(NULL);
	int itim = (int) tim;
	struct timeval start, stop;
	for (j = 0; j < dim1; j++)				// allocate the arrays
		arrs[j] = (int *) malloc(dim2 * sizeof(int));
	gettimeofday( & start, NULL);
	for (i = 0; i < reps; i++) {				// repeat this a few times
		for (j = 0; j < dim1; j++) {		// fill the arrays with stuff
			for (k = 0; k < dim2; k++) {
				value = (itim / 10000) + (j * k) + i - j;
				arrs[j][k] = value;
	} } }
	gettimeofday( & stop, NULL);
	for (j = 0; j < dim1; j++)				// free the arrays
		free(arrs[j]);
	long elapsed = (((stop.tv_sec - start.tv_sec) * 1000000) + (stop.tv_usec - start.tv_usec));
	return (short) ((30000000L * reps) / elapsed);	// pretty arbitrary magic numbers
}

// Answer an estimate of the node's MFLOPS performance (very rough)

short Node :: benchmark_MFLOPS() {
	int i, j, k;						// counters
	float * arrs[dim1], value;				// large arrays
	time_t tim = time(NULL);
	float ftim = (float) tim;
	struct timeval start, stop;
	for (j = 0; j < dim1; j++)				// allocate the arrays
		arrs[j] = (float *) malloc(dim2 * sizeof(float));
	gettimeofday( & start, 0);
	for (i = 0; i < reps; i++) {				// repeat this a few times
		for (j = 0; j < dim1; j++) {		// fill the arrays with stuff
			for (k = 0; k < dim2; k++) {
				value = (ftim / 10000.0) + ((float) j * (float) k) + (float) i - (float) j;
				arrs[j][k] = value;
	} } }
	gettimeofday( & stop, 0);
	for (j = 0; j < dim1; j++)				// free the arrays
		free(arrs[j]);
	long elapsed = (((stop.tv_sec - start.tv_sec) * 1000000) + (stop.tv_usec - start.tv_usec));
	return (short) ((80000000L * reps) / elapsed);	// pretty arbitrary magic numbers
}

// The heartbeat loop thread process

extern "C" void * heartbeat_loop (void * inst) {
/*	int count = 1;
	Node * theNode = (Node *) inst;		// the object pointer
	Logger * themLog = theNode->getmLogger();
	CRAMPacketHeader pkt;
	unsigned val, delay;
	
	// loop through the node's services
	// send ping to one's with heartbeats
	pkt.magic = CRAM_PACKET_MAGIC | CRAM_HEARTBEAT;		// heartbeat packet 
	pkt.size = theProg->get_id();
	pkt.sequence = count++;
	themLog->log_msg("\t[Starting heartbeat loop on %x/%d]\n", name.sin_addr.s_addr, name.sin_port);
	while (1) {			// LOOP
		list<ServiceRecord *>::iterator it;
	
		for (it = mServices.begin(); it != mServices.end(); ++it)
		printf("\t[Sending heartbeat %d]\n", count);		// send a packet
		theNode->send_to_service(unsigned command, char * name)
		if (val < CRAM_HEADER_SIZE)
			perror("Could not write to socket");
		sleep(delay);
		pkt.sequence = count++;
	}
	*/
	return NULL;
}

