/*
 * Copyright (C) 2000-2003 ASANO Masahiro
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "crash.h"

PRIVATE addr_t alias(), help(), quit(), function(), set();
const commandtable_t
	command_alias = {"alias", alias, "[name[=value]]", "Print the list of aliases with no arguments aliases. When an argument is supplied, an alias is defined for name."},
	command_function = {"function", function, "[name [ { list; } ] ]", "print/define function\ncurrently, function-define is not supported"},
	command_help = {"help", help, "[command]", "print command usage"},
	command_quit = {"quit", quit, "", "terminate this session"},
	command_question = {"?", help, "[command]", "print command usage"},
	command_set = {"set", set, "[name=value]", "set or list variable"};

const commandtable_t **commandtable;

PRIVATE addr_t
quit()
{
	exit(0);
}

void
introduce_myself()
{
	extern const char kernel[], version[], builddate[];

	mprintf("%s %s (build %s) for Linux %s\n", APPNAME, version, builddate, kernel);
}

PRIVATE addr_t
help()
{
	int i, j, ncmd, partial, len;
	addr_t (*func)();

	for (ncmd = 0; commandtable[ncmd]; ncmd++)
		;

	switch (argcnt) {
	case 1:
		introduce_myself();
		mprintf("\n");
#define STEP 5
		for (i = 0; i * STEP < ncmd; i++) {
			int colm = 0;
			for (j = 0; j < STEP; j++) {
				int x = ((ncmd + STEP - 1) / STEP) * j + i;
				if (x >= ncmd)
					break;
				if (j) {
					int n = j * (80/STEP) - colm;
					if (n <= 0)
						n = 1;
					mprintstr("                       ", n);
					colm += n;
				}
				colm += mprintstr(commandtable[x]->name, 999);
			}
			mprintf("\n");
		}
		mprintf("\nType `%s' followed by command name for documentation.\n", args[0]);
		return 0;

	case 2:
		break;

	default: /* (argcnt >= 3) */
		THROW(usage);
	}

	for (i = 1; i < ncmd; i++) {
		if (strcmp(commandtable[i]->name, args[1]) == 0)
			goto found;
	}

	len = strlen(args[1]);
	partial = -1;
	for (i = 1; i < ncmd; i++) {
		if (strncmp(commandtable[i]->name, args[1], len) == 0) {
			if (partial != -1)
				goto ambigous;
			partial = i;
		}
	}
	if (partial != -1) {
		i = partial;
		goto found;
	}

	THROWF("%s: command not found", args[1]);

 ambigous:
	mprintf("%s:", args[1]);
	for (i = 1; i < ncmd; i++) {
		if (strncmp(commandtable[i]->name, args[1], len) == 0) {
			mprintf(" %s", commandtable[i]->name);
		}
	}
	mprintf("\n");
	return -1;

 found:
	mprintf("usage: %s %s\n%s\n",
		commandtable[i]->name,
		commandtable[i]->args,
		commandtable[i]->help);
	func = commandtable[i]->func;
	for (j = 1; j < ncmd; j++) {
		if (i != j && commandtable[j]->func == func)
			mprintf("`%s' is the same command.\n",
				commandtable[j]->name);
	}
	return 0;
}

void
print_alias(n)
	int n;
{
	if (aliastable[n].keyin == NULL || aliastable[n].alias == NULL)
		return;
	mprintf("alias %s='%s'\n", aliastable[n].keyin, aliastable[n].alias);
}

int
geteql(str, name, value)
const char *str;
char **name, **value;
{
	int i;

	*name = *value = NULL;

	if (!isalpha(str[0]) && str[0] != '_') {
		return -1;
	}

	for (i = 1; str[i]; i++) {
		if (str[i] == '=') {
			*value = strdup(str + i + 1);
			break;
		} else if (!isalnum(str[i]) && str[i] != '_') {
			return -1;
		}
	}

	if ((*name = malloc(i + 1)) == NULL) {
		return -1;
	}
	memcpy(*name, str, i);
	(*name)[i] = '\0';
	return 0;
}

PRIVATE addr_t
alias()
{
	int i, j;
	char *name, *value;
	struct aliastable *p;

	if (argcnt == 1) {
		for (i = 0; i < aliastable_size; i++) {
			print_alias(i);
		}
		return 0;
	}

	for (i = 1; i < argcnt; i++) {
		if (geteql(args[i], &name, &value) == -1) {
			THROW("invalid alias");
		}
		for (j = 0; j < aliastable_size; j++) {
			if (strcmp(name, aliastable[j].keyin) == 0)
				break;
		}
		if (j >= aliastable_size && value == NULL) {
			free(name);
			THROW("alias: not found");
		}
		if (j >= aliastable_size) {
			p = realloc(aliastable, (aliastable_size + 1) * sizeof(struct aliastable));
			if (p == NULL) {
				free(name);
				free(value);
				THROW("memory error");
			}
			p[j].keyin = name;
			p[j].alias = value;
			p[j].keyin_sz = strlen(name);
			p[j].alias_sz = strlen(value);
			aliastable = p;
			aliastable_size++;
		} else if (value) {
			free(name);
			if (aliastable[j].alias)
				free(aliastable[j].alias);
			aliastable[j].alias = value;
			aliastable[j].alias_sz = strlen(value);
		}
		print_alias(j);
	}
	return 0;
}

