#include <stdio.h>
#include <memory.h>
#include <bool.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>

#include "darray.h"
#include "registry.h"
#include "FieldCore.h"
#include "action.h"
#include "verbrgy.h"
#include "profile.h"
#include "al.h"
#include "OSinter.h"

#include "rule.h"
#include "ruleset.h"

#include "RSreg.h"
#include "RSregP.h"

#define RSREG_FILE	"RSreg"

AlRSregSt AlRSR = {
	(Registry)0,
	(Darray)0,
	Bool_FALSE
};

extern char	*sys_errlist[];
const char	*RSdir;
/* Support functions are the following few */

/* escape_string plows through str, counts how many \'s and "'s there are,
   then makes a new string with thoughs occurences preceeded by \
   RETURNS the new string or, if no chars to escape, the same str
*/

static char *
escape_string(str)
char	*str;
{
	unsigned short	n_esc = 0;
	char	*n_str,
		*tmp;

	if(!str)
		return str;

	for(tmp = str; *tmp; ++tmp)
		if(*tmp == '"' || *tmp == '\\')
			++n_esc;
/***
	if(!n_esc)
		return str;
 *** No longer do the above so that can always safely free returned string */

	n_str = (char *)Memory_allocate(strlen(str) + (int)n_esc + 1);
	for(tmp = n_str, n_esc = 0; *str; tmp++, str++, n_esc++)
	{	if(*str == '"' || *str == '\\')
			*tmp++ = '\\';
		*tmp = *str;
	}
	*tmp = '\0';
	return n_str;
}
	

static NORET
print_member(member, f, level)
AlRuleMember	member;
FILE		*f;
unsigned	level;
{
	AlRuleMember	element;
	AlRuleAssoc	assoc;
	AlRuleField	field;
	AlFieldCore	fc;
	char		*to_free = (char *)0;

	int	n_args = 0;

	if(AlRuleMember_get_type(member) == ALRMT_ASSOC)
	{	assoc = AlRuleMember_get_assoc(member);
		for(element = AlRuleAssoc_get_first_member(assoc); element; 
		    n_args++, element = AlRuleMember_get_sibling(element))
			print_member(element, f, level+1);
		fprintf(f, "%*c", level+1, ' ');
		fprintf(f, "%%%s %d\n", AlRuleAssoc_atype(AlRuleAssoc_get_type(assoc)), n_args);
	}
	else
	{	field = AlRuleMember_get_field(member);
		fc = AlRuleField_get_fc(field);
		fprintf(f, "%*c", level+1, ' ');
		fprintf(f, "\"%s\" \"%s\" \"%s\" \"%s\" %%%d\n",
			AlFieldCore_get_key(fc),
			AlRuleField_get_op_name(field), 
			to_free = escape_string(AlRuleField_get_strvalue(field)),
			AlFieldCore_get_field_type_name(fc),
			(int)AlRuleField_get_type(field));
	}
	if(to_free)
		Memory_free(to_free);
}

static NORET
print_actions(actions, f)
Darray	actions;
FILE	*f;
{
	unsigned	c_acts = 0,
			c_args = 0,
			len_acts,
			len_args;
	AlAction	act;
	Darray		args;
	char		*to_free = (char *)NULL;

	for(len_acts = Darray_len(actions); c_acts < len_acts; c_acts++)
	{	act = (AlAction)Darray_get(actions, c_acts);
		args = AlAction_args(act);
		c_args = 0;
		fputs(" ", f);
		for(len_args = Darray_len(args); c_args < len_args; c_args++)
			fprintf(f, "\"%s\" ", to_free = escape_string((char *)Darray_get(args, c_args)));
		if(to_free)
		{	Memory_free(to_free);
			to_free = (char *)NULL;
		}
		fprintf(f, "%%\"%s\" %d\n", AlVerbReg_get_name(AlAction_verb(act)), len_args);
	}
}

/* Actual stuff */

