#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include "AVLTree.h"
#include "error.h"
#include "conffile.h"
#include "status.h"
#include "config.h"
#include "intl.h"

#define READBUF 4096

/* Various files */
#define KEEPERS VAR"/lib/debfoster/keepers"
#define STATUSFILE VAR"/lib/dpkg/status"
#define AVAILABLEFILE VAR"/lib/dpkg/available"
#define CONFIG_FILE ETC"/debfoster.conf"

/* Only simple space-separated arguments can be given */
#define INSTALLCOMMAND "apt-get install"
#define REMOVECOMMAND "apt-get --purge remove"
#define INFOCOMMAND "dpkg -s"

/* Default policies for packages */
#define MAXPRIORITY "standard"

static AVLTree *options = NULL;

#define StringConfigVal(s) const char *s = NULL; \
	static void set_##s(const char *str) { s = xstrdup(str); }

static void set_bool(int *holder, const char *val) {
	if(!strcasecmp(val, "y") || !strcasecmp(val, "yes")
	|| !strcasecmp(val, "t") || !strcasecmp(val, "true")
	|| !strcasecmp(val, "1") || !strcasecmp(val, "on"))
		*holder = 1;
	else if(!strcasecmp(val, "n") || !strcasecmp(val, "no")
 	     || !strcasecmp(val, "f") || !strcasecmp(val, "false")
 	     || !strcasecmp(val, "0") || !strcasecmp(val, "off"))
		*holder = 0;
	else error_exit(ERROR_CONFIG, _("Not a truth value: \"%s\"\n"), val);
}

#define BoolConfigVal(b) int b = 0; \
	static void set_##b(const char *str) { set_bool(&b, str); }

StringConfigVal(KeeperFile)
StringConfigVal(DpkgStatus)
StringConfigVal(DpkgAvailable)
StringConfigVal(InstallCmd)
StringConfigVal(RemoveCmd)
StringConfigVal(InfoCmd)
StringConfigVal(MaxPriority)
StringConfigVal(KeepSections)
StringConfigVal(NokeepSections)
StringConfigVal(GuessDepends)

BoolConfigVal(UseHold)
BoolConfigVal(UseEssential)
BoolConfigVal(UsePreDepends)
BoolConfigVal(UseRecommends)
BoolConfigVal(UseSuggests)
BoolConfigVal(UseTasks)

BoolConfigVal(NegativeKeepers)

BoolConfigVal(Quiet)
BoolConfigVal(Force)
BoolConfigVal(Verbose)

static int optcmp(const option_t a, const option_t b) {
	return strcasecmp(a->key, b->key);
}

option_t find_option(const char *key) {
	struct option_t o;
	AVLNode *c;

	o.key = key;

	c = AVLSearch(options, &o);

	return c?c->item:NULL;
}

static void add_option(const char *key, void (*set)(const char *), const char *def) {
	option_t o;
	o = xmalloc(sizeof *o);
	o->key = xstrdup(key);
	o->set = set;
	set(def);
	if(!AVLInsert(options, o))
		perror_exit(ERROR_CONFIG, key);
}

void readconfig(const char *config) {
	FILE *f;
	char buf[READBUF];
	option_t o;
	char *key, *value;
	int lineno = 0;

	if(!config)
		config = CONFIG_FILE;

	options = AVLAllocTree((AVLCompare)optcmp);

	/* Seed tree with defaults for all options */
	add_option("KeeperFile",    set_KeeperFile,    KEEPERS);
	add_option("DpkgStatus",    set_DpkgStatus,    STATUSFILE);
	add_option("DpkgAvailable", set_DpkgAvailable, AVAILABLEFILE);
	add_option("InstallCmd",    set_InstallCmd,    INSTALLCOMMAND);
	add_option("RemoveCmd",     set_RemoveCmd,     REMOVECOMMAND);
	add_option("InfoCmd",       set_InfoCmd,       INFOCOMMAND);
	add_option("MaxPriority",   set_MaxPriority,   MAXPRIORITY);
	add_option("KeepSections",  set_KeepSections,  "");
	add_option("NokeepSections",  set_NokeepSections, "");
	add_option("GuessDepends",  set_GuessDepends, "");

	add_option("UseHold",       set_UseHold,       "yes");
	add_option("UseEssential",  set_UseEssential,  "yes");
	add_option("UsePreDepends", set_UsePreDepends, "yes");
	add_option("UseRecommends", set_UseRecommends, "yes");
	add_option("UseSuggests",   set_UseSuggests,   "no");
	add_option("UseTasks",      set_UseTasks,      "no");

	add_option("NegativeKeepers", set_NegativeKeepers, "no");

	add_option("Quiet",         set_Quiet,         "no");
	add_option("Force",         set_Force,         "no");
	add_option("Verbose",       set_Verbose,       "no");

	/* Read the options but fail if there's no default for it */
	f = fopen(config, "r");
	if(f) {
		while(fgets(buf, READBUF, f)) {
			lineno++;
			key = chop(buf);
			if(*key && *key != '#') {
				value = strchr(key, '=');
				if(!value)
					error_exit(ERROR_CONFIG, _("Syntax error in %s:%d\n"), CONFIG_FILE, lineno);
				*value++ = '\0';
				key = chop(key);
				if(!(o = find_option(key)))
					error_exit(ERROR_CONFIG, _("Unknown option `%s' at %s:%d\n"), key, CONFIG_FILE, lineno);
				o->set(chop(value));
			}
		}
	}
}
