/*
 * Copyright (C) 2007 Martin Willi
 * Hochschule fuer Technik Rapperswil
 *
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <library.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <dlfcn.h>
#include <dirent.h>

#include "dumm.h"

/**
 * global set of UMLs guests
 */
dumm_t *dumm;

/**
 * show usage information (program arguments)
 */
static void usage()
{
	printf("Usage:\n");
	printf("  --dir|-d <path>            set working dir to <path>\n");
	printf("  --help|-h                  show this help\n");
}

/**
 * readline() wrapper
 */
static char* get_line(char *format, ...)
{
	char *line = NULL;
	char *prompt = "";
	va_list args;
	
	va_start(args, format);
	vasprintf(&prompt, format, args);
	va_end(args);
	
	while (TRUE)
	{
		line = readline(prompt);
		if (line == NULL)
		{
			printf("quit\n");
			dumm->destroy(dumm);
			clear_history();
			exit(0);
		}
		if (*line == '\0')
		{
			free(line);
			continue;
		}
		add_history(line);
		break;
	}
	free(prompt);
	return line;
}

/**
 * get a guest by name
 */
static guest_t* get_guest(char *name)
{
	iterator_t *iterator;
	guest_t *guest = NULL;
	
	iterator = dumm->create_guest_iterator(dumm);
	while (iterator->iterate(iterator, (void**)&guest))
	{
		if (streq(guest->get_name(guest), name))
		{
			break;
		}
		guest = NULL;
	}
	iterator->destroy(iterator);
	return guest;
}

/**
 * get a bridge by name
 */
static bridge_t* get_bridge(char *name)
{
	iterator_t *iterator;
	bridge_t *bridge = NULL;
	
	iterator = dumm->create_bridge_iterator(dumm);
	while (iterator->iterate(iterator, (void**)&bridge))
	{
		if (streq(bridge->get_name(bridge), name))
		{
			break;
		}
		bridge = NULL;
	}
	iterator->destroy(iterator);
	return bridge;
}

/**
 * get an interface by guest name
 */
static iface_t* get_iface(char *name, char *ifname)
{
	iterator_t *guests, *ifaces;
	guest_t *guest;
	iface_t *iface;
	
	guests = dumm->create_guest_iterator(dumm);
	while (guests->iterate(guests, (void**)&guest))
	{
		if (streq(guest->get_name(guest), name))
		{
			iface = NULL;
			ifaces = guest->create_iface_iterator(guest);
			while (ifaces->iterate(ifaces, (void**)&iface))
			{
				if (streq(iface->get_guestif(iface), ifname))
				{
					break;
				}
				iface = NULL;
			}
			ifaces->destroy(ifaces);
			if (iface)
			{
				break;
			}
		}
	}
	guests->destroy(guests);
	return iface;
}

static void guest_addif_menu(guest_t *guest)
{
	char *name;
	
	name = get_line("interface name: ");
	
	if (!guest->create_iface(guest, name))
	{
		printf("creating interface failed\n");
	}
	free(name);
}

static void guest_delif_menu(guest_t *guest)
{
	char *name;
	iface_t *iface;
	iterator_t *iterator;
	bool found = FALSE;
	
	name = get_line("interface name: ");
	
	iterator = guest->create_iface_iterator(guest);
	while (iterator->iterate(iterator, (void**)&iface))
	{
		if (streq(iface->get_guestif(iface), name))
		{
			iterator->remove(iterator);
			iface->destroy(iface);
			found = TRUE;
			break;
		}
	}
	iterator->destroy(iterator);
	
	if (!found)
	{
		printf("interface '%s' not found\n");
	}
	free(name);
}

static void guest_console(guest_t *guest)
{
	int con;
	
	for (con = 1; con <= 6; con++)
	{
		char *pts = guest->get_console(guest, con);
		if (pts)
		{
			printf("%d: %s\n", con, pts);
			free(pts);
		}
	}
}

