/*
 * Copyright (C) 2008-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CALLER	0

#define NHASH	0x10000

struct line {
	struct line *real_prev;
	struct line *real_next;

	struct line *hash_next;

	enum { TEXT, FUNC, CODE } type;

	/* text */
	char *text_string;

	/* func */
	int func_addrspace;
	unsigned long func_addr;
	char *func_string;
	int func_number;
	unsigned long func_count_caller;
	unsigned long func_count_direct;
	unsigned long func_count_callee;
#if CALLER
	unsigned long *func_count;
#endif

	/* code */
	int code_addrspace;
	unsigned long code_addr;
	char *code_string;
	unsigned long code_hit;
};
struct line *line_first;
struct line *line_last;
struct line *line_hash[NHASH];
struct line *line_func[0x10000];
int nfuncs;


static void
line_add(struct line *line)
{
	line->real_prev = line_last;
	line->real_next = (struct line *) 0;
	if (line->real_prev == (struct line *) 0) {
		line_first = line;
	} else {
		line->real_prev->real_next = line;
	}
	line_last = line;

	if (line->type == FUNC) {
		assert(nfuncs < 0x10000);
		line_func[nfuncs++] = line;
	}

	if (line->type == CODE) {
		line->hash_next = line_hash[line->code_addr % NHASH];
		line_hash[line->code_addr % NHASH] = line;
	}
}

static void
fake_user(void)
{
	struct line *line;

	line = (struct line *) malloc(sizeof(struct line));
	assert(line != (struct line *) 0);

	line->type = FUNC;
	line->func_addrspace = 0;
	line->func_addr = 0x00000000;
	line->func_string = strdup("user program");
	assert(line->func_string != (char *) 0);
	line->func_number = nfuncs;

	line_add(line);


	line = (struct line *) malloc(sizeof(struct line));
	assert(line != (struct line *) 0);

	line->type = CODE;
	line->code_addrspace = 0;
	line->code_addr = 0x00000000;
	line->code_string = strdup("...");
	assert(line->code_string != (char *) 0);

	line_add(line);
}

static struct line *
read_line(FILE *fp, int addrspace)
{
	char line[1024];
	struct line *l;
	int c;
	int x;
	unsigned long addr;

	/*
	 * Read line.
	 */
	c = fgetc(fp);
	if (c == -1) {
		return (struct line *) 0;
	}
	x = 0;
	while (c != '\n') {
		line[x++] = (char) c;
		c = fgetc(fp);
	}
	line[x] = '\0';

	/*
	 * Save to list element.
	 */
	l = malloc(sizeof(struct line));
	assert(l != (struct line *) 0);

	addr = 0;
	for (x = 0; ; x++) {
		if (line[x] == ':') {
			/* Code */
			char *p;

			/* Skip white space. */
			for (p = &line[9];
			    *p == ' ' || *p == '\t';
			    p++) {
			}

			l->type = CODE;
			l->code_addrspace = addrspace;
			l->code_addr = addr;
			l->code_string = strdup(p);
			assert(l->code_string != (char *) 0);
			break;

		} else if (line[x] == '<') {
			/* Label */
			char *p;

			p = strchr(line, '<');
			assert(p != (char *) 0);
			p++;
			assert(strchr(p, '>') != (char *) 0);
			*strchr(p, '>') = '\0';

			l->type = FUNC;
			l->func_addrspace = addrspace;
			l->func_addr = addr;
			l->func_string = strdup(p);
			assert(l->func_string != (char *) 0);
			l->func_number = nfuncs;
			break;
		}

		if (line[x] == ' ') {
			/* Skip spaces. */

		} else if ('0' <= line[x] && line[x] <= '9') {
			addr *= 16;
			addr += line[x] - '0';

		} else if ('a' <= line[x] && line[x] <= 'f') {
			addr *= 16;
			addr += line[x] - 'a' + 10;

		} else {
			/* Text */
			l->text_string = strdup(line);
			assert(l->text_string != (char *) 0);
			break;
		}
	}

	return l;
}

