//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// Program.cpp -- the CRAM abstract program class, superclass of both Server and Node ////
//
// This file contains the high-level Program methods, including the handle_packet() command switch.
// The file ProgramUtil.cpp has the low-level socket/packet I/O and background thread utilities.
//

#include "Program.h"

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

using namespace cram;

// Constructor -- args are program/service name (not shell command), and listener/emergency port #s
// Verbosely initialize whole object

Program :: Program(char * name, unsigned short lport, unsigned short eport) 
			: mStatus(CRAM_INIT), mAppStatus(CRAM_UNKNOWN), 
			  mPid(0), mSequence(0), mLog(name),
			  mLPort(lport), mLSocket(0), mLThread(0),
			  mEPort(eport), mESocket(0), mEThread(0),
			  mLastLMsg(0), mRestarts(0), mReuseAddresses(REUSE_ADDRESSES) {
//	printf("\t[CRAM Program construction]\n"); fflush(stdout);
	mName = (char *) malloc(strlen(name) + 1);
	strcpy(mName, name);
	mLog.mLevel = DEFAULT_LOG_LEVEL;
	init_options();
}

// Destructor; free name, kill threads

Program :: ~Program() {
	try {
		if (mLThread) 
			pthread_kill(mLThread, SIGINT);
		if (mLSocket !=0 )
			close(mLSocket);
		if (mEThread) 
			pthread_kill(mEThread, SIGINT);
		if (mESocket !=0 )
			close(mESocket);
	} catch (int exc) {				// catch any error and fail
		printf("CRAM Service %s: Unrecoverable shut-down error; exiting.", mName);
	}
	free(mName);	
}

// Initialize the options list; subclasses extend this.

void Program :: init_options() {
	mOptions.push_back (make_pair (string("log_level"), string("3")));
	mOptions.push_back (make_pair (string("log_to_stdout"), string("true")));
	mOptions.push_back (make_pair (string("log_to_socket"), string("false")));
	mOptions.push_back (make_pair (string("log_to_file"), string("true")));
	mOptions.push_back (make_pair (string("log_cache_size"), string("8192")));
//	mOptions.push_back (make_pair (string("listener_port"), string("12090")));
//	mOptions.push_back (make_pair (string("emergency_port"), string("12092")));
	mOptions.push_back (make_pair (string("use_select"), string("true")));
}

// Packet Processor

// Process a request packet; answer the number of bytes in the response.
// This is called from the reader thread; it may write into the packet buffer it's handed, 
// and returns the number of bytes to send back to the client.

int Program :: process_request(in_addr_t sender, char * buffer) {
	CRAMPacketHeader * pkt_header = (CRAMPacketHeader *) buffer;
	int to_write;
	
	if (mLog.mLevel > 3) printf("Processing request packet: %d bytes\n", pkt_header->size);	
	int reply = check_header(pkt_header, CRAM_HEADER_SIZE);	// check packet header
	if (reply != CRAM_AOK)				// on error, return packet header with error flag in the size field
		return (CRAM_HEADER_SIZE);
#ifdef CHECK_SEQUENCE
		// be picky here
#endif
										// else handle the packet
	to_write = this->handle_packet(sender, pkt_header);
		
	if ((to_write < 0) || (to_write > CRAM_MAX_MSG)) {
		mLog.log_msg(kLogError, "Error handling packet: %d", to_write);
		return 0;
	}
	return to_write;
}

//
// Main command decoder switch, extended in subclasses.
//
// This implements the commands understood by all Programs (i.e., all CRAM components).
// The return value is # of bytes to write back as response
//