static void guest_menu(guest_t *guest)
{
	while (TRUE)
	{
		char *line = get_line("guest/%s# ", guest->get_name(guest));
		
		if (streq(line, "back"))
		{
			free(line);
			break;
		}
		else if (streq(line, "start"))
		{
			if (guest->start(guest))
			{
				printf("guest '%s' is running\n", guest->get_name(guest));
			}
			else
			{
				printf("failed to start guest '%s'\n", guest->get_name(guest));
			}
		}
		else if (streq(line, "stop"))
		{
			printf("stopping guest '%s'...\n", guest->get_name(guest));
			guest->stop(guest);
			printf("guest '%s' is down\n", guest->get_name(guest));
		}
		else if (streq(line, "addif"))
		{
			guest_addif_menu(guest);
		}
		else if (streq(line, "delif"))
		{
			guest_delif_menu(guest);
		}
		else if (streq(line, "console"))
		{
			guest_console(guest);
		}
		else
		{
			printf("back|start|stop|addif|delif|console\n");
		}
		free(line);
	}
}

static void guest_create_menu()
{
	char *name, *kernel, *master, *mem;
	guest_t *guest;
	
	name = get_line("guest name: ");
	kernel = get_line("kernel image: ");
	master = get_line("master filesystem: ");
	mem = get_line("amount of memory in MB: ");
	
	guest = dumm->create_guest(dumm, name, kernel, master, atoi(mem));
	if (guest)
	{
		printf("guest '%s' created\n", guest->get_name(guest));
		guest_menu(guest);
	}
	else
	{
		printf("failed to create guest '%s'\n", name);
	}
	free(name);
	free(kernel);
	free(master);
	free(mem);
}

static void guest_list_menu()
{
	while (TRUE)
	{
		iterator_t *iterator;
		guest_t *guest;
		char *line = get_line("guest# ");
		
		if (streq(line, "back"))
		{
			free(line);
			break;
		}
		else if (streq(line, "list"))
		{
			iterator = dumm->create_guest_iterator(dumm);
			while (iterator->iterate(iterator, (void**)&guest))
			{
				printf("%s\n", guest->get_name(guest));
			}
			iterator->destroy(iterator);
		}
		else if (streq(line, "create"))
		{
			guest_create_menu();
		}
		else
		{
			guest = get_guest(line);
			if (guest)
			{
				guest_menu(guest);
			}
			else
			{
				printf("back|list|create|<guest>\n");
			}
		}
		free(line);
	}
}

static void bridge_addif_menu(bridge_t *bridge)
{
	char *name, *ifname;
	iface_t *iface;
	
	name = get_line("guest name: ");
	ifname = get_line("interface name: ");
	
	iface = get_iface(name, ifname);
	if (!iface)
	{
		printf("guest '%s' has no interface named '%s'\n", name, ifname);
	}
	else if (!bridge->connect_iface(bridge, iface))
	{
		printf("failed to add interface '%s' to bridge '%s'\n", ifname,
			   bridge->get_name(bridge));
	}
	free(name);
	free(ifname);
}

static void bridge_delif_menu(bridge_t *bridge)
{
	char *name, *ifname;
	iface_t *iface;
	
	name = get_line("guest name: ");
	ifname = get_line("interface name: ");
	
	iface = get_iface(name, ifname);
	if (!iface)
	{
		printf("guest '%s' has no interface named '%s'\n", name, ifname);
	}
	else if (!bridge->disconnect_iface(bridge, iface))
	{
		printf("failed to remove interface '%s' from bridge '%s'\n", ifname,
			   bridge->get_name(bridge));
	}
	free(name);
	free(ifname);
}

static void bridge_menu(bridge_t *bridge)
{
	while (TRUE)
	{
		char *line = get_line("bridge/%s# ", bridge->get_name(bridge));
		
		if (streq(line, "back"))
		{
			free(line);
			break;
		}
		else if (streq(line, "list"))
		{
			iterator_t *iterator;
			iface_t *iface;

			iterator = bridge->create_iface_iterator(bridge);
			while (iterator->iterate(iterator, (void**)&iface))
			{
				printf("%s (%s)\n", iface->get_guestif(iface), iface->get_hostif(iface));
			}
			iterator->destroy(iterator);
		}
		else if (streq(line, "addif"))
		{
			bridge_addif_menu(bridge);
		}
		else if (streq(line, "delif"))
		{
			bridge_delif_menu(bridge);
		}
		else
		{
			printf("back|list|addif|delif\n");
		}
		free(line);
	}
}

static void bridge_create_menu()
{
	char *name;
	bridge_t *bridge;
	
	name = get_line("bridge name: ");
	
	bridge = dumm->create_bridge(dumm, name);
	if (bridge)
	{
		printf("bridge '%s' created\n", bridge->get_name(bridge));
		bridge_menu(bridge);
	}
	else
	{
		printf("failed to create bridge '%s'\n", name);
	}
	free(name);
}

