/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1998, 2000, 2002 Peter Miller;
 *	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 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, USA.
 *
 * MANIFEST: functions to report execution traces
 */

#include <ac/errno.h>
#include <ac/stddef.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/stdarg.h>

#include <error.h>
#include <mem.h>
#include <progname.h>
#include <str.h>
#include <trace.h>


#define INDENT 2
#define PAGE_WIDTH 79

typedef struct known_ty known_ty;
struct known_ty
{
	string_ty	*filename;
	int		flag;
	int		*flag_p;
	known_ty	*next;
};

static	string_ty	*file_name;
static	int		line_number;
static	int		page_width;
static	known_ty	*known;


static string_ty *
base_name(const char *file)
{
	const char	*cp1;
	const char	*cp2;

	cp1 = strrchr(file, '/');
	if (cp1)
		++cp1;
	else
		cp1 = file;
	cp2 = strrchr(cp1, '.');
	if (!cp2)
		cp2 = cp1 + strlen(cp1);
	if (cp2 > cp1 + 6)
		return str_n_from_c(cp1, 6);
	return str_n_from_c(cp1, cp2 - cp1);
}


int
trace_pretest(const char *file, int *result)
{
	string_ty	*s;
	known_ty	*kp;
	int		err;

	err = errno;
	s = base_name(file);
	for (kp = known; kp; kp = kp->next)
	{
		if (str_equal(s, kp->filename))
		{
			str_free(s);
			break;
		}
	}
	if (!kp)
	{
		kp = (known_ty *)mem_alloc(sizeof(known_ty));
		kp->filename = s;
		kp->next = known;
		kp->flag = 2; /* disabled */
		known = kp;
	}
	kp->flag_p = result;
	*result = kp->flag;
	errno = err;
	return *result;
}


void
trace_where(const char *file, int line)
{
	string_ty	*s;
	int		err;

	/*
	 * take new name fist, because will probably be same as last
	 * thus saving a free and a malloc (which are slow)
	 */
	err = errno;
	s = base_name(file);
	if (file_name)
		str_free(file_name);
	file_name = s;
	line_number = line;
	errno = err;
}


static void
trace_putchar(int c)
{
	static int	depth;
	static char	buffer[PAGE_WIDTH + 2];
	static char	*cp;
	static int	in_col;
	static int	out_col;
	char		*progname;

	progname = progname_get();
	if (!page_width)
	{
		page_width = PAGE_WIDTH - 24;
		if (page_width < 8)
			page_width = 8;
	}
	if (!cp)
	{
		strcpy(buffer, progname);
		cp = buffer + strlen(buffer);
		if (cp > buffer + 6)
			cp = buffer + 6;
		*cp++ = ':';
		*cp++ = '\t';
		strcpy(cp, file_name->str_text);
		cp += file_name->str_length;
		*cp++ = ':';
		*cp++ = '\t';
		sprintf(cp, "%d:\t", line_number);
		cp += strlen(cp);
		in_col = 0;
		out_col = 0;
	}
	switch (c)
	{
	case '\n':
		*cp++ = '\n';
		*cp = 0;
		fflush(stdout);
		fputs(buffer, stderr);
		fflush(stderr);
		if (ferror(stderr))
			nfatal_raw("standard error");
		cp = 0;
		break;

	case ' ':
		if (out_col)
			++in_col;
		break;

	case '\t':
		if (out_col)
			in_col = (in_col/INDENT + 1) * INDENT;
		break;

	case /*{*/'}':
	case /*(*/')':
	case /*[*/']':
		if (depth > 0)
		--depth;
		/* fall through */

	default:
		if (!out_col)
		{
			if (c != '#')
				/* modulo so never too long */
				in_col = (INDENT * depth) % page_width;
			else
				in_col = 0;
		}
		if (in_col >= page_width)
		{
			trace_putchar('\n');
			trace_putchar(c);
			return;
		}
		while (((out_col + 8) & -8) <= in_col && out_col + 1 < in_col)
		{
			*cp++ = '\t';
			out_col = (out_col + 8) & -8;
		}
		while (out_col < in_col)
		{
			*cp++ = ' ';
			++out_col;
		}
		if (c == '{'/*}*/ || c == '('/*)*/ || c == '['/*]*/)
			++depth;
		*cp++ = c;
		in_col++;
		out_col++;
		break;
	}
}


void
trace_printf(const char *s, ...)
{
	string_ty	*buffer;
	va_list		ap;
	int		err;

	err = errno;
	va_start(ap, s);
	buffer = str_vformat(s, ap);
	va_end(ap);
	for (s = buffer->str_text; *s; ++s)
		trace_putchar(*s);
	str_free(buffer);
	errno = err;
}


