//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// Logger.cpp: the CRAM log file class ////
//

#include "Logger.h"
#include <string.h>

using namespace cram;

#include <time.h>		// time-of-day formatting method at end may be UNIX-specific

// Constructors, set-up, etc.

//Logger :: Logger(const char * name, Program & prog) : mLogStream(NULL), _program(prog), mLogToStdout(LOG_TO_STDOUT) {
Logger :: Logger(const char * name) 
				: mLogStream(NULL), mPosition(0), mIndex(0), 
				  mSocket(0), mLevel(DEFAULT_LOG_LEVEL), 
				  mLogToStdout(LOG_TO_STDOUT), mLogToSocket(LOG_TO_SOCKET), mLogToFile(true),
				  mCacheLen(LOG_CACHE_SIZE) {
	memset(mMessage, 0, CRAM_MAX_MSG);			
	set_name(name);
	open();
}

Logger::~Logger() {
	close();
	free(mLogName);
}	

// Allocate a copy of the name

void Logger :: set_name(const char * name) {
	mLogName = (char *) malloc (64);
	sprintf(mLogName, "%s%s.log.txt", CRAM_LOG_DIR, name);
}

// Open your file and print a banner/timestamp to it

void Logger :: open() {
	if (mLogStream != NULL)
		return;
	if (mLogToFile) {			// this may be false, in which case I log to stdout without a file
		try {
			mLogStream = fopen(mLogName, "a+");
		} catch (int err) {
			printf("\n%s: HELP: cannot open log file\n\n", time_string());
                        fflush(stdout);
			mLogStream = NULL;
		}
		try {
                    fprintf(mLogStream, "\n%s  ----  Log started ----\n\n", time_string());
		} catch (int err) {
			printf("\n%s: HELP: cannot write to log file\n\n", time_string());
                        fflush(stdout);
			mLogStream = NULL;
		}
	} else
		mLogStream = NULL;
	if (mLogToStdout)
		printf("\n%s  ----  Log started ----\n\n", time_string());
	if (mCacheLen > 0) {
		mCache = (char *) malloc(mCacheLen);
		mPosition = mIndex = 0;
	}
}

// Close it up with a goodbye message

void Logger :: close() {
	if (mLogStream == NULL)
		return;
	try {
		fprintf(mLogStream, "\n%s  ----  Log stopped\n", time_string());
		fclose(mLogStream);
	} catch (int err) {
		printf("\n%s: HELP: cannot close log file\n\n", time_string());
	}
	mLogStream = NULL;
}

// Set up an output socket (or set sock = 0 to stop socket logging)

void Logger :: set_socket(int sock, sockaddr_in client) {
	mSocket = sock;
	mClient = client;
}

// Log-writing functions

// This is the general call that takes a va_list

