/* ARISA - logging functions
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"
#include <stdarg.h>
#include <unistd.h>

static pthread_key_t 	title_key;
static pthread_once_t 	title_key_once = PTHREAD_ONCE_INIT;

static const struct {
	const char *mark;
	const char *colour;
} log_colours[] = {
	{ L_ERR, COLOUR_LRED 	},
	{ L_INF, COLOUR_YELLOW	},
	{ L_ADM, COLOUR_LYELLOW	},
	{ L_XDC, COLOUR_GREEN	},
	{ L_INT, COLOUR_CYAN	},
	{ L_IRC, COLOUR_LCYAN	},
	{ NULL }
};

static size_t log_line(char *buffer, size_t bufsize, const char *src);
/** arisa_log
 * 	The core arisa logging function, takes a format and associated
 * 	arguments and prints it into the bots log file and any other listening
 * 	threads message queues.
 */
void arisa_log(const char *format, ...) {
	char	buffer[524],logmsg[512], *p;
	time_t	now = time(NULL);
	va_list va;
	size_t	len;
	int i,j;
	
	/* Something important to note here, we embed a timestamp in the
	   4 byte following the null terminator of the log message. */
	
	if(global == NULL) {
		va_start(va,format);
		vfprintf(stderr,format,va);
		va_end(va);
		fprintf(stderr,"\n");
		return;
	}
	
	strncpy(buffer,"%n",2);
	va_start(va, format);
	len = IMIN(
		vsnprintf(logmsg,511,format,va),
		510
	); 
	va_end(va);
	len = quote_colours(buffer+2,512,logmsg);
	
	if(strlen(buffer+2) > 0) {
		memcpy(buffer+1+2+len,&now,sizeof(time_t));
		len += 2 + 1 + sizeof(time_t);
		
		for(j = 0; log_colours[j].mark != NULL; ++j) {
			if(strncmp(log_colours[j].mark,buffer+2,
					strlen(log_colours[j].mark)) == 0) {
				strncpy(buffer,log_colours[j].colour,2);
				break;
			}
		}
		
		LOCKR(&(global->log_subs.lock));
		for(i = 0; i < global->log_subs.no_subs; ++i) {
			p = xalloc(len); 
			memcpy(p,buffer,len);
			pqueue_push_back(global->log_subs.subs[i],p);
		}
		UNLOCKR(&(global->log_subs.lock));

		if(global->log_subs.fd != -1) {
			char buffer2[524];
			len = log_line(buffer2,sizeof(buffer2),buffer);
			buffer2[len] = '\n';
			write(global->log_subs.fd,buffer2,len+1);
		}
	}
}

/** title_free
 * 	Internal function used by pthreads to free a threads title.
 */
static void title_free(void *title) {
	if(title != NULL)
		xfree(title);
}

/** title_key_alloc
 * 	Internal function used by pthreads to allocate a title key.
 */
static void title_key_alloc(void) {
	pthread_key_create(&title_key,title_free);
}

/** log_title
 * 	Returns a pointer to the title string of the calling thread,
 * 	or "" if one is not set.
 */
const char *log_title(void) {
	const char *title = (const char *)pthread_getspecific(title_key);
	if(title != NULL)
		return title;
	else
		return "";
}

/** log_set_title
 * 	Sets the title string of the calling thread, based on a format and
 * 	associated arguments.
 */
void log_set_title(const char *fmt, ...) {
	char *buffer;
	va_list va;
	pthread_once(&title_key_once,title_key_alloc);
	buffer = pthread_getspecific(title_key);
	if(buffer == NULL) {
		buffer = xalloc(128);
		pthread_setspecific(title_key,buffer);
	}
	va_start(va,fmt);
	vsnprintf(buffer,128,fmt,va);
	va_end(va);
}

/** log_subscribe
 * 	Attaches a pointer queue to the list of logging message queues.
 * 	New log messages will be xstrdup'd onto the pointer queue as they
 * 	occur.
 * 	Duplicate calls are safe.
 */