int Program :: handle_packet(in_addr_t sender, CRAMPacketHeader * pkt) {
	int answer, value;						// temps
	char * buffer = (char *) pkt;			// char pointer to the packet
	short s_value;							// short argument
	int ret_val = CRAM_HEADER_SIZE;	// default return value = packet size (i.e., echo packet back)
	int cmd = (pkt->magic) & 0xff;			// get the command nibble from the packet
	
	switch (cmd) {				// This is the root CRAM command processing switch
	
	case CRAM_PING:				// ping message: set the header size to my mStatus and return
		mLog.log_msg("\tPing of %s", mName);
		LOG_CHECK_SIZE("[Ping]", CRAM_HEADER_SIZE);
		pkt->value = mStatus;
		break;
		
	case CRAM_TEST:				// test message: set the header size to my mStatus and
		LOG_CHECK_SIZE("[Test]", CRAM_HEADER_SIZE);	// put my app status and last message time in the return packet
		pkt->value = mStatus;				// write data (2 int) onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE]), (void *) & mAppStatus, 4);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 4]), (void *) & mLastLMsg, 4);
		value = CRAM_PACKET_MAGIC;
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 8]), (void *) & value, 4);
		pkt->size = CRAM_HEADER_SIZE + 12;
		ret_val = pkt->size;				// set the return packet size
		break;

	case CRAM_GET_NAME:			// name request message: answer with my name
		LOG_CHECK_SIZE("[Name]", CRAM_HEADER_SIZE);
											// copy my name into the buffer
		strcpy( & (buffer[CRAM_HEADER_SIZE]), mName);
		value = CRAM_PACKET_MAGIC;			// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + strlen(mName) + 1]), (void *) & value, 4);
		pkt->value = CRAM_AOK;
		pkt->size = CRAM_HEADER_SIZE + strlen(mName) + 5;
		ret_val = pkt->size;				// set the return packet size
		break;
		
	case CRAM_GET_LOG:			// Answer the tail of my log file
		LOG_CHECK_SIZE("[Get log]", CRAM_HEADER_SIZE);
		s_value = (short) pkt->value;
		if (s_value > (CRAM_MAX_MSG - 20)) {	// if request was too large
			mLog.log_msg("Error getting log text: request too large");
			pkt->value = CRAM_ARGUMENT_ERROR;
			break;		
		}	
												// get the log text into the packet buffer
		answer = mLog.get_text( & buffer[CRAM_HEADER_SIZE], (unsigned) s_value);
		if (answer > s_value) {
			pkt->size = CRAM_ARGUMENT_ERROR;
			break;		
		}
		value = CRAM_PACKET_MAGIC;				// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + answer + 1]), (void *) & value, 4);
		pkt->size = CRAM_HEADER_SIZE + answer + 5;
		ret_val = pkt->size;					// set the return packet size
		break;
		
	case CRAM_GET_LOGAFTER:		// Answer the tail of my log file
		LOG_CHECK_SIZE("[Get log tail]", CRAM_HEADER_SIZE);
		s_value = (short) pkt->value;
		
		answer = mLog.get_text_after( & buffer[CRAM_HEADER_SIZE], (unsigned) s_value);
		if (answer > CRAM_MAX_MSG) {
			pkt->size = CRAM_ARGUMENT_ERROR;
			break;		
		}
		value = CRAM_PACKET_MAGIC;				// write footer onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + answer + 1]), (void *) & value, 4);
		pkt->size = CRAM_HEADER_SIZE + answer + 5;
		ret_val = pkt->size;					// set the return packet size
		break;
		
	case CRAM_SET_OPTS:			// configure message: set the opts
		mLog.log_msg(kLogInfo, "[Set options]");
		if (pkt->size <= CRAM_HEADER_SIZE) {	// arguments required
			pkt->value = CRAM_ARGUMENT_ERROR;
		} else {
			configure( & buffer[CRAM_HEADER_SIZE], (pkt->size - CRAM_HEADER_SIZE - 4));
			pkt->value = CRAM_AOK;
		}
		break;

	case CRAM_GET_OPTS:			// get options message: respond with the opts
		LOG_CHECK_SIZE("[Get options]", CRAM_HEADER_SIZE);
		answer = get_opts( & buffer[CRAM_HEADER_SIZE]);
		value = CRAM_PACKET_MAGIC;
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + answer]), (void *) & value, 4);
		pkt->value = CRAM_AOK;
		pkt->size = CRAM_HEADER_SIZE + answer + 4;
		ret_val = pkt->size;
		break;

						//// Emergency packets
	case CRAM_EMER_PING:		// Ping message  (no arg; respond with general status flag)
		LOG_CHECK_SIZE("[EMERGENCY Ping]", CRAM_HEADER_SIZE);
		pkt->value = mStatus;				// write into pkt size flag
		break;

	case CRAM_EMER_PORTS:		// Port query  (no arg; respond with listener port # & status)
		LOG_CHECK_SIZE("[EMERGENCY Ports]", CRAM_HEADER_SIZE);
		pkt->value = mStatus;			// write data (1 short + 1 int + footer) onto packet
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE]), (void *) & mLPort, 2);
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 2]), (void *) & mStatus, 4);
		value = CRAM_PACKET_MAGIC;
		memcpy((void *) (& buffer[CRAM_HEADER_SIZE + 4]), (void *) & value, 4);
		pkt->size = CRAM_HEADER_SIZE + 10;
		ret_val = pkt->size;				// set the return packet size
		break;

	case CRAM_EMER_RESTART:		// Emergency restart of listener thread (no arg; respond with listener port # & status)
		LOG_CHECK_SIZE("[EMERGENCY Restart]", CRAM_HEADER_SIZE);
		mLog.log_msg(kLogWarning, "Attempting emergency restart.");
		try {
			if (mLThread) 
				pthread_kill(mLThread, SIGINT);
			if (mLSocket !=0 )
				close(mLSocket);
		} catch (int exc) { 
			pkt->value = CRAM_PROCESSING_ERROR;			// we're REALLY screwed if we end up here...
			ret_val = pkt->size = CRAM_HEADER_SIZE;
			break;
		}
		mLThread = 0;
		mLSocket = 0;
		answer = spawn_l_thread();	// start listener thread
		sleep(1);
		if ((answer != 0) || (mLThread == 0)) {	
												// if start failed, the listener socket is probably really hung, 
			mLPort = guess_port(0);				// so try another port for the listener
			mLThread = 0;
			mLSocket = 0;
			if (spawn_l_thread() != 0) {	// start listener thread
				pkt->value = CRAM_PROCESSING_ERROR;			// we're REALLY screwed if we end up here...
				ret_val = pkt->size = CRAM_HEADER_SIZE;
				break;
			}
		}
		mLog.log_msg(kLogWarning, "Listener thread restarted on port %d.", mLPort);
		pkt->value = CRAM_AOK;
		print_packet(pkt, "dd", mLPort, mStatus);
		ret_val = pkt->size = CRAM_HEADER_SIZE + 8;
		break;
						// OOPS -- there's no upper handler for the packet !?!?
	default:						// unknown command -- flag the error: return a broken packet
		mLog.log_msg(kLogError, "Error unknown command in packet: %d", cmd);
		pkt->value = CRAM_COMMAND_ERROR;
	}
	return ret_val;
}