void
trace_enable(const char *file)
{
	string_ty	*s;
	known_ty	*kp;

	s = base_name(file);
	for (kp = known; kp; kp = kp->next)
	{
		if (str_equal(s, kp->filename))
		{
			str_free(s);
			break;
		}
	}
	if (!kp)
	{
		kp = (known_ty *)mem_alloc(sizeof(known_ty));
		kp->filename = s;
		kp->flag_p = 0;
		kp->next = known;
		known = kp;
	}
	kp->flag = 3; /* enabled */
	if (kp->flag_p)
		*kp->flag_p = kp->flag;

	/*
	 * silence a warning
	 */
#ifdef DEBUG
	trace_pretest_result = 0;
#endif
}


void
trace_char_real(const char *name, const char *vp)
{
	trace_printf("%s = '", name);
	if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
	{
		char	*s;

		s = strchr("\bb\nn\tt\rr\ff", *vp);
		if (s)
		{
			trace_putchar('\\');
			trace_putchar(s[1]);
		}
		else
			trace_printf("\\%03o", (unsigned char)*vp);
	}
	else
	{
		if (strchr("'\\", *vp))
			trace_putchar('\\');
		trace_putchar(*vp);
	}
	trace_printf("'; /* 0x%02X, %d */\n", (unsigned char)*vp, *vp);
}


void
trace_char_unsigned_real(const char *name, const unsigned char *vp)
{
	trace_printf("%s = '", name);
	if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
	{
		char	*s;

		s = strchr("\bb\nn\tt\rr\ff", *vp);
		if (s)
		{
			trace_putchar('\\');
			trace_putchar(s[1]);
		}
		else
			trace_printf("\\%03o", *vp);
	}
	else
	{
		if (strchr("'\\", *vp))
			trace_putchar('\\');
		trace_putchar(*vp);
	}
	trace_printf("'; /* 0x%02X, %d */\n", *vp, *vp);
}


void
trace_int_real(const char *name, const int *vp)
{
	trace_printf("%s = %d;\n", name, *vp);
}


void
trace_int_unsigned_real(const char *name, const unsigned int *vp)
{
	trace_printf("%s = %u;\n", name, *vp);
}


void
trace_long_real(const char *name, const long *vp)
{
	trace_printf("%s = %ld;\n", name, *vp);
}


void
trace_long_unsigned_real(const char *name, const unsigned long *vp)
{
	trace_printf("%s = %lu;\n", name, *vp);
}


void
trace_pointer_real(const char *name, const void *vptrptr)
{
	const void *const *ptr_ptr = vptrptr;
	const void	*ptr;

	ptr = *ptr_ptr;
	if (!ptr)
		trace_printf("%s = NULL;\n", name);
	else
		trace_printf("%s = 0x%08lX;\n", name, ptr);
}


void
trace_short_real(const char *name, const short *vp)
{
	trace_printf("%s = %hd;\n", name, *vp);
}


void
trace_short_unsigned_real(const char *name, const unsigned short *vp)
{
	trace_printf("%s = %hu;\n", name, *vp);
}


void
trace_string_real(const char *name, const char *vp)
{
	const char	*s;
	long		count;

	trace_printf("%s = ", name);
	if (!vp)
	{
		trace_printf("NULL;\n");
		return;
	}
	trace_printf("\"");
	count = 0;
	for (s = vp; *s; ++s)
	{
		switch (*s)
		{
		case '('/*)*/:
		case '['/*]*/:
		case '{'/*}*/:
			++count;
			break;

		case /*(*/')':
		case /*[*/']':
		case /*{*/'}':
			--count;
			break;
		}
	}
	if (count > 0)
		count = -count;
	else
		count = 0;
	for (s = vp; *s; ++s)
	{
		int	c;

		c = *s;
		if (c < ' ' || c > '~')
		{
			char	*cp;

			cp = strchr("\bb\ff\nn\rr\tt", c);
			if (cp)
				trace_printf("\\%c", cp[1]);
			else
			{
				escape:
				trace_printf("\\%03o", (unsigned char)c);
			}
		}
		else
		{
			switch (c)
			{
			case '('/*)*/:
			case '['/*]*/:
			case '{'/*}*/:
				++count;
				if (count <= 0)
					goto escape;
				break;
	
			case /*(*/')':
			case /*[*/']':
			case /*{*/'}':
				--count;
				if (count < 0)
					goto escape;
				break;

			case '\\':
			case '"':
				trace_printf("\\");
				break;
			}
			trace_printf("%c", c);
		}
	}
	trace_printf("\";\n");
}