void Logger :: vlog_msg(LogLevel level, char * format, va_list args) {
	char * ptr;
	unsigned len, to_copy, left;
	
	switch(level) {
		case kLogInfo:
			if (mLevel < 3)			// check verbosity level: 3 = verbose, 0 = real quiet (fatal error messsages only)
				return;
			ptr = "Info:		";
			break;
		case kLogImportant:
			if (mLevel < 2)			// mLevel 2 or greater gets all these
				return;
			ptr = "Message:		";		
			break;
		case kLogWarning:
			if (mLevel < 2)			// mLevel 2 or greater gets all these
				return;
			ptr = "Warning: ";		
			break;
		case kLogError:
			if (mLevel < 1)			// level 1 = serious errors
				return;
			ptr = "Error: ";		
			break;
		case kLogFatal:				// even log level 0 gets fatal error messages...
			ptr = "FATAL: ";
			break;
	}
	try {							// print the log item onto the _message string
		sprintf(mMessage, "%s:	%s", time_string(), ptr);
		len = strlen(mMessage);					// messy...
		vsprintf( & (mMessage[len]), format, args);
		len = strlen(mMessage);
		sprintf( & (mMessage[len]), "\n");
		len++;
		
		if (mLogToFile && (mLogStream != NULL)) {	// Now write to log file
			fprintf(mLogStream, "%s", mMessage);
			fflush(mLogStream);
		}
		
		if (mLogToStdout) {							// and/or to stdout
			printf("%s", mMessage);
			fflush(stdout);
		}
		
		if (mLogToSocket && (mSocket != 0)) {		// and/or to a socket
			safe_send(mSocket, ( & mMessage[0]), len + 1, (struct sockaddr *) & mClient);
		}
		
		if (mCacheLen > 0) {						// and/or to the in-memory cache
			to_copy = ((mIndex + len) > mCacheLen) 
								? (mCacheLen - mIndex) : (len);
			memcpy( & (mCache[mIndex]), mMessage, to_copy);
			if (to_copy < len) {					// wrap-around in-memory cache pointer
				left = len - to_copy;
				memcpy( & (mCache[0]), & (mMessage[to_copy]), left);
			}
			mIndex += len;
			if (mIndex > mCacheLen) mIndex -= mCacheLen;
			mPosition += len;
		}
	} catch (int err) {							// exc handler shuts down logging on error
		printf("\n%s: HELP: cannot write to log file or socket\n\n", time_string());
		perror("");
		fflush(stdout);
		if (mLogStream != NULL) 		this->close();
		if (mLogToSocket) 				mLogToSocket = false;
	}
}

// External calls: log_msg() with/without level; Default level = kLogInfo

void Logger :: log_msg(LogLevel level, char * format, ...) {
	va_list args;
	va_start(args, format);
	vlog_msg(level, format, args);
	va_end(args);
}

void Logger :: log_msg(char * format, ...) {
	if (mLevel < 3)		// if default level and low verbosity
		return;
	va_list args;
	va_start(args, format);
	vlog_msg(kLogInfo, format, args);	// use kLogInfo
//	vlog_msg(format, args);
	va_end(args);
}

/*
void Logger :: vlog_msg(char * format, va_list args) {
	if (mLevel < 3)
		return;
	vlog_msg(kLogInfo, format, args);	// use kLogInfo
}
*/

// Get the log's text, up to maxChars; answer the number of chars read
// This version uses the file (i.e., slow)

int Logger :: get_text(char * dest, unsigned maxChars) {
	int answer;
	unsigned pos;
	size_t how_many, num_read;
	if (mLogStream == NULL)
		return 0;						// or should I get it from the cache?
#ifndef LOG_TO_FILE
	return 0;
#endif
	try {								// be careful -- this is IO, y'know
		pos = (int) ftell(mLogStream);
		if (pos <= maxChars) {			// if you're asking for more than is there
			rewind(mLogStream);
			how_many = (size_t) pos;
		} else {									// seek back
			answer = fseek(mLogStream, (long) (0 - maxChars), SEEK_END);
			if (answer < 0) {
				perror("Error seeking on log file");
				return 0;
			}
			how_many = (size_t) maxChars;
		}											// read a block
		num_read = fread((void *) dest, (size_t) 1, how_many, mLogStream);
		dest[num_read] = '\0';
		fseek(mLogStream, 0, SEEK_END);			// set to end again
	} catch (int err) {
		printf("\n%s: HELP: cannot re-read log file\n\n", time_string());
	}
	return (int) num_read;							// answer actual # of bytes read
}

// More efficient version -- get the log tail after the given position
// this assumes the in-memory cache

int Logger :: get_text_after(char * dest, unsigned mark) {
	unsigned to_copy, r_ptr;

	if (mCacheLen <= 0)
		return 0;
	if (mPosition <= mark)
		return 0;
	r_ptr = mark % mCacheLen;			// get the mark position modulo the cache size
	if (mIndex > r_ptr) {				// simple case write_ptr > read_ptr
		to_copy = mPosition - mark;
		memcpy( (void *) dest, & (mCache[r_ptr]), to_copy);
		dest[to_copy] = '\0';
	//	printf("\nGetLogAfter = \"%s\"\n\n", dest);
		return to_copy;
	}
	to_copy = mCacheLen - r_ptr;		// else wrap-around case
	memcpy( (void *) dest, & (mCache[r_ptr]), to_copy);	
	memcpy( (void *) (dest + to_copy), & (mCache[0]), mIndex);
	return mPosition - mark;
}