static void
read_list(char *filename, int addrspace)
{
	FILE *fp;
	struct line *line;

	fp = fopen(filename, "r");
	assert(fp != (FILE *) 0);

	for (;;) {
		line = read_line(fp, addrspace);

		if (line == (struct line *) 0) {
			break;
		}

		line_add(line);
	}

	fclose(fp);
}

static unsigned long
read_address(FILE *fp)
{
	unsigned long address;
	int c;

	c = fgetc(fp);

	address = 0;
	while (('0' <= c && c <= '9')
	    || ('A' <= c && c <= 'F')
	    || ('a' <= c && c <= 'f')) {
		address *= 16;
		if ('0' <= c && c <= '9') {
			address += c - '0';
		} else if ('A' <= c && c <= 'F') {
			address += c - 'A' + 10;
		} else if ('a' <= c && c <= 'f') {
			address += c - 'a' + 10;
		}
		c = fgetc(fp);
	}

	ungetc(c, fp);

	return address;
}

static struct line *
lookup_line(unsigned long addr)
{
	struct line *line;

	for (line = line_hash[addr % NHASH]; ; line = line->hash_next) {
		if (line == (struct line *) 0) {
			for (line = line_first;
			    line->type != CODE;
			    line = line->real_next) {
			}
			break;
		}
		assert(line->type == CODE);
		if (line->code_addr == addr) {
			break;
		}
	}

	return line;
}

static struct line *
lookup_func(unsigned long addr)
{
	struct line *func;

	func = lookup_line(addr);
	do {
		func = func->real_prev;
	} while (func->type != FUNC);

	return func;
}

static void
prof_one(int direct, unsigned long callee, unsigned long caller)
{
	struct line *f_callee;
	struct line *f_caller;
	struct line *line;

	f_callee = lookup_func(callee);
	assert(f_callee != (struct line *) 0);
	if (direct) {
		f_callee->func_count_direct++;
	}

	f_caller = lookup_func(caller);
	assert(f_caller != (struct line *) 0);
	assert(0 <= f_caller->func_number && f_caller->func_number < nfuncs);
#if CALLER
	f_callee->func_count[f_caller->func_number]++;
#endif

	f_caller->func_count_caller++;
	f_callee->func_count_callee++;

	if (direct) {
		line = lookup_line(callee);
		line->code_hit++;
	}
}

static void
prof(char *filename)
{
	struct line *l;
	FILE *fp;
	int line;

	for (l = line_first; l != (struct line *) 0; l = l->real_next) {
		if (l->type != FUNC) {
			continue;
		}

#if CALLER
		l->func_count = malloc(sizeof(unsigned long) * nfuncs);
		assert(l->func_count != (unsigned long *) 0);
#endif

		l->func_count_caller = 0;
		l->func_count_direct = 0;
		l->func_count_callee = 0;
#if CALLER
		memset(l->func_count, 0, sizeof(unsigned long) * nfuncs);
#endif
	}

	fp = fopen(filename, "r");
	assert(fp != (FILE *) 0);

	line = 1;
	for (;;) {
		int direct;
		unsigned long addr0;
		unsigned long addr1;
		int c;

		c = fgetc(fp);
		if (c == EOF) {
			break;
		}
		ungetc(c, fp);

		direct = 1;

		/* read first address */
		addr0 = read_address(fp);

		/* read next addresses */
		c = fgetc(fp);
		while (c == ' ') {
			ungetc(c, fp);

			/* skip space */
			c = fgetc(fp);
			assert(c == ' ');

			/* read address */
			addr1 = read_address(fp);

			/* increment indirect hits */
			prof_one(direct, addr0, addr1);

			direct = 0;
			addr0 = addr1;

			c = fgetc(fp);
		}
		ungetc(c, fp);

		prof_one(direct, addr0, 0x00000000);

		/* check eoln */
		c = fgetc(fp);
		if (c != '\n') fprintf(stderr, "%s: %d: syntax error\n",
				filename, line);
		assert(c == '\n');

		line++;
	}

	fclose(fp);
}