Bool
AlRSreg_init()
{
	struct stat	sbuf;
	
	if(stat(RSdir = AlProfile_get_CP_val(ALPROF_RULEDIR), &sbuf) == -1)
	{	assert(errno != EFAULT);
		if(errno != ENOENT)
		{	
			Al_warning2("AlRSreg_init(): %s: %s; cannot initialize\n",
				RSdir, sys_errlist[errno]);
			return Bool_FALSE;
		}
		Al_warning1("AlRSreg_init(): ruleset directory %s does not exist, creating it.", RSdir);
		if(mkdir(RSdir, 0777) == -1)
		{       Al_warning2("AlRSreg_init(): %s: %s; cannot initialize\n",
				RSdir, sys_errlist[errno]);
			return Bool_FALSE;
		}
	}
	AlRSR.reg = Registry_create(Registry_strcmp, Registry_strhash);
	AlRSR.delete = Darray_create();
	AlRSR.initialized = Bool_TRUE;
	return Bool_TRUE;
}

Bool
AlRSreg_register(rs)
AlRuleSet	rs;
{
	VOIDP	int_name;

	assert(rs);
	assert(AlRSR.initialized == Bool_TRUE);
	int_name = (VOIDP)AlRuleSet_get_internal_name(rs);
	if(!Registry_add(AlRSR.reg, int_name, (VOIDP)rs))
	{	Registry_remove(AlRSR.reg, int_name);
		if(!Registry_add(AlRSR.reg, int_name, (VOIDP)rs))
			/* first set some error somewhere (?)*/
			return Bool_FALSE;
	}
	return Bool_TRUE;
}

#define RSFV	1	/* format version */

Bool
AlRSreg_write_to_file(rs, fn)
AlRuleSet rs;
const char *fn;
{
	FILE		*rsf;
	AlRule		r;
	Darray		rules;
	unsigned	i = 0,
			len;

	rules = Darray_create();
	AlRuleSet_get_rules(rs, rules);
	
	if(!(rsf = fopen(fn, "w")))
		return Bool_FALSE;

	fprintf(rsf, "$format_version %d\n$desc %s\n$created %ld\n$updated %ld\n\n", 
		RSFV, 
		AlRuleSet_get_desc(rs),
		AlRuleSet_get_create(rs), 
		AlRuleSet_get_update(rs));
	for(len = Darray_len(rules); i < len; ++i)
	{	r = (AlRule)Darray_get(rules, i);
		fprintf(rsf, "$created %ld\n$updated %ld\n$name %s\n", 
			AlRule_get_creation(r), 
			AlRule_get_update(r), 
			AlRule_get_name(r));
		fputs("$if\n", rsf);
		print_member(AlRule_get_predicate(r), rsf, (unsigned)0);
		fputs("$then\n", rsf);
		print_actions(AlRule_get_actions(r), rsf);
		fputs("$end\n\n", rsf);
		AlRule_mark_unchanged(r);
	}
	fclose(rsf);
	AlRuleSet_mark_unmod(rs);

	return Bool_TRUE;
}

Bool
AlRSreg_save(fn)
char	*fn;
{
	AlRuleSet	rs;
	char		wd[MAXPATHLEN+1];

	assert(AlRSR.initialized == Bool_TRUE);

	if((rs = (AlRuleSet)Registry_get(AlRSR.reg, (VOIDP)fn)) == 
	   (AlRuleSet)NULL)
		/* !!! set error */
		return Bool_FALSE;

	OSinter_get_cwd(wd);
	OSinter_set_cwd(RSdir);
	if(AlRSreg_write_to_file(rs, fn) == Bool_FALSE)
	{	OSinter_set_cwd(wd);
		if(AlRSreg_write_to_file(rs, fn) == Bool_FALSE)
			/* !! set error */
			return Bool_FALSE;
	}
	OSinter_set_cwd(wd);
	return Bool_TRUE;
}

static AlRAType 
otype(name)
char	*name;
{
	switch(*name) {
	case 'a':
		return ALRAT_AND;
	case 'o':
		return ALRAT_OR;
	case 'n':
		return ALRAT_NOT;
	}
}

#define push(ptr)	(Darray_addh(stack, ptr))
#define pop()		(Darray_remh(stack))
#define popl()		(Darray_reml(stack))

#define STRSIZE		128