// Truncate the log file if it grows...

int Logger :: truncate_file(unsigned numChars) {
	int answer;
	unsigned pos;
	size_t how_many, num_read;
	if (mLogStream == NULL)
		return 0;
#ifndef LOG_TO_FILE
	return 0;
#endif
	char * buffer = NULL;
	FILE * _tmp_stream = mLogStream;
	mLogStream = NULL;					// stop other logging while I'm doing this...
	
	try {								// be careful -- this is IO, y'know
		pos = (int) ftell(_tmp_stream);	// find out where we are in the file
		if (pos < numChars) {			// if the file isn't that large, return
			mLogStream = _tmp_stream;
			return -1;	
		}								// seek back maxChars items
		answer = fseek(_tmp_stream, (long) (0 - numChars), SEEK_END);
		if (answer < 0) {
			perror("Error seeking on log file");
			mLogStream = _tmp_stream;
			return -1;
		}								// allocate a buffer and read a block of the file
		how_many = (size_t) numChars;
		buffer = (char *) malloc(numChars);
		num_read = fread((void *) buffer, (size_t) 1, how_many, _tmp_stream);
										// close the file, re-open as "w" and write the block
		fclose(_tmp_stream);
		_tmp_stream = fopen(mLogName, "w");
		if (fwrite((void *) buffer, (size_t) 1, how_many, _tmp_stream) != num_read)
			printf("\n%s: HELP: cannot write truncated log file\n\n", time_string());
										// re-close, re-open
		fclose(_tmp_stream);
		mLogStream = fopen(mLogName, "a+");

	} catch (int err) {
		printf("\n%s: HELP: cannot re-read log file\n\n", time_string());
		mLogStream = _tmp_stream;
		if (buffer)
				free(buffer);
		return -1;
	}
	free(buffer);
	return 0;
}

// Answer a time string, without the annoying \n that ctime() appends

char the_time[26];		// Global time string, for convenience

char * Logger :: time_string() {
	time_t tim = time(NULL);
	struct tm now;
	localtime_r( & tim, & now);
			// format: 21/12/02 12:23
//	sprintf(the_time, "%d/%d/%d, %d:%d:%d", 
//					now.tm_mday, (now.tm_mon + 1), (now.tm_year + 1900), 
//					now.tm_hour, now.tm_min, now.tm_sec);
			// format 09:08:07 with leading zeros as necessary
	sprintf(the_time, "%s%d:%s%d:%s%d", 
					((now.tm_hour < 10) ? "0" : ""), now.tm_hour, 
					((now.tm_min < 10) ? "0" : ""), now.tm_min, 
					((now.tm_sec < 10) ? "0" : ""), now.tm_sec);
			// the old way -- use ctime_r()
//	ctime_r(& tim, the_time);
//	the_time[24] = '\0';
	return the_time;
}

// Safe_send retries multiple writes to a socket; returns bytes sent or error code
// It does no exception handling; call it with in try{}catch{}

int Logger :: safe_send(int ssoc, char * buf, unsigned ssiz, struct sockaddr * scln) {
	int val;
	unsigned ssent = 0;	
	unsigned to_ssend = ssiz;	
	while (ssent < ssiz) {					// loop retrying partial sends
		val = sendto(ssoc, (& buf[ssent]), to_ssend, 0, scln, sizeof(struct sockaddr_in));
		if (val < 0) {	
			return -1;
		} else {
			ssent += val;
			to_ssend -= val;
		}
	}
	return ssent;
}

