/* Simple debug functions for level and category filtering
 *
 * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
 * All Rights Reserved
 *
 * 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include "debug.h"

const char *debug_level[] = {
	"debug  ",
	"info   ",
	"notice ",
	"error  ",
	NULL,
};

struct debug_cat {
	const char *name;
	const char *color;
} debug_cat[] = {
	{ "options", "\033[0;33m" },
	{ "sender", "\033[1;33m" },
	{ "sound", "\033[0;35m" },
	{ "dsp", "\033[0;31m" },
	{ "anetz", "\033[1;34m" },
	{ "bnetz", "\033[1;34m" },
	{ "cnetz", "\033[1;34m" },
	{ "nmt", "\033[1;34m" },
	{ "amps", "\033[1;34m" },
	{ "r2000", "\033[1;34m" },
	{ "imts", "\033[1;34m" },
	{ "mpt1327", "\033[1;34m" },
	{ "jollycom", "\033[1;34m" },
	{ "eurosignal", "\033[1;34m" },
	{ "pocsag", "\033[1;34m" },
	{ "golay", "\033[1;34m" },
	{ "5-ton-folge", "\033[1;34m" },
	{ "frame", "\033[0;36m" },
	{ "call", "\033[0;37m" },
	{ "cc", "\033[1;32m" },
	{ "database", "\033[0;33m" },
	{ "transaction", "\033[0;32m" },
	{ "dms", "\033[0;33m" },
	{ "sms", "\033[1;37m" },
	{ "sdr", "\033[1;31m" },
	{ "uhd", "\033[1;35m" },
	{ "soapy", "\033[1;35m" },
	{ "wave", "\033[1;33m" },
	{ "radio", "\033[1;34m" },
	{ "am791x", "\033[0;31m" },
	{ "uart", "\033[0;32m" },
	{ "device", "\033[0;33m" },
	{ "datenklo", "\033[1;34m" },
	{ "zeit", "\033[1;34m" },
	{ "sim layer 1", "\033[0;31m" },
	{ "sim layer 2", "\033[0;33m" },
	{ "sim ICL layer", "\033[0;36m" },
	{ "sim layer 7", "\033[0;37m" },
	{ "mtp layer 2", "\033[1;33m" },
	{ "mtp layer 3", "\033[1;36m" },
	{ "MuP", "\033[1;37m" },
	{ "router", "\033[1;35m" },
	{ "stderr", "\033[1;37m" },
	{ "ss5", "\033[1;34m" },
	{ "isdn", "\033[1;35m" },
	{ "misdn", "\033[0;34m" },
	{ "dss1", "\033[1;34m" },
	{ "sip", "\033[1;35m" },
	{ "telephone", "\033[1;34m" },
	{ "uk0", "\033[1;34m" },
	{ "ph", "\033[0;33m" },
	{ "dcf77", "\033[1;34m" },
	{ "jitter", "\033[0;36m" },
	{ NULL, NULL }
};

int debuglevel = DEBUG_INFO;
int debug_date = 0;
uint64_t debug_mask[2] = { ~0, ~0 };
extern int num_kanal;

void (*clear_console_text)(void) = NULL;
void (*print_console_text)(void) = NULL;

int debug_limit_scroll = 0;

static int lock_initialized = 0;
static pthread_mutex_t debug_mutex;

void lock_debug(void)
{
	int rc;

	if (!lock_initialized) {
		rc = pthread_mutex_init(&debug_mutex, NULL);
		if (rc == 0)
			lock_initialized = 1;
	}
	if (lock_initialized)
		pthread_mutex_lock(&debug_mutex);
}

void unlock_debug(void)
{
	if (lock_initialized)
		pthread_mutex_unlock(&debug_mutex);
}

void get_win_size(int *w, int *h)
{
	struct winsize win;
	int rc;

	rc = ioctl(0, TIOCGWINSZ, &win);
	if (rc) {
		*w = 80;
		*h = 25;
		return;
	}

	*h = win.ws_row;
	*w = win.ws_col;
}

void _printdebug(const char *file, const char __attribute__((unused)) *function, int line, int cat, int level, const char *kanal, const char *fmt, ...)
{
	char buffer[4096], *b = buffer;
	int s = sizeof(buffer) - 1;
	const char *p;
	va_list args;
	int w, h = 0; // make GCC happy

	if (debuglevel > level)
		return;

	if (!(debug_mask[cat >> 6] & ((uint64_t)1 << (cat & 63))))
		return;

	lock_debug();

	buffer[sizeof(buffer) - 1] = '\0';

	/* if kanal is used, prefix the channel number */
	if (num_kanal > 1 && kanal) {
		sprintf(buffer, "(chan %s) ", kanal);
		b = strchr(buffer, '\0');
		s -= strlen(buffer);
	}

	va_start(args, fmt);
	vsnprintf(b, s, fmt, args);
	va_end(args);

	while ((p = strchr(file, '/')))
		file = p + 1;
	if (clear_console_text)
		clear_console_text();
	if (debug_limit_scroll) {
		get_win_size(&w, &h);
		printf("\0337\033[%d;%dr\0338", debug_limit_scroll + 1, h);
	}
	if (debug_date) {
		struct timeval tv;
		struct tm *tm;

		gettimeofday(&tv, NULL);
		tm = localtime(&tv.tv_sec);

		printf("%04d-%02d-%02d %02d:%02d:%02d.%03d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(tv.tv_usec / 10000.0));
	}
	printf("%s%s %4d %s-%s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_cat[cat].name, debug_level[level], buffer);
	if (debug_limit_scroll)
		printf("\0337\033[%d;%dr\0338", 1, h);
	if (print_console_text)
		print_console_text();
	fflush(stdout);

	unlock_debug();
}

const char *debug_amplitude(double level)
{
	static char text[42];

	strcpy(text, "                    :                    ");
	if (level > 1.0)
		level = 1.0;
	if (level < -1.0)
		level = -1.0;
	text[20 + (int)(level * 20)] = '*';

	return text;
}

#define level2db(level)         (20 * log10(level))

const char *debug_db(double level_db)
{
	static char text[128];
	int l;

	strcpy(text, ":  .  :  .  :  .  :  .  :  .  :  .  :  .  :  .  |  .  :  .  :  .  :  .  :  .  :  .  :  .  :  .  :");
	if (level_db <= 0.0)
		return text;
	l = (int)round(level2db(level_db));
	if (l > 48)
		return text;
	if (l < -48)
		return text;
	text[l + 48] = '*';

	return text;
}

void debug_print_help(void)
{
	printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
	printf("        Use 'list' to get a list of all levels and categories\n");
	printf("        Verbose level: digit of debug level (default = '%d')\n", debuglevel);
	printf("        Verbose level+category: level digit followed by one or more categories\n");
	printf("        -> If no category is specified, all categories are selected\n");
	printf(" -v --verbose date\n");
	printf("        Show date with debug output\n");
}

void debug_list_cat(void)
{
	int i;

	printf("Give number of debug level:\n");
	for (i = 0; debug_level[i]; i++)
		printf(" %d = %s\n", i, debug_level[i]);
	printf("\n");

	printf("Give name(s) of debug category:\n");
	for (i = 0; debug_cat[i].name; i++)
		printf(" %s%s\033[0;39m\n", debug_cat[i].color, debug_cat[i].name);
	printf("\n");
}

int parse_debug_opt(const char *optarg)
{
	int i, max_level = 0;
	char *dup, *dstring, *p;

	if (!strcasecmp(optarg, "date")) {
		debug_date = 1;
		return 0;
	}

	for (i = 0; debug_level[i]; i++)
		max_level = i;
	
	dup = dstring = strdup(optarg);
	p = strsep(&dstring, ",");
	for (i = 0; i < p[i]; i++) {
		if (p[i] < '0' || p[i] > '9') {
			fprintf(stderr, "Only digits are allowed for debug level!\n");
			free(dup);
			return -EINVAL;
		}
	}
	debuglevel = atoi(p);
	if (debuglevel > max_level) {
		fprintf(stderr, "Debug level too high, use 'list' to show available levels!\n");
		free(dup);
		return -EINVAL;
	}
	if (dstring)
		memset(debug_mask, 0, sizeof(debug_mask));
	while((p = strsep(&dstring, ","))) {
		for (i = 0; debug_cat[i].name; i++) {
			if (!strcasecmp(p, debug_cat[i].name))
				break;
		}
		if (!debug_cat[i].name) {
			fprintf(stderr, "Given debug category '%s' unknown, use 'list' to show available categories!\n", p);
			free(dup);
			return -EINVAL;
		}
		debug_mask[i >> 6] |= ((uint64_t)1 << (i & 63));
	}

	free(dup);
	return 0;
}

const char *debug_hex(const uint8_t *data, int len)
{
	static char *text = NULL;
	char *p;
	int i;

	if (text)
		free(text);
	p = text = calloc(1, len * 3 + 1);
	for (i = 0; i < len; i++) {
		sprintf(p, "%02x ", *data++);
		p += 3;
	}
	if (text[0])
		p[-1] = '\0';

	return text;
}