AlRuleSet
AlRSreg_read_from_file(fn)
const char *fn;
{
	FILE		*rsf;
	Darray		stack,
			args,
			rules,
			yucko; /* don't  ask */
	AlRuleSet	rs;
	AlRuleMember	rm;
	AlRuleAssoc	assoc;
	time_t		c_t, u_t,
			rc_t, ru_t;
	char		buf[STRSIZE], rbuf[STRSIZE], gbuf[STRSIZE], *abuf,
			key[STRSIZE], op[STRSIZE], value[STRSIZE], 
			type[STRSIZE], c;
	int		n;
	AlRFType	code;
	AlVerb		temp_verb;

	assert(fn);

	if(!(rsf = fopen(fn, "r")))
		return (AlRuleSet)NULL;
	
	fscanf(rsf, " $format_version %d", &n);
	if(n != RSFV)
		/* !! set error */
		return (AlRuleSet)NULL;
	fscanf(rsf, " $desc");
	if((c = getc(rsf)) == '\n')	/* because optional value */
		*buf = '\0';
	else
		fscanf(rsf, "%[^\n]", buf);
	fscanf(rsf, " $created %ld $updated %ld", &c_t, &u_t);
	rules = Darray_create();
	while(fscanf(rsf, " $created %ld $updated %ld $name",
			&rc_t, &ru_t) != EOF)
	{
		if((c = getc(rsf)) == '\n')	/* because optional value */
			*rbuf = '\0';		/* yes, it's a last minute*/
		else				/* kludge */
			fscanf(rsf, "%[^\n]", rbuf);
		fscanf(rsf, " $if");
		stack = Darray_create();
		fscanf(rsf, "%*[ \n]");
		while((c = getc(rsf)) != '$')
		{ 	ungetc(c, rsf);
			key[0] = op[0] = value[0] = type[0] = '\0';
			while(((c = getc(rsf)) != '%') && (c != '$'))
			{
				AlRuleMember RM;
				ungetc(c, rsf);
				fscanf(rsf, "\"%[^\"]\" \"%[^\"]\" \"", key, 
				       op);
				while(fscanf(rsf, "%[^\\\"]", 
					     &value[strlen(value)]) != EOF)
				{	if(getc(rsf) == '"')
						break;
					c = getc(rsf);
					strncat(value, (char *)&c, 1);
				}
				fscanf(rsf, " \"%[^\"]\"", type);
				fscanf(rsf, " %%%d", &code);
				RM=AlRuleMember_mcreate_field(code,
							      key, 
							      type,
							      value,
							      op);
				if (RM==NULL) {
					RM =
					AlRuleMember_mcreate_field_no_parse(
									    code, 
									    key, 
									    type, 
									    value, 
									    op);
			  
					if (RM==NULL)
					Al_fatal_error("RSreg_load: could not create member as a field.\n");
					}
				push((VOIDP)RM);
				key[0] = op[0] = value[0] = type[0] = '\0';
				fscanf(rsf, "%*[ \n]");
			}
			if(c == '$')	/* This is so that one can have an */
				break;	/* $if with no assocs, only a field */
			/* association time */
			fscanf(rsf, "%s %d", op, &n);
			assoc = AlRuleMember_get_assoc(
				rm = AlRuleMember_mcreate_assoc(otype(op)));
			yucko = Darray_create(); /* this sucks */
			while(n--)
				Darray_addh(yucko, pop());
			for(n = Darray_len(yucko); --n >= 0; 
				AlRuleAssoc_make_member_of(assoc, 
				 (AlRuleMember)Darray_get(yucko, (unsigned)n)))
				;
			Darray_destroy(yucko); /* yeh! */
			push((VOIDP)rm);
			op[0] = '\0';
			fscanf(rsf, "%*[ \n]");
		}
		/* from 'if' to 'then' */
		fscanf(rsf, "then %*[ ]");
		gbuf[0] = '\0';
		while((c = getc(rsf)) != '$')
		{	ungetc(c, rsf);
			while((c = getc(rsf)) != '%')
			{	ungetc(c, rsf);
				fscanf(rsf, "\"");
/* read escapes */		while(fscanf(rsf, "%[^\\\"]", 
					     &gbuf[strlen(gbuf)]) != EOF)
				{	if(getc(rsf) == '"')
						break;
					c = getc(rsf);
					strncat(gbuf, (char *)&c, 1);
				}
				abuf = (char *)Memory_allocate(strlen(gbuf) + 1);
				push((VOIDP)strcpy(abuf,gbuf));
				gbuf[0] = '\0';
				fscanf(rsf, "%*[ \n]");
			}
			fscanf(rsf, "\"%[^\"]\" %d", op, &n);
			args = Darray_create();
			Darray_hint(args, (unsigned)n, (unsigned)0);
			while(n--)
				Darray_addl(args, pop());
			temp_verb = AlVerbReg_get_obj(op);
			if (temp_verb == NULL)
			  Al_fatal_error1("Unknown verb %s in ruleset", op);
			push(AlAction_create(temp_verb, args));
			op[0] = '\0';
			fscanf(rsf, "%*[ \n]");
		}
		fscanf(rsf,"end%*[ \n]");
		rm = (AlRuleMember)popl();
		
		Darray_addh(rules, AlRule_create(rm, stack, rc_t, ru_t, rbuf));
	}
	fclose(rsf);
	rs = AlRuleSet_create(buf, fn, c_t, u_t, rules);
	Darray_destroy(rules);
	return rs;
}