void log_subscribe(pqueue_t *q) {
	LOCKR(&(global->log_subs.lock));
	PTRARR_ADD(&(global->log_subs.subs),&(global->log_subs.no_subs),q);
	UNLOCKR(&(global->log_subs.lock));
}

/** log_unsubscribe
 * 	Removes a pointer queue from the list of logging message queues.
 * 	Calls for queues not on the list are safe.
 */
void log_unsubscribe(pqueue_t *q) {
	LOCKR(&(global->log_subs.lock));
	PTRARR_DEL(&(global->log_subs.subs),&(global->log_subs.no_subs),q);
	UNLOCKR(&(global->log_subs.lock));
}

/** log_open
 * 	Returns a FILE type handle to the bot log file, in append mode, 
 * 	or NULL if the file is not set or cannot be openned.
 */
static FILE *log_open(const char *fn) {
	FILE *fh = NULL;
	
	if(fn != NULL)
		fh = fopen(fn,"a");
	
	return fh;
}	

/** log_close
 * 	Closes a FILE type hanlde to the bot log file.
 */
static void log_close(FILE *fh) {
	fclose(fh);
}

/** log_line
 * 	Used by the log thread to convert a line from the log pointer queue
 * 	to a line suitable for writing to the bot log file.  The main function
 * 	performed is extracting the timestamp and converting it to a textual
 * 	form.
 */
static size_t log_line(char *buffer, size_t bufsize, const char *src) {
	char ctime_buf[32];
	int len;
	
	lctime_r((time_t *)(src+strlen(src)+1),ctime_buf,sizeof(ctime_buf));
	len = strlen(ctime_buf);
	if(ctime_buf[len-1] == '\n') {
		ctime_buf[len-1] = '\0';
		len -= 1;
	}
	
	len = IMIN(
		snprintf(buffer,bufsize,"[%s] ",ctime_buf),
		bufsize-1
	);
	
	return len + colourise(COLOUR_NONE,buffer + len,bufsize - len,src);
}
	
static void log_flush(pqueue_t *logq) {
	struct 	tm timeval;
	char 	buffer[2048],format[2048],filename[2048];
	char 	*p;
	FILE 	*fh = NULL;
	time_t	occured;
	
	LOCK(global->settings);
	if(global->settings->log != NULL)
		xstrncpy(format,global->settings->log,sizeof(format));
	else
		strcpy(format,"");
	UNLOCK(global->settings);
	
	strcpy(filename,"");
	
	if(strlen(format) == 0)
		return;
	
	while((p = pqueue_pop_front(logq)) != NULL) {
		/* Open log if not open */
		memcpy(&occured,p+strlen(p)+1,sizeof(time_t));
		localtime_r(&occured,&timeval);
		strftime(buffer,sizeof(buffer),format,&timeval);
		if(strcmp(buffer,filename) != 0) {
			if(fh != NULL)
				log_close(fh);
			xstrncpy(filename,buffer,sizeof(filename));
			fh = log_open(filename);
		}
		if(fh == NULL) {
			pqueue_push_front(logq,p);
			break;
		}
		
		/* Process Message */
		log_line(buffer,sizeof(buffer),p);
		xfree(p);
		
		/* Write to Log */
		fprintf(fh,"%s\n",buffer);
	}
	
	if(fh != NULL)
		log_close(fh);
}

void *log_thread(void *arg) {
	pqueue_t *logq;
	int sleepfor = 0;
	
	thread_signal_started(&(global->log_thread));
	
	logq = pqueue_init(NULL,1);
	log_subscribe(logq);
	
	LOG_TITLE("Log Thread");
	LOGTP(L_INF,"Started, %s",ARISA_VERSION);
	
	while(!thread_should_end()) {
		if(pqueue_length(logq) > 0)
			log_flush(logq);
		LOCK(global->settings);	
		sleepfor = global->settings->log_interval;
		UNLOCK(global->settings);
		
		if(sleepfor <= 0)
			sleepfor = 60;
		thread_sleep(sleepfor);
	}

	LOGTP(L_INF,"Shutdown");
	
	log_unsubscribe(logq);
	log_flush(logq);
	pqueue_free(logq,xfree);

	thread_signal_finished();
	
	return NULL;
}
