//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// Program.h -- the CRAM abstract program class (all CRAM components are programs) ////
//
// A Program manages a socket listener thread that waits for commands (normally from the node
// manager) and has a command parsing loop (process_request()) that can be extended by subclasses.
// Program instances also support emergency command threads and log files for system messages.
//

#ifndef __PROGRAM_INCLUDE__
#define __PROGRAM_INCLUDE__

#include "CRAM.h"
#include "Logger.h"
#include "Options.h"

#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>

namespace cram {				// We all live in the namespace cram.

// The abstract "Program" class (all CRAM components are programs)

class Program {

public:				// Instance variables: common to node managers and service instances;
					// mostly public for simplicity of access by forked threads.
	char * mName;				// program's name (arbitrary, used by manager and logger)
	int mStatus;				// flag: is the CRAM wrapper OK? (e.g., CRAM_AOK)
	int mAppStatus;				// flag: is my application running? (e.g., CRAM_RUNNING)
	int mPid;					// the program's process id
	unsigned mSequence;			// the current packet sequence number
	Logger mLog;				// message logger object
	Options::OptVec mOptions;	// the current option map
	
	unsigned short mLPort;		// program's listener port (commands arrive here)
	int mLSocket;				// my command I/O socket id
	pthread_t mLThread;  		// my command I/O thread
	
	unsigned short mEPort;		// program's emergency port (used if the main listener fails to respond)
	int mESocket;				// my "emergency" I/O socket id
	pthread_t mEThread; 		// and separate emergency I/O thread
	
	in_addr_t mMgrIP;			// the IP address I receive messages from (my manager)	
	time_t mLastLMsg;			// the time (in sec) of the last listener thread message (updated in read_loop)
	unsigned mRestarts;			// Count of the program's emergency restarts
	bool mReuseAddresses;		// Whether to try to reuse socket addresses and ports
	
					// Constructor
	Program(char * name, unsigned short lport, unsigned short eport);
	virtual ~Program();
	
					// Command processing methods
							// The main command decoder method (called from the read thread).
							// Answer the number of bytes to send back (may be 0)
	int process_request(in_addr_t sender, char * request_packet);
	
							// The command switch, called from process_request()
							// and extended in my subclasses (adding their own handlers).
							// Answer the number of bytes to send back (may be 0)
	virtual int handle_packet(in_addr_t sender, CRAMPacketHeader * pkt);
							// check the packet size and header flags; answer 0 or not
	int check_header(CRAMPacketHeader * pkt, unsigned short min_size);
	
					// Low-level safe socket operations
							// Safe_open creates and binds to a socket with exception handling
	int safe_open(int * ssoc, unsigned short port, struct sockaddr_in * scln);
							// Safe_send retries multiple writes to a socket; returns bytes sent or error code
	int safe_send(int ssoc, char * buf, unsigned ssiz, sockaddr * scln);
							// Safe_recv times-out after a given period, has several levels of retry, 
	int safe_recv(int ssoc, char * buf, unsigned ssiz, sockaddr * scln, int * addr_len, int stimeo, int srtry);

	int start_threads();	// main init method
	void shut_down();		// kill threads and exit
	
	void incr_sequence(void) { mSequence = mSequence + 1; };

protected:

	int scan_packet(CRAMPacketHeader * pkt, char * format, ...);		// scan arguments from a packet (like scanf)
	int print_packet(CRAMPacketHeader * pkt, char * format, ...);		// print onto a packet (like printf)
	char * flag_string(int flag); 									// get a string for the given value

					// Low-level stuff
					// Thread utilities for programs
	int spawn_l_thread();			// start listener resp. emergency threads
	int spawn_e_thread();
	unsigned short guess_port(unsigned short avoidThisOne); // ask about the next available port (carefully)
	int test_port(unsigned short port);		// Test a socket on the given port; answer AOK if available

											// safe strlen (essentially str_n_len) 
											// returns null if string shorter than max
	int safe_strlen(char * src, unsigned max);

					// Option setting
	void init_options();
	virtual void set_opt(const char * nam, const char * val);
	void configure(char * opts, unsigned length);
	int get_opts(char * where);
};

// Useful macros

// Print the given string and check the packet size against the argument

#define LOG_CHECK_SIZE(sstr, ssiz) 					\
	if (pkt->size != ssiz) {						\
		mLog.log_msg(kLogError, 					\
			"Packet size error: %d", pkt->size);	\
		pkt->size = CRAM_ARGUMENT_ERROR;			\
	}

// Set up the given inet address

#define SET_IP_ADDR(the_addr, the_port, the_ip)		\
	the_addr->sin_family = AF_INET;					\
	the_addr->sin_port = htons(the_port);			\
	the_addr->sin_addr.s_addr = htonl(the_ip);		\
	bzero( & (the_addr->sin_zero), 8);

}	// end of namespace

#endif

