#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>


struct in_addr {
  uint32_t s_addr;
};


/*----------------------------------------------------------------------*/

#define MAXOPTS 32

enum source {
	SRC_DEFAULT,
	SRC_TFTP,
	SRC_CD,
	SRC_HDD
};

struct file {
	enum source src;
	char *path;
	struct in_addr server;
};

struct boot {
	char *title;
	struct file kernel;
	struct file initrd;
	char *append;
};

void print_bootopt(struct boot *b) {
	printf("Boot config \"%s\"\n", b->title);
	printf("	Kernel: \"%s\" %i\n", b->kernel.path, b->kernel.src);
	if (b->initrd.path)
	printf("	Initrd: \"%s\" %i\n", b->initrd.path, b->initrd.src);
	if (b->append)
	printf("	Append: \"%s\"\n", b->append);
}

#define isspace(c) ((c)==' ' || (c)=='\r' || (c)=='\n' || (c)=='\t')

/* skip leading whitespace in string at ptr */
#define SKIP_WS(ptr) while (isspace(*ptr)) ptr++;

/* macro to parse a command and execute according code
 *
 * @ptr: pointer to command line start (will be changed)
 * @opt: command name (i.e. "timeout")
 * @optlen: strlen(opt) (i.e. 7)
 * @trailer: what follows the command? (i.e. isspace(ptr[optlen]))
 * @code: what to execute when the command is matched
 **/
#define IFOPT_X(ptr, opt, optlen, trailer, code) \
	if ((0 == strncmp(ptr, opt, optlen)) && (trailer)) { \
		ptr += optlen + 1; \
		SKIP_WS(ptr); \
		code; \
	}

/* macro to parse whitespace separated args */
#define IFOPT(ptr, opt, optlen, code) \
	IFOPT_X(ptr, opt, optlen, isspace(ptr[optlen]), code)

/* macro to parse colon separated args */
#define IFPATH(ptr, opt, optlen, code) \
	IFOPT_X(ptr, opt, optlen, ptr[optlen] == ':', code)

void parse_file(char *data, struct file *p) {
	IFPATH(data, "tftp", 4,
		p->src = SRC_TFTP;
		p->path = data;
	) else
	IFPATH(data, "cd", 2,
		p->src = SRC_CD;
		p->path = data;
	) else
	IFPATH(data, "hdd", 3,
		p->src = SRC_HDD;
		p->path = data;
	) else {
		printf("Invalid path: \"%s\"\n", data);
	}
}

/* global boot config vars:
 * boot_optc: number of boot entries
 * boot_opt: boot menu entries
 * boot_opt[0] = default fallback entries, no real boot menu
 **/
int boot_optc;
struct boot boot_opt[MAXOPTS];

int boot_timeout;
struct in_addr boot_ip, boot_netmask, boot_server;

void finish_bootopt() {
#define boc boot_opt[boot_optc]
	/* check for title and kernel set */
	if (boot_optc == 0 || (boc.title && boc.kernel.path)) {
		boot_optc++;
	} else {
		printf("Incomplete config section \"%s\"!\n",
			boc.title?boc.title:"unknown");
		memset(&boc, 0, sizeof(struct boot));
	}
#undef boc
}

void parse_input_line(char *data) {
#define boc boot_opt[boot_optc]
	char *tmp;

	SKIP_WS(data);
	IFOPT(data, "ip", 2, 
		tmp = strchr(data, '/');
		if (tmp) {
			*tmp = '\0';
			tmp++;
			inet_aton(tmp, &boot_netmask);
		}
		inet_aton(data, &boot_ip);
		printf("IP: %08x/%08x\n", boot_ip.s_addr, boot_netmask.s_addr);
	) else
	IFOPT(data, "server", 6, 
		inet_aton(data, &boot_server);
		printf("TFTP: %08x\n", boot_server.s_addr);
	) else
	IFOPT(data, "timeout", 7, 
		boot_timeout = atoi(data);
	) else
	IFOPT(data, "title", 5, 
		finish_bootopt();
		boc.title = data;
		printf("Loading kernel config %i \"%s\"\n", boot_optc, data);
	) else
	IFOPT(data, "kernel", 6, 
		parse_file(data, &boc.kernel);
	) else
	IFOPT(data, "initrd", 6, 
		parse_file(data, &boc.initrd);
	) else
	IFOPT(data, "append", 6, 
		boc.append = data;
	) else {
		printf("Ignoring unknown command: \"%s\"\n", data); 
	}
#undef boc
}

/* parse a zero terminated config file string */
void parse_config(char *data) {
	char *next, *eol, *tmp;

	boot_optc = 0;
	boot_timeout = 5;

	while (data && *data) {

		/* find EOL and replace with NUL byte */
		eol = strpbrk(data, "\r\n");
		if (eol == NULL) {
			next = NULL;
		} else {
			*eol = '\0';
			next = eol + 1;
		}
		//printf("%p param %s\n", data, data);

		parse_input_line(data);
		data = next;

		if (data)
			SKIP_WS(data);
	}
	finish_bootopt();
}

void main() {
	char buf[1024];
	int len, fd;

	fd = open("possible_xell.cfg", O_RDONLY);
	len = read(fd, buf, 1023);
	buf[len] = 0;

	parse_config(buf);
	for (len = 0; len < boot_optc; len++)
		print_bootopt(&boot_opt[len]);
}