static int
sort_name(struct line *f0, struct line *f1)
{
	return strcmp(f0->func_string, f1->func_string);
}

static int
sort_count_direct(struct line *f0, struct line *f1)
{
	return f0->func_count_direct - f1->func_count_direct;
}

static int
sort_count(struct line *f0, struct line *f1)
{
	return (f0->func_count_direct + f0->func_count_caller)
	     - (f1->func_count_direct + f1->func_count_caller);
}

static void
sort(int (*cmp)(struct line *, struct line *))
{
	int prev;
	int next;
	int done;

	if (nfuncs < 2) {
		/*
		 * Nothing to sort.
		 */
		return;
	}

	do {
		done = 1;

		for (prev = 0, next = 1;
		    next != nfuncs;
		    prev = next, next = next + 1) {
			if (0 < (*cmp)(line_func[prev], line_func[next])) {
				struct line *tmp;

				tmp = line_func[next];
				line_func[next] = line_func[prev];
				line_func[prev] = tmp;

				done = 0;
			}
		}
	} while (! done);
}

static void
output_list(void)
{
	struct line *line;
	unsigned long count_direct;

	count_direct = 0;
	for (line = line_first;
	    line != (struct line *) 0;
	    line = line->real_next) {
		switch (line->type) {
		case TEXT:
			printf("            %s\n", line->text_string);
			break;
		case FUNC:
			count_direct = line->func_count_direct;
			printf("%8ld     %08lx <%s>\n",
					count_direct,
					line->func_addr, line->func_string);
			break;
		case CODE:
			if (count_direct == 0) count_direct = 1;
			printf("%8ld/%2ld%% %08lx:\t%s\n",
					line->code_hit,
					line->code_hit * 100 / count_direct,
					line->code_addr, line->code_string);
			break;
		default:
			assert(0);
		}
	}

	printf("\n");
}

static void
output_count_direct(void)
{
	int nr;
	struct line *f;
	unsigned long count;
	unsigned long sum;

	printf("Functions (direct hits):\n");

	count = 0;
	for (nr = 0; nr < nfuncs; nr++) {
		f = line_func[nr];

		if (f->func_addrspace != 1) continue;

		count += f->func_count_direct;
	}

	sum = count;
	for (nr = 0; nr < nfuncs; nr++) {
		unsigned long c;

		f = line_func[nr];

		if (f->func_addrspace != 1) continue;

		if (f->func_count_direct == 0) {
			continue;
		}

		c = f->func_count_direct;
		assert(c <= count);

		printf("%6ld / %5.2f%%", sum, sum * 100.0 / count);
		printf(" ");
		printf("%6ld / %5.2f%%", c, c * 100.0 / count);

		printf(" ");

		switch (f->func_addrspace) {
		case 0: printf("U"); break;
		case 1: printf("S"); break;
		default: assert(0);
		}

		printf(" ");

		printf("%s (%08lx)\n", f->func_string, f->func_addr);

		sum -= c;
	}

	printf("\n");
}

static void
output_count(void)
{
	int nr;
	struct line *f;
	unsigned long count;

	printf("Functions (direct and caller hits):\n");

	count = 0;
	for (nr = 0; nr < nfuncs; nr++) {
		f = line_func[nr];

		if (f->func_addrspace != 1) continue;

		count += f->func_count_direct;
	}

	for (nr = 0; nr < nfuncs; nr++) {
		unsigned long c;

		f = line_func[nr];

		if (f->func_addrspace != 1) continue;

		if (f->func_count_direct == 0) {
			continue;
		}

		c = f->func_count_direct + f->func_count_caller;
		/* assert(c <= count); FIXME VOSSI */

		printf("%6ld / %5.2f%%", c, c * 100.0 / count);

		printf(" ");

		switch (f->func_addrspace) {
		case 0: printf("U"); break;
		case 1: printf("S"); break;
		default: assert(0);
		}

		printf(" ");

		printf("%s (%08lx)\n", f->func_string, f->func_addr);
	}

	printf("\n");
}

