//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// Node.cpp --  the CRAM node manager class constructor and handle_packet() loop ////
//
// 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;

// heartbeat loop thread function prototype

extern "C" void * heartbeat_loop (void * inst);

// Node Constructor, etc.

Node :: Node(char * name, unsigned short lport, unsigned short eport) 
			: Server(name, lport, eport),
			mNextPort(CRAM_SERVICE_PORT), 
			mParanoia(1), 
			mHeartThread(0), 
			mReuseAddresses(REUSE_ADDRESSES), 
			mGuessPorts(GUESS_PORTS),
			mPingNewServices(PING_NEW_SERVICES) { 
	mStatus = CRAM_ON;
	mOptions.push_back (make_pair (string("guess_ports"), string("true")));
	mOptions.push_back (make_pair (string("reuse_addresses"), string("false")));
//	printf("\t[Node instance created]\n"); fflush(stdout);
}

// On delete, kill all services

Node :: ~Node() {
	if (mHeartThread) 
		pthread_kill(mHeartThread, SIGINT);
#ifdef NODE_KILLS_SERVICES
	list<ServiceRecord *>::iterator it;
	for (it = mServices.begin(); it != mServices.end(); ++it)
		send_to_service(CRAM_STOP, ( * it)->mName);
#endif
}

// Handle_packet processes the command packets specific to nodes.
// It forwards to the superclass (Program) for packets it doesn't handle.
// Return the # of bytes to write back as response

int Node :: handle_packet(in_addr_t sender, CRAMPacketHeader * pkt) {
	int answer, sstatus, offset;				// temp variables
	short sh_answer;
	ServiceRecord * svc;
	list<ServiceRecord *>::iterator iter;
	char cmd[CRAM_SMALL_MSG], 					// temp strings
		name[CRAM_SMALL_MSG], 
		args[CRAM_SMALL_MSG];
	char * buffer = (char *) pkt;				// char * pointer to packet
	
	int op = pkt->magic & 0x000000ff;			// get command byte out of header
	int ret_val = CRAM_HEADER_SIZE;				// default return = 12 bytes (i.e., echo packet back to sender)

	switch (op) {				//// The big command switch
	
					//// create/start/stop/suspend/resume: basic service management
					
	case CRAM_CREATE_SVC:		// Create service; args = 3 strings: name/cmd/args
//		mLog.log_msg("[Create service]");
		if (pkt->size <= CRAM_HEADER_SIZE + 10) {
oops:		mLog.log_msg(kLogError, "ERROR parsing create_service command arguments");
			pkt->value = CRAM_ARGUMENT_ERROR;	// signal a command parsing error
			break;
		}										// get pointers to 4 null-terminated strings from packet
		answer = scan_packet(pkt, "sss", name, cmd, args);
		if (answer != CRAM_AOK)
			goto oops;		
		answer = create_service(name, cmd, args);	// create the new service program!
		if (answer == CRAM_AOK)
			pkt->value = CRAM_AOK;
		else
			pkt->value = CRAM_PROCESSING_ERROR;
		pkt->size = CRAM_HEADER_SIZE;
//		printf("\tCreate returning %d\n", pkt->value);
		break;
		
	case CRAM_PING_SVC:			// ping service command; arg = string service name
//		mLog.log_msg("[Ping service]");
		answer = send_to_service(CRAM_PING, pkt);
		break;
		
	case CRAM_TEST_SVC:			// Test service command; arg = string service name
//		mLog.log_msg("[Test service]");
		answer = send_to_service(CRAM_TEST, pkt);
	//	if (answer == CRAM_AOK)
	//		pkt->value = CRAM_AOK;			// ...
		break;

	case CRAM_INIT_SVC:		// init service command; arg = string service name
//		mLog.log_msg("[Initialize service]");
		answer = send_to_service(CRAM_INITIALIZE, pkt);	// send the init command to the service
	//	printf("\t\tInit returning %d\n", answer);
		pkt->value = answer;
		break;
		
	case CRAM_START_SVC:		// start service command; arg = string service name
//		mLog.log_msg("[Start service]");
		answer = send_to_service(CRAM_START, pkt);		// send the start command to the service
	//	printf("\t\tStart returning %d\n", answer);
		pkt->value = answer;
		break;
		
	case CRAM_STOP_SVC:			// stop service command; arg = string service name
//		mLog.log_msg("[Stop service]");
		answer = scan_packet(pkt, "s", name);			// find the service name in the command packet
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			mLog.log_msg(kLogError, "getting service name in stop command");
			break;		
		}
		svc = find_service(name);						// locate the service
		if (svc == NULL) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			mLog.log_msg(kLogError, "getting service instance in stop command");
		//	break;
		}
		answer = send_to_service(CRAM_STOP, pkt);		// send the stop command to the service
		if (answer != CRAM_AOK) {
			mLog.log_msg(kLogError, "stopping service; will use kill() instead");
			if (svc != NULL) {
				answer = kill(svc->mPid, SIGINT);		// if necessary, just kill it
				if (answer)
					kill(svc->mPid, SIGKILL);
			}
		}
		if (svc != NULL) {
			remove_service(name);						// remove and delete...
			delete svc;
		}
		break;

	case CRAM_SUSPEND_SVC:			// suspend service command; arg = string service name