static void bridge_list_menu()
{
	while (TRUE)
	{
		iterator_t *iterator;
		bridge_t *bridge;
		char *line = get_line("bridge# ");
		
		if (streq(line, "back"))
		{
			free(line);
			break;
		}
		else if (streq(line, "list"))
		{
			iterator = dumm->create_bridge_iterator(dumm);
			while (iterator->iterate(iterator, (void**)&bridge))
			{
				printf("%s\n", bridge->get_name(bridge));
			}
			iterator->destroy(iterator);
		}
		else if (streq(line, "create"))
		{
			bridge_create_menu();
		}
		else
		{
			bridge = get_bridge(line);
			if (bridge)
			{
				bridge_menu(bridge);
			}
			else
			{
				printf("back|list|create|<bridge>\n");
			}
		}
		free(line);
	}
}

static void template_menu()
{
	char *name;
	
	name = get_line("template name (or 'none'): ");
	
	dumm->load_template(dumm, streq(name, "none") ? NULL : name);
	
	free(name);
}

typedef bool (*uml_test_t)(dumm_t *dumm);

static void test_menu()
{
	char *name;
	void *handle;
	struct dirent *ent;
	DIR *dir;
	uml_test_t test;
	
	name = get_line("test name: ");
	
	dir = opendir("tests");
	if (dir)
	{
		while ((ent = readdir(dir)))
		{
			char buf[PATH_MAX];
			size_t len;
			
			len = strlen(ent->d_name);
			if (strlen(ent->d_name) < 4 || !streq(ent->d_name + len - 3, ".so"))
			{
				continue;
			}
			
			snprintf(buf, sizeof(buf), "%s/%s", "tests", ent->d_name);
			handle = dlopen(buf, RTLD_LAZY);
			if (!handle)
			{
				printf("failed to open test %s\n", ent->d_name);
				continue;
			}
			test = dlsym(handle, "test");
			if (test && dumm->load_template(dumm, ent->d_name))
			{
				printf("running test %s: ", ent->d_name);
				if (test(dumm))
				{
					printf("success\n");
				}
				else
				{
					printf("failed\n");
				}
			}
			else
			{
				printf("failed to open test %s\n", ent->d_name);
			}
			dlclose(handle);
		}
	}
	free(name);
}

/**
 * Signal handler 
 */
void signal_action(int sig, siginfo_t *info, void *ucontext)
{
	dumm->destroy(dumm);
	clear_history();
	exit(0);
}

/**
 * main routine, parses args and reads from console
 */
int main(int argc, char *argv[])
{
	struct sigaction action;
	char *dir = ".";

	while (TRUE)
	{
		struct option options[] = {
			{"dir", 1, 0, 0},
			{"help", 0, 0, 0},
			{0, 0, 0, 0}
		};
		
		switch (getopt_long(argc, argv, "d:h", options, NULL)) 
		{
			case -1:
				break;
			case 'd':
				dir = optarg;
				continue;
			case 'h':
				usage();
				return 0;
			default:
				usage();
				return 1;
		}
		break;
	}
	
	memset(&action, 0, sizeof(action));
	action.sa_sigaction = signal_action;
	action.sa_flags = SA_SIGINFO;
	if (sigaction(SIGINT, &action, NULL) != 0 ||
		sigaction(SIGQUIT, &action, NULL) != 0 ||
		sigaction(SIGTERM, &action, NULL) != 0)
	{
		printf("signal handler setup failed: %m.\n");
		return 1;
	}
	
	dumm = dumm_create(dir);
	while (TRUE)
	{
		char *line = get_line("# ");
		
		if (streq(line, "quit"))
		{
			free(line);
			break;
		}
		else if (streq(line, "guest"))
		{
			guest_list_menu();
		}
		else if (streq(line, "bridge"))
		{
			bridge_list_menu();
		}
		else if (streq(line, "template"))
		{
			template_menu();
		}
		else if (streq(line, "test"))
		{
			test_menu();
		}
		else
		{
			printf("quit|guest|bridge|template|test\n");
		}
		free(line);
	}
	dumm->load_template(dumm, NULL);
	dumm->destroy(dumm);
	clear_history();
	return 0;
}