Bool
AlRSreg_load(fn)
char	*fn;
{
	AlRuleSet	rs;
	char wd[MAXPATHLEN+1];

	assert(AlRSR.initialized == Bool_TRUE);

/* The following may fail if get_cwd() fails.  This can occur if a superior
 * directory is read-protected, or if the current directory is deleted.
 * It should be rewritten to not use set_cwd and just prepend the RSdir to the
 * fn before trying the fopen() */

	if (OSinter_get_cwd(wd) == Bool_FALSE)
	  Al_fatal_error("Error when trying to get current directory (RSreg)");

	OSinter_set_cwd(RSdir);
	if((rs = AlRSreg_read_from_file(fn)) == (AlRuleSet)NULL)
	{	OSinter_set_cwd(wd);
		if((rs = AlRSreg_read_from_file(fn)) == (AlRuleSet)NULL)
			/* !! set error */
			return Bool_FALSE;
	}
	OSinter_set_cwd(wd);

	return AlRSreg_register(rs);

}

AlRuleSet
AlRSreg_find(name)
char	*name;
{
	AlRuleSet	rs;

	assert(name);
	assert(AlRSR.initialized == Bool_TRUE);
	if((rs = (AlRuleSet)Registry_get(AlRSR.reg, (VOIDP)name)) != (AlRuleSet)NULL)
		return rs;
	AlRSreg_load(name);
	return (AlRuleSet)Registry_get(AlRSR.reg, (VOIDP)name);
}

Bool
AlRSreg_remove(internal)
char	*internal;
{
	AlRuleSet	rs;
	char		*str;

	assert(AlRSR.initialized == Bool_TRUE);
	assert(internal);
	if((rs = (AlRuleSet)Registry_get(AlRSR.reg, (CONSTVOIDP)internal)) ==
								(AlRuleSet)NULL)
		/* set some code */
		return Bool_FALSE;
	if(AlRuleSet_deletable_on_diskp(rs) == Bool_TRUE)
	{	str = (char *)Memory_allocate(strlen(internal) + 1);
		Darray_addh(AlRSR.delete, strcpy(str, internal));
	}
	Registry_remove(AlRSR.reg, (CONSTVOIDP)internal);
	AlRuleSet_destroy(rs);
	return Bool_TRUE;
}

static NORET 
save_proc(key, value, a2)
VOIDP	key,
	value,
	a2;
{
	AlRuleSet	rs;
	rs = (AlRuleSet)Registry_get(AlRSR.reg, key);
	if(AlRuleSet_modp(rs) == Bool_FALSE)
		return;
	AlRSreg_save((char *)key);
	AlRuleSet_mark_unmod(rs);
}


unsigned int
AlRSreg_entry_count()
{
	return Registry_entry_count(AlRSR.reg);
}

NORET
AlRSreg_fetch_rs_objs(results)
Darray	results;
{
	Registry_fetch_contents(AlRSR.reg, NULL, results);
}

NORET
AlRSreg_saveall()
{
	unsigned int	len;

	assert(AlRSR.initialized == Bool_TRUE);

	len = Darray_len(AlRSR.delete);	
	while(len--)
		OSinter_remove((const char *)Darray_reml(AlRSR.delete));
	Registry_traverse(AlRSR.reg, (Registry_ActionProc)save_proc, 
			  (VOIDP)NULL);
}


	