//		mLog.log_msg("[Suspend service]");
		answer = send_to_service(CRAM_SUSPEND, pkt);
		break;
		
	case CRAM_RESUME_SVC:			// resume service command; arg = string service name
//		mLog.log_msg("[Resume service]");
		answer = send_to_service(CRAM_RESUME, pkt);
		break;	
		
	case CRAM_GET_SVC_LOG:			// Get a service's log; arg = string service name, short length
		sh_answer = pkt->value;
		answer = scan_packet(pkt, "s", name);
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}
//		mLog.log_msg("[Get service log for \"%s\"]", name);
		try {
			answer = get_service_log(name, buffer, sh_answer);	// get the service's log
		} catch (int exc) {
			mLog.log_msg(kLogError, "[Exception getting service log from \"%s\": %d", name, exc);
		}
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			mLog.log_msg(kLogError, "while getting service log");
			break;		
		}
		ret_val = pkt->size;						// send back all the data retrieved from the service
		break;
		
	case CRAM_GET_SVC_AFTER:			// Get the tail of a service's log; arg = string service name, short length
//		mLog.log_msg("[Get service log tail]");
		sh_answer = pkt->value;
		answer = scan_packet(pkt, "s", name);
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}
		try {
			answer = get_service_log_after(name, buffer, sh_answer);	// get the service's log
		} catch (int exc) {
			mLog.log_msg(kLogError, "[Exception getting service log from \"%s\": %d", name, exc);
		}
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			mLog.log_msg(kLogError, "while getting service log");
			break;		
		}
		ret_val = pkt->size;						// send back all the data retrieved from the service
		break;
		
	case CRAM_GET_SVC_OPTS:			// get options message: respond with the opts
//		mLog.log_msg("[Get service options]");
		sh_answer = pkt->value;
		answer = scan_packet(pkt, "s", name);
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}
		try {
			answer = get_service_options(name, buffer);		// get the service's options dictionary
		} catch (int exc) {
			mLog.log_msg(kLogError, "[Exception getting service log from \"%s\": %d", name, exc);
		}
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			mLog.log_msg(kLogError, "while getting service options");
			break;		
		}
	//	char k1[16], v1[16], k2[16], v2[16];
	//	scan_packet(pkt, "ssss", k1, v1, k2, v2);
	//	printf("\t%s = %s\t\t%s = %s\n", k1, v1, k2, v2);
		ret_val = pkt->size;						// send back all the data retrieved from the service
		break;	

	case CRAM_SET_SVC_OPTS:			// get options message: respond with the opts