// Option-handling

// configure: assume the argument points to an even number of null-terminated strings

void Program :: configure (char * opts, unsigned len) {
	char * nam, * val, * ptr = opts;
	
	while (ptr < (opts + len)) {
		nam = ptr;					// get the option name
		ptr += (strlen(nam) + 1);
		val = ptr;					// get the option value
		ptr += (strlen(val) + 1);
		set_opt(nam, val);			// set the option value
	}
}

void Program :: set_opt(const char * nam, const char * val) {
	int i_val;
	
//	mLog.log_msg("[Configure %s = %s]", nam, val);
 	if ( ! strcmp(nam, "log_level")) {
		i_val = atoi(val);
		if (i_val < 0)		i_val = 0;
		if (i_val > 4)		i_val = 4;
		mLog.mLevel = i_val;
		return;
	}
	if ( ! strcmp(nam, "log_to_stdout")) {
		mLog.mLogToStdout = (strcmp(val, "true") == 0);
		mLog.close();
		mLog.open();
		return;
	}
	if ( ! strcmp(nam, "log_to_socket")) {
		mLog.mLogToSocket = (strcmp(val, "true") == 0);
		mLog.close();
		mLog.open();
		return;
	}
	if ( ! strcmp(nam, "log_to_file")) {
		mLog.mLogToFile = (strcmp(val, "true") == 0);
		mLog.close();
		mLog.open();
		return;
	}
	if ( ! strcmp(nam, "log_cache_size")) {
		mLog.mCacheLen = atoi(val);
		mLog.close();
		mLog.open();
		return;
	}
	if ( ! strcmp(nam, "listener_port")) {
		i_val = atoi(val);
		return;
	}
	if ( ! strcmp(nam, "emergency_port")) {
		i_val = atoi(val);
		return;
	}
//	if ( ! strcmp(nam, "watch_sequence")) {
//		mWatchSequence = (strcmp(val, "true") == 0);
//		return;
//	}
}

// List all the options onto the given string as a sequence of null-termianted strings
// Answer the length of the string.

int Program :: get_opts(char * where) {
	char * item, * out = where;
	unsigned siz;
	for (Options::OptVec::const_iterator i = mOptions.begin(); i != mOptions.end(); ++i) {
		item = (char *) (*i).first.c_str();
		siz = strlen(item) + 1;
		sprintf(out, "%s", item);
	//	printf("%s\t", item);
		out += siz;
		item = (char *) (*i).second.c_str();
		siz = strlen(item) + 1;
		sprintf(out, "%s", item);
	//	printf("=\t%s\n", item);
		out += siz;
	}
	return (out - where);
}