void
print_variable(n)
	int n;
{
	if (variabletable == NULL)
		return;
	if (variabletable[n].name == NULL || variabletable[n].value == NULL)
		return;
	mprintf("%s='%s'\n", variabletable[n].name, variabletable[n].value);
}

PRIVATE addr_t
set()
{
	int i;

	if (argcnt == 1) {
		for (i = 0; i < variabletable_size; i++) {
			print_variable(i);
		}
		return 0;
	}
	for (i = 1; i < argcnt; i++) {
		setvariable(args[i]);
	}
	return 0;
}

void
setvariable(str)
	const char *str;
{
	int i;
	char *name, *value;
	struct variabletable *p;

	if (geteql(str, &name, &value) == -1) {
		THROW("invalid command");
	}
	for (i = 0; i < variabletable_size; i++) {
		if (strcmp(name, variabletable[i].name) == 0)
			break;
	}
	if (value == NULL) {
		THROW("invalid command");
	}
	if (i >= variabletable_size) {
		p = realloc(variabletable, (variabletable_size + 1) * sizeof(struct variabletable));
		if (p == NULL) {
			free(name);
			free(value);
			THROW("memory error");
		}
		p[i].name = name;
		p[i].value = value;
		p[i].name_sz = strlen(name);
		p[i].value_sz = strlen(value);
		variabletable = p;
		variabletable_size++;
	} else if (value) {
		free(name);
		if (variabletable[i].value)
			free(variabletable[i].value);
		variabletable[i].value = value;
		variabletable[i].value_sz = strlen(value);
	}
}

void
print_statement(stmt, lvl)
	struct statement *stmt;
	int lvl;
{
	int i;

	while (stmt) {
		for (i = 0; i < lvl; i++)
			mprintf("  ");
		mprintf("%s\n", stmt->line);
		switch (stmt->st_type) {
		case IF_CONVENTION:
			if (stmt->ext1) {
				for (i = 0; i < lvl; i++)
					mprintf("  ");
				mprintf("then\n");
				print_statement(stmt->ext1, lvl + 1);
			}
			if (stmt->ext2) {
				for (i = 0; i < lvl; i++)
					mprintf("  ");
				mprintf("else\n");
				print_statement(stmt->ext2, lvl + 1);
			}
			for (i = 0; i < lvl; i++)
				mprintf("  ");
			mprintf("endif\n");
			break;
		case WHILE:
			for (i = 0; i < lvl; i++)
				mprintf("  ");
			mprintf("do\n");
			print_statement(stmt->ext1, lvl + 1);
			for (i = 0; i < lvl; i++)
				mprintf("  ");
			mprintf("done\n");
			break;
		default:
			break;
		}
		stmt = stmt->next;
	}
}

void
print_function(n)
	int n;
{
	mprintf("function %s()\n{\n", functiontable[n].name);
	print_statement(functiontable[n].stmt, 1);
	mprintf("}\n");
}

PRIVATE int
wordcheck(str)
	const char *str;
{
	if (!isascii(*str) && *str != '_')
		return -1;
	str++;
	while (isalnum(*str) || *str == '_')
		str++;
	if (*str != '\0')
		return -1;
	return 0;
}

PRIVATE addr_t
function()
{
	int i;

	if (argcnt == 1) {
		for (i = 0; i < functiontable_size; i++) {
			print_function(i);
		}
		return -1;
	}
	if (wordcheck(args[1]) == -1) {
		THROW("invalid function name");
	}
	if (argcnt == 2) {
		for (i = 0; i < functiontable_size; i++) {
			if (strcmp(args[1], functiontable[i].name) == 0)
				print_function(i);
		}
		return -1;
	}
	THROW("currently, function-define is not implemented");
	return 0;
}

PRIVATE int
compare(cmda, cmdb)
	const commandtable_t **cmda, **cmdb;
{
	return strcmp((*cmda)->name, (*cmdb)->name);
}

void
init_command()
{
	int n;
	const commandtable_t **p;

	n = ncmdtable_base + ncmdtable_arch + ncmdtable_vers + ncmdtable_xfs + 1;
	if ((commandtable = malloc(n * sizeof(commandtable_t *))) == NULL) {
		perror("malloc");
		exit(1);
	}
	p = commandtable;
	memcpy(p, commandtable_base, sizeof(commandtable_t *) * ncmdtable_base);
	p += ncmdtable_base;
	memcpy(p, commandtable_vers, sizeof(commandtable_t *) * ncmdtable_vers);
	p += ncmdtable_vers;
	memcpy(p, commandtable_arch, sizeof(commandtable_t *) * ncmdtable_arch);
	p += ncmdtable_arch;
	memcpy(p, commandtable_xfs,  sizeof(commandtable_t *) * ncmdtable_xfs);
	qsort(commandtable + 1, n - 2, sizeof(commandtable_t *), compare);
	commandtable[n - 1] = NULL;
}