//		mLog.log_msg("[Set service options]");
		answer = send_to_service(CRAM_SET_OPTS, pkt);
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}
		ret_val = pkt->size;						// send back all the data retrieved from the service
		break;	

	case CRAM_LIST_SVCS:			// list services command; no args, answer array of service names
		LOG_CHECK_SIZE("[List services]", CRAM_HEADER_SIZE);
		offset = CRAM_HEADER_SIZE;					// iterate through the services writing their names into the response packet
		for (iter = mServices.begin(); iter != mServices.end(); ++iter) {
			strcpy( & buffer[offset], ( * iter)->mName);
	//		printf("Found service named: \"%s\"\n", ( * it)->_name);
			offset += (strlen(( * iter)->mName) + 1);
		}
		answer = CRAM_PACKET_MAGIC;		// write footer onto packet
		memcpy((void *) (& buffer[offset]), (void *) & answer, 4);
	//	printf("Service list response packet size: %d\n", offset + 4);
		pkt->size = offset + 4;
		ret_val = pkt->size;					// set the return packet size
		break;
		
						//// Service accessing and queries		
	case CRAM_WATCH_SVCs:			// start heartbeat message: get the port num and period and fork a new thread
		LOG_CHECK_SIZE("[Start Heartbeat]", (CRAM_HEADER_SIZE + 6));
		sh_answer = pkt->value;
		answer = scan_packet(pkt, "s", & name);
		if (answer != CRAM_AOK) {
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}
		mMgrIP = sender;
		if (mHeartThread) {					// if thread is running, kill it
			pthread_kill(mHeartThread, SIGINT);
			mHeartThread = 0;
		}
		mLog.log_msg("\t[Start heartbeat sent to %s every %d sec]", name, sh_answer);
		// get named svc
		// set its heartrate record count
		if ( ! mHeartThread) {
			int err = pthread_create( & mHeartThread, NULL, heartbeat_loop, (void *) this);
			if (err !=0 )
				mLog.log_msg("Error starting CRAM program heartbeat thread %d\n", err);
		}
		break;

	case CRAM_PING_NODE:			// Ping a the node manager (respond with intersection of service status)
		LOG_CHECK_SIZE("[Ping]", CRAM_HEADER_SIZE);
		if ( ! mParanoia) {
			pkt->value = mStatus;
			break;
		}
		sstatus = CRAM_ON;						// get the highest cached service status
		for (iter = mServices.begin(); iter != mServices.end(); ++iter) {
			answer = ( * iter)->mStatus;
			if (answer > CRAM_SUSPENDED)
				sstatus = answer;
		}
		pkt->value = sstatus;
		break;
		
	case CRAM_PING_CAREFUL:			// Ping a the node manager (respond after getting status of each service)
		break;
		
						//// Node properties and statistics
	case CRAM_GET_TIME:				// get time command; no args, answer msec clock
		LOG_CHECK_SIZE("[Get time]", CRAM_HEADER_SIZE);
#ifdef SUPPORT_GET_TIME
		answer = get_time();
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE]), (void *) & answer, 4);
		answer = CRAM_PACKET_MAGIC;				// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 4]), (void *) & answer, 4);
		pkt->size = CRAM_HEADER_SIZE + 8;
		ret_val = pkt->size;					// set the return packet size
#endif
		break;
		
	case CRAM_NODE_BENCH:			// get_performance command; answer 2 shorts for MIPS and MFLOPS
		LOG_CHECK_SIZE("[Run benchmarks]", CRAM_HEADER_SIZE);
		mLog.log_msg("[]");
		sh_answer = benchmark_MIPS();
	//	printf("MIPS: %d\n", sh_answer);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE]), (void *) & sh_answer, 2);
		sh_answer = benchmark_MFLOPS();
	//	printf("MFLOPS: %d\n", sh_answer);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 2]), (void *) & sh_answer, 2);
		answer = CRAM_PACKET_MAGIC;				// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 4]), (void *) & answer, 4);
		pkt->size = CRAM_HEADER_SIZE + 8;
		ret_val = pkt->size;					// set the return packet size
		break;
		
	case CRAM_GET_STATS:			// get statistics command; no args, answer 4 bytes
		LOG_CHECK_SIZE("[Get statistics]", CRAM_HEADER_SIZE);
#ifdef SUPPORT_GET_STATS
		get_statistics();						// get the system statistics
	//	printf("Stats: CPU: %g MEM: %g\n", _statistics.cpu, _statistics.mem);
		sh_answer = (short) (mStatistics.cpu);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE]), (void *) & sh_answer, 2);
		sh_answer = (short) (mStatistics.mem);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 2]), (void *) & sh_answer, 2);
		answer = CRAM_PACKET_MAGIC;				// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 4]), (void *) & answer, 4);
		pkt->size = CRAM_HEADER_SIZE + 8;
		ret_val = pkt->size;					// set the return packet size
#endif
		break;

	default:				//// if command not understood here, pass it up to Program's switch
		return (Program :: handle_packet(sender, pkt));
		
	}											// end of switch
	return ret_val;			// return value is # of bytes to write back as response
}

// Set node-specific options

void Node :: set_opt(const char * nam, const char * val) {
	
	mLog.log_msg("[Configure %s = %s]", nam, val);
	
 	if ( ! strcmp(nam, "guess_ports")) {
		mGuessPorts = (strcmp(val, "true") == 0);
		return;
	}
 	if ( ! strcmp(nam, "reuse_addresses")) {
		mReuseAddresses = (strcmp(val, "true") == 0);
		return;
	}
	Program :: set_opt(nam, val);				// else hand it up to the superclass
}

