/*
   Logging API

   OpenChange Project

   Copyright (C) Jelmer Vernooij 2015

   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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "libmapi/libmapi.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

int _oc_log_samba_level = 0;

void oc_log(enum oc_log_level level, const char *fmt_string, ...)
{
	va_list ap;
	va_start(ap, fmt_string);
	oc_logv(level, fmt_string, ap);
	va_end(ap);
}

void oc_logv(enum oc_log_level level, const char *fmt_string, va_list ap)
{
	char line[OC_LOG_MAX_LINE];
	int samba_level;
	int nwritten;

	nwritten = vsnprintf(line, sizeof(line), fmt_string, ap);
	if (nwritten < 0) return;

	if (level >= 0) {
		samba_level = level;
	} else {
		/* Log OC_LOG_FATAL, OC_LOG_ERROR, OC_LOG_WARNING and OC_LOG_INFO
		 * all at samba debug level 0 */
		samba_level = 0;
	}

	if (samba_level && !(_oc_log_samba_level & samba_level))
		return;

	/* Add a trailing newline if one is not already present */
	if (line[strlen(line)-1] == '\n') {
		fprintf(stderr, "%s", line);
	} else {
		fprintf(stderr, "%s\n", line);
	}
	fflush(stderr);
}

void oc_log_init_stdout()
{
	setup_logging("", DEBUG_DEFAULT_STDOUT);
}

void oc_log_init_stderr()
{
	setup_logging("", DEBUG_DEFAULT_STDERR);
}

/* Initialize logging subsystem to write to users' local log file, e.g. ~/.openchange.log */
void oc_log_init_user(const char *progname, struct loadparm_context *lp_ctx)
{
	/* For the moment, just log to the system logs if possible. */
	setup_logging(progname, DEBUG_FILE);
}

/* Initialize logging subsystem to write to config file specified in smb.conf,
   defaulting to /var/log/openchange.log */
void oc_log_init_server(const char *progname, struct loadparm_context *lp_ctx)
{
	setup_logging(progname, DEBUG_FILE);
}

void oc_panic(const char *why)
{
	if (why) {
		fprintf(stderr, "PANIC: %s\n", why);
		fflush(stderr);
	}

	abort();
}
static void oc_debugadd_cb(const char *buf, void *private_data)
{
	int *plevel = (int *)private_data;
	if (plevel && *plevel <= _oc_log_samba_level) {
		printf ("%s", buf);
	}
}

static void oc_print_asc_cb(const uint8_t *buf, int len,
			    void (*cb)(const char *buf, void *private_data),
			    void *private_data)
{
	int i;
	char s[2];
	s[1] = 0;

	for (i=0; i<len; i++) {
		s[0] = isprint(buf[i]) ? buf[i] : '.';
		cb(s, private_data);
	}
}

static void oc_dump_data_cb(const uint8_t *buf, int len,
			    bool omit_zero_bytes,
			    void (*cb)(const char *buf, void *private_data),
			    void *private_data)
{
	int i=0;
	static const uint8_t empty[16] = { 0, };
	bool skipped = false;
	char tmp[16];

	if (len<=0) return;

	for (i=0;i<len;) {

		if (i%16 == 0) {
			if ((omit_zero_bytes == true) &&
			    (i > 0) &&
			    (len > i+16) &&
			    (memcmp(&buf[i], &empty, 16) == 0))
			{
				i +=16;
				continue;
			}

			if (i<len)  {
				snprintf(tmp, sizeof(tmp), "[%04X] ", i);
				cb(tmp, private_data);
			}
		}

		snprintf(tmp, sizeof(tmp), "%02X ", (int)buf[i]);
		cb(tmp, private_data);
		i++;
		if (i%8 == 0) {
			cb("  ", private_data);
		}
		if (i%16 == 0) {

			oc_print_asc_cb(&buf[i-16], 8, cb, private_data);
			cb(" ", private_data);
			oc_print_asc_cb(&buf[i-8], 8, cb, private_data);
			cb("\n", private_data);

			if ((omit_zero_bytes == true) &&
			    (len > i+16) &&
			    (memcmp(&buf[i], &empty, 16) == 0)) {
				if (!skipped) {
					cb("skipping zero buffer bytes\n",
					   private_data);
					skipped = true;
				}
			}
		}
	}

	if (i%16) {
		int n;
		n = 16 - (i%16);
		cb("  ", private_data);
		if (n>8) {
			cb(" ", private_data);
		}
		while (n--) {
			cb("   ", private_data);
		}
		n = MIN(8,i%16);
		oc_print_asc_cb(&buf[i-(i%16)], n, cb, private_data);
		cb(" ", private_data);
		n = (i%16) - n;
		if (n>0) {
			oc_print_asc_cb(&buf[i-n], n, cb, private_data);
		}
		cb("\n", private_data);
	}

}

void oc_dump_data(int level, const uint8_t *buf,int len)
{
	if (_oc_log_samba_level < level || !buf || !len)
		return;

	oc_dump_data_cb(buf, len, false, oc_debugadd_cb, &level);
}