static void
output_caller(void)
{
	int nr;
	struct line *f;
	int nr_callee;
	struct line *f_callee;
	int nr_caller;
	struct line *f_caller;
	unsigned int n;
	unsigned long count;

	printf("Functions:\n");

	count = 0;
	for (nr = 0; nr < nfuncs; nr++) {
		f = line_func[nr];

		if (f->func_addrspace != 1) {
			continue;
		}

		count += f->func_count_direct;
	}

	for (nr = 0; nr < nfuncs; nr++) {
		f = line_func[nr];

		if (f->func_addrspace != 1) {
			/* Not in simulator => no output. */
			continue;
		}

		if (f->func_count_callee == 0
		 && f->func_count_direct == 0
		 && f->func_count_caller == 0) {
			/* Not called, not calling => no output. */
			continue;
		}

		printf("%6ld / %5.2f%% %s (%08lx)\n",
				f->func_count_direct + f->func_count_caller,
				(f->func_count_direct + f->func_count_caller) * 100.0 / count,
				f->func_string, f->func_addr);

#if CALLER
		/*
		 * Callee statistic.
		 */
		if (f->func_count_callee != 0) {
			printf("\t\t\tcalled by\n");

			f_callee = f;
			for (n = 0; n < nfuncs; n++) {
				if (f_callee->func_count[n] != 0) {
					for (nr_caller = 0;
					    line_func[nr_caller]->func_number != n;
					    nr_caller++) {
					}
					f_caller = line_func[nr_caller];
					printf("\t\t\t");
					printf("%6lu / %5.2f%% %s (%08lx)",
						f_callee->func_count[n],
						f_callee->func_count[n] * 100.0 / f->func_count_callee,
						f_caller->func_string,
						f_caller->func_addr);
					printf("\n");
				}
			}
		}
#endif

		/*
		 * Hit statistic.
		 */
		if (f->func_count_direct != 0) {
			printf("\t\t\tdirect hits\n");

			printf("\t\t\t");
			printf("%6lu / %5.2f%%",
					f->func_count_direct,
					f->func_count_direct * 100.0 / count);

			printf("\n");
		}

#if CALLER
		/*
		 * Caller statistic.
		 */
		if (f->func_count_caller != 0) {
			printf("\t\t\tcalling\n");

			f_caller = f;
			for (nr_callee = 0; nr_callee < nfuncs; nr_callee++) {
				f_callee = line_func[nr_callee];
				if (f_callee->func_count[f_caller->func_number] != 0) {
					printf("\t\t\t");
					printf("%6lu / %5.2f%% %s (%08lx)",
						f_callee->func_count[f_caller->func_number],
						f_callee->func_count[f_caller->func_number] * 100.0 / f->func_count_caller,
						f_callee->func_string,
						f_callee->func_addr);
					printf("\n");
				}
			}
		}
#endif

		printf("\n");
	}
}

static void
output_sum(void)
{
	unsigned long count;
	unsigned long count_user;
	unsigned long count_sim;
	struct line *line;

	count = 0;
	count_user = 0;
	count_sim = 0;
	for (line = line_first;
	    line != (struct line *) 0;
	    line = line->real_next) {
		if (line->type != CODE) {
			continue;
		}
		switch (line->code_addrspace) {
		case 0: count_user += line->code_hit; break;
		case 1: count_sim += line->code_hit; break;
		default: assert(0);
		}
		count += line->code_hit;
	}

	printf("Sum:\n");
	printf("U %10lu (%f%%)\n", count_user, count_user * 100.0 / count);
	printf("S %10lu (%f%%)\n", count_sim, count_sim * 100.0 / count);

	printf("\n");

	printf("Time:\n");
	printf("Ticks: %lu\n", count_user + count_sim);
}

int
main(int argc, char **argv)
{
	assert(argc == 3);

	fake_user();
	read_list(argv[1], 1);
	prof(argv[2]);

	output_list();

	sort(sort_count_direct);
	output_count_direct();

	sort(sort_count);
	output_count();

	sort(sort_count);
	output_caller();

	output_sum();

	return 0;
}
