#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include "version.h"
#include "str.h"

int use_syslog = 0;
static FILE *logfp = 0;

#ifdef HAVE_SYSLOG
#include <syslog.h>

const int log_debug = LOG_DEBUG;
const int log_info = LOG_INFO;
const int log_warning = LOG_WARNING;
const int log_fatal = LOG_CRIT;

#else

const int log_debug = -1;
const int log_info = 0;
const int log_warning = 1;
const int log_fatal = 2;

#endif

static void log_pipe(const char *s)
{
	int fds[2];

	if (pipe(fds) == -1) {
		cfatal("Could not create pipe: %s");
	}

	switch (fork()) {
	case -1:
		cfatal("Could not fork: %s");
		break;
	case 0:
		close(0);
		close(1);
		close(2);
		dup2(fds[0], 0);
		execl("/bin/sh", "/bin/sh", "-c", s);
		exit(1);
	};
	close(fds[0]);
	logfp = fdopen(fds[1], "w");
	if (!logfp) {
		logfp = stderr;
		cfatal("Could not attach fd to logger: %s");
	}
}
static void log_file(const char *s)
{
	int fd;

	fd = open(s, O_CREAT|O_WRONLY|O_APPEND, 0666);
	if (fd == -1) {
		cfatal("Could not open `%s' for writing: %s", s);
	}

	logfp = fdopen(fd, "w");
	if (!logfp) {
		logfp = stderr;
		cfatal("Could not attach fd to logger: %s");
	}
}

void log_init(char *s)
{
	int fd;

	use_syslog = 0;
	logfp = stderr;
	if (s && str_equali(s, "quiet")) {
		logfp = fopen("/dev/null", "w");
	} else if (s && *s == '/') {
		log_file(s);
	} else if (s && *s == '|') {
		log_pipe(s+1);
	} else if (s && str_starti(s, "file:/")) {
		log_file(s+5);
	} else if (s && str_starti(s, "pipe:/")) {
		log_pipe(s+5);
	} else if (s && str_starti(s, "exec:/")) {
		log_pipe(s+5);
	} else if (s && str_starti(s, "prog:/")) {
		log_pipe(s+5);
	} else if (s && str_starti(s, "program:/")) {
		log_pipe(s+8);

	} else if (s && str_equali(s, "syslog")) {
#ifdef HAVE_SYSLOG
		use_syslog = 1;
		openlog(PROGRAM, LOG_CONS | LOG_NDELAY 
#ifdef LOG_PERROR
				| LOG_PERROR 
#endif
				| LOG_PID, LOG_DAEMON);
		logfp = 0;
#else
		use_syslog = 0;
		warning("You do not have syslog() support compiled in, defaulting to stderr");
#endif
	}
}
static void _log_helper(int lev, int clib_flag, const char *m, va_list ap)
{
	str_t buf;
	char lbuf[32];

	str_init(buf);
	while (*m) {
		if (*m == '%') {
			switch (m[1]) {
			case '%':
				str_addch(buf, '%');
				break;
			case 's':
				if (clib_flag) {/* Clib */
					clib_flag = 0;
					str_cat(buf, strerror(errno));
				} else
					str_cat(buf, va_arg(ap, char *));
				break;
			case 'd':
				sprintf(lbuf, "%d", va_arg(ap, int));
				str_cat(buf, lbuf);
				break;
			case 'c':
				str_addch(buf, va_arg(ap, int));
				break;
			default:
				fatal("unknown log() specifier %%%c", m[1]);
			};
			m++; m++;
		} else {
			str_addch(buf, m[0]);
			m++;
		}
	}
#ifdef HAVE_SYSLOG
	if (use_syslog) {
		fprintf(stderr, "level = %d: %s\n", lev, str(buf));
		syslog(lev, "%s", str(buf));
	} else
#endif
	if (logfp) {
		fprintf(logfp, "%s %s: %s\n", PROGRAM,
			(lev == log_info ? "info"
			:lev == log_warning ? "warning"
			:lev == log_debug ? "debug"
			:"fatal"),
			str(buf));
		fflush(logfp);
	}
	mem_free(str(buf));
}
void inline status(const char *s, ...)
{
	va_list ap;

	if (!logfp) return;
	va_start(ap, s);
	vfprintf(logfp, s, ap);
	fputc('\n', logfp);
	fflush(logfp);
	va_end(ap);
}
void inline log(int lev, const char *m, ...)
{
	va_list ap;
	va_start(ap, m);
	_log_helper(lev, 0, m, ap);
	va_end(ap);
}
#define MAKE_LOGGING_FUNCTION(A,B) \
void inline A(const char *m, ...) \
{ \
	va_list ap; \
	va_start(ap, m); \
	_log_helper(log_ ## A, 0, m, ap); \
	va_end(ap); B; \
} \
void inline c ## A(const char *m, ...) \
{ \
	va_list ap; \
	va_start(ap, m); \
	_log_helper(log_ ## A, 1, m, ap); \
	va_end(ap); B; \
}
MAKE_LOGGING_FUNCTION(fatal,exit(1))
MAKE_LOGGING_FUNCTION(warning,return)
MAKE_LOGGING_FUNCTION(debug,return)
#undef MAKE_LOGGING_FUNCTION


syntax highlighted by Code2HTML, v. 0.9.1