#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#include "memory.h"
#include "assert.h"
#include "FieldCore.h"
#include "FieldType.h"
#include "verbrgy.h"
#include "action.h"
#include "altime.h"
#include "registry.h"
#include "darray.h"
#include "al.h"
#include "profile.h"

#include "data.h"
#include "dataP.h"
#include "rule.h"
#include "ruleset.h"
#include "RSreg.h"

Bool AlDCinitialized = Bool_FALSE;
Bool Conversion = Bool_FALSE;	/* format conversion in effect if true */
extern char	*sys_errlist[];
const char	*DSf,
		*Dsnap,
		*Dra,
		*Drl,
		*Dmsg;


/**** \/\//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//\//\/\/\/\/\/\/ *****/
/**** The following few are definitions of Registry_ActionProc's  *****/

/**** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *****/

#define DCNO_DIRECTION 1	/* THM 6/8/89 Currently we do not 
				   differentiate between directons of msgs
				*/

static NORET 
Al_we_mt_rec(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	char	*mt_name = (char *)a0;
	ActHist	*hist = (ActHist *)a1;
	FILE	*fp = (FILE *)a2;
#ifdef	DCNO_DIRECTION
	int	fc = 0, 
		tc = 0;
#endif
	AlTimeCode	i;

	for(i = 0; i < ALTIME_NUM_TIMES; i++)

#ifndef DCNO_DIRECTION
	{	fprintf(fp, "\t\tFired %d out of %d times on %s messages\n",
			hist[i]->fire_count,
			hist[i]->test_count,
			AlTime_get_name_from_code(i));
		Memory_free((VOIDP)hist[i]);
	}
#else

	{	fc += hist[i]->fire_count;
		tc += hist[i]->test_count;
		Memory_free((VOIDP)hist[i]);
	}
	fprintf(fp, "\t\tFired %d out of %d times\n", fc, tc);
#endif
	Memory_free((VOIDP)hist);
	fprintf(fp, "\t\ton Message Type %s\n", mt_name);
}

static NORET
Al_we_msg_act(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	char	*mt_name = (char *)a0;
	char	*directions[ALTIME_NUM_TIMES];
	ActHist	*hist = (ActHist *)a1;
	FILE	*fp = (FILE *)a2;
	AlTimeCode	i;

	directions[0] = "shuffled or unspecified";
	directions[1] = "received";
	directions[2] =	"sent";

	fprintf(fp, "Number of messages of type %s\n", mt_name);
	for(i = 0; i < ALTIME_NUM_TIMES; i++)
	{	fprintf(fp, "\t%s (%s): %d\n", directions[i], 
			AlTime_get_name_from_code(i), hist[i]->test_count);
		Memory_free((VOIDP)hist[i]);
	}
	Memory_free((VOIDP)hist);
}

static NORET 
Al_we_rs_rec(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	char		*rs_name = (char *)a0;
	Registry	mt_rec = (Registry)a1;
	FILE		*fp = (FILE *)a2;

	fprintf(fp, "\tIn RuleSet %s\n", rs_name);
	Registry_traverse(mt_rec, (Registry_ActionProc)Al_we_mt_rec, a2);
	Registry_destroy(mt_rec);
}

static NORET
Al_we_rule_act(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	time_t		t_id = (time_t)a0;
	Registry	rs_rec = (Registry)a1;
	FILE		*fp = (FILE *)a2;

	fprintf(fp, "Rule %s", ctime(&t_id));
	Registry_traverse(rs_rec, (Registry_ActionProc)Al_we_rs_rec, a2);
	Registry_destroy(rs_rec);
}

/**** \/\//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//\//\/\/\/\/\/\/ *****/
/**** Definitions of auxiliary functions used only in this module *****/
/**** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *****/

static char *
pname(index)
EntCode	index;
{
	assert(index < PLUGS && index >= 0);
	switch(index){
	case R_LIFE:
		return "RULE-LIFE";
	case R_ACT:
		return "RULE-ACTV";
	case MSG:
		return "MSG";
	}
}

static NORET
get_msg_act(fp, num)
FILE	*fp;
int	num;
{
	char		buf[128],
			*str;
	int		i;
	ActHist		*hist;

	Registry_size_hint(msg_act, (unsigned)num);
	while(num--)
	{	fscanf(fp, " %s", buf);
		str = (char *)Memory_allocate(strlen(buf) + 1);
		hist = (ActHist *)Memory_allocate(ALTIME_NUM_TIMES *
						       sizeof(ActHist));
		for(i = 0; i < ALTIME_NUM_TIMES; i++)
		{	hist[i] = (ActHist)Memory_allocate(sizeof(ActHistSt));
			fscanf(fp, " %d %d", &hist[i]->test_count, 
			       &hist[i]->fire_count);
		}
		Registry_add(msg_act, (VOIDP)strcpy(str, buf), (VOIDP)hist);
	}
}

static Registry
get_mt_rec(fp, num)
FILE	*fp;
int	num;
{
	Registry	mt_rec;
	char		buf[128],
			*str;
	int		i;
	ActHist		*hist;

	mt_rec = Registry_create(Registry_strcmp, Registry_strhash);
	Registry_size_hint(mt_rec, (unsigned)num);
	while(num--)
	{	fscanf(fp, " %s", buf);
		str = (char *)Memory_allocate(strlen(buf) + 1);
		hist = (ActHist *)Memory_allocate(ALTIME_NUM_TIMES *
						 sizeof(ActHist));
		for(i = 0; i < ALTIME_NUM_TIMES; i++)
		{	hist[i] = (ActHist)Memory_allocate(sizeof(ActHistSt));
			fscanf(fp, " %d %d", &hist[i]->test_count,
					     &hist[i]->fire_count);
		}
		Registry_add(mt_rec, (VOIDP)strcpy(str, buf), (VOIDP)hist);
	}
	return mt_rec;
}

	
static Registry
get_rs_rec(fp, num)
FILE	*fp;
int	num;
{
	Registry	rs_rec;
	char		buf[128],
			*str;
	int		n;

	rs_rec =  Registry_create(Registry_strcmp, Registry_strhash);
	Registry_size_hint(rs_rec, (unsigned)num);
	while(num--)
	{	fscanf(fp, " %s #%d", buf, &n);
		str = (char *)Memory_allocate(strlen(buf) + 1);
		Registry_add(rs_rec, (VOIDP)strcpy(str, buf), 
				(VOIDP)get_mt_rec(fp, n));
	}
	return rs_rec;
}

static Bool
init_from_without(fp)
FILE	*fp;
{
	AlDCinfo	plug;
	char		str[128];
	time_t		t_id;
	int		id,
			num = 0,
			num_el = 0,
			rec_type = -1;

	fscanf(fp, "format_version %d", &id);

/***************
 The following switch  should only exist when one means to convert
 from an old format version to the current one.  Otherwise the code
 should return Bool_FALSE on any id other than DCF_VERSION
***************/

	switch(id) {
	case DCF_VERSION:
		break;
	case CNV_FR_DCFV:
		Conversion = Bool_TRUE;
		break;
	default:
		/* set error code */
		return Bool_FALSE;
	}
	for(id = (EntCode)0; id < PLUGS; ++id)
	{	plug = &AlDC_plugs[id];
		sprintf(str, " **%s:\n%%d\n%%d\n%%ld\n%%ld\n%%d\n", 
			pname(id));
		/* the %[^\n] is because that string is optional */
		plug->file = (char *)Memory_allocate((size_t)128);
		fscanf(fp, str, 
			&(int)plug->enabled, &(int)plug->in_time,	
			&plug->start, &plug->stop, &plug->data);

	}
	if(Conversion == Bool_TRUE)	/* read a format version *5* file */
	{	while(fscanf(fp, " %ld #%d", &t_id, &num) != EOF)
			Registry_add(Rule_act, (VOIDP)t_id, 
				     (VOIDP)get_rs_rec(fp, num));
		return Bool_TRUE;
	}
	while(fscanf(fp, " %d: %d", &rec_type, &num_el) != EOF)
		switch(rec_type) {
		case 0:	/** Read in rule_activity record **/
			Registry_size_hint(Rule_act, (unsigned)num_el);
			while(num_el--)
			{	fscanf(fp, " %ld #%d", &t_id, &num);
					Registry_add(Rule_act, 
						     (VOIDP)t_id, 
						     (VOIDP)get_rs_rec(fp, 
								       num));
			}
			break;
		case 1: /** Read in msg_activity record **/
			get_msg_act(fp, num_el);
			break;
		default:
			/** shouldn't get here **/
			assert(0);
		}
	return Bool_TRUE;
}

static time_t
past(t)
time_t	t;
{	
	time_t	now;

	if(!t)
		/* what should  be done? */
		return (time_t)0;
	return (time(&now) < t) ? (time_t)0 : now;
}

static time_t
between(t0, t1)
time_t	t0,
	t1;
{
	time_t	now;

	return (!past(t0)) ? (time_t)0 : (past(t1) ? (time_t)0 : time(&now));
}

static Bool
enabledp(ind)
EntCode	ind;
{
	return AlDC_plugs[ind].enabled;
}

static Bool
in_timep(ind)
EntCode	ind;
{
	AlDCinfo	plug;
	time_t		now;
	char		t_str[26];

	assert(AlDCinitialized);
	assert(ind < PLUGS);
	
	plug = &AlDC_plugs[ind];
	if(plug->in_time == Bool_TRUE)
	{	if(now = past(plug->stop))
		{	plug->in_time = Bool_FALSE;
			/* The following stuff is because ctime uses the same
			 * location to print stuff
			 */
			my_ctime(plug->stop);
			if(plug->fp = fopen(plug->file, "a"))
			{	fprintf(plug->fp, 
				"Shutting off logging: was set to end @ %s and it is now %s", 
				 t_str, ctime(&now));
				fclose(plug->fp);
			}
			return Bool_FALSE;
		}
		return Bool_TRUE;
	}
	if(now = between(plug->start, plug->stop))
	{	plug->in_time = Bool_TRUE;
		/*should error check  !!!!!*/
		if(plug->fp = fopen(plug->file, "a"))
		{
		/* The following stuff is because ctime uses the same
		 * location to print stuff
		 */
			my_ctime(plug->start);
			fprintf(plug->fp, "Turning on logging: was set to start @ %s and it is now %s",
				t_str, ctime(&now));
			fclose(plug->fp);
		}
		return Bool_TRUE;
	}
	return Bool_FALSE;
}

static Bool
can_rec(ind)
EntCode ind;
{
	return (enabledp(ind) && in_timep(ind));
}

time_t
Al_general_col_int(start, col_for, id)
time_t	start;
char	*col_for;
EntCode	id;
{
	AlDCinfo	plug;
	time_t		inc;

	plug = &AlDC_plugs[id];
	plug->start = start;

	/* !! Will want to do this parsing a lot better */
	switch(*col_for) {
	case 'h':
	case 'H':
		plug->stop = plug->start + (inc = (time_t)3600);
		break;
	case 'm':
 	case 'M':
		plug->stop = plug->start + (inc = (time_t)3600*24*30);
		break;
	case 'd':
 	case 'D':
		plug->stop = plug->start + (inc = (time_t)(3600*24));
		break;
	case 'w':
	case 'W':
		plug->stop = plug->start + (inc = (time_t)(3600*24*7));
		break;
	case 's':
	case 'S':
		plug->stop = plug->start + (inc = (time_t)(3600*24*30*6));
		break;
	default:
		plug->stop = plug->start + (inc = (time_t)(3600*12));
	}
	plug->data = (int)inc;
	return inc;
}

static NORET
write_and_erase_msg_hist(fp)
FILE	*fp;
{
	Registry_traverse(msg_act, (Registry_ActionProc)Al_we_msg_act, (VOIDP)fp);
	Registry_destroy(msg_act);
	msg_act = Registry_create(Registry_ptrcmp, Registry_ptrhash);
}

static NORET
write_and_erase_ft_hist(fp)
FILE	*fp;
{
	Registry_traverse(Rule_act, (Registry_ActionProc)Al_we_rule_act, (VOIDP)fp);
	Registry_destroy(Rule_act);
	Rule_act = Registry_create(Registry_ptrcmp, Registry_ptrhash);
}

static NORET
write_and_erase_hist(fp, ind)
FILE	*fp;
EntCode	ind;
{
	switch(ind) {
	case R_ACT:
		write_and_erase_ft_hist(fp);
		break;
	case MSG:
		write_and_erase_msg_hist(fp);
	}
}

static Bool
renew_period(ind)
EntCode	ind;
{
	AlDCinfo	plug;
	FILE	*hfp;
	char	t_str[26];


	plug = &AlDC_plugs[ind];
	if(plug->start == plug->stop || !past(plug->start))
		return Bool_FALSE;
	if(!between(plug->start, plug->stop))
	{	do
		{	if(!(hfp = fopen(plug->file, "a")))
				hfp = fopen("/dev/null", "a");
				/* because still want some stuff done */
			my_ctime(plug->start);
			fprintf(hfp, "History between %s and %s", 
				t_str, ctime(&plug->stop));
			write_and_erase_hist(hfp, ind);
			fputc('\n', hfp);
			fclose(hfp);
			plug->start = plug->stop;
			plug->stop += (time_t)plug->data;
		} while(!between(plug->start, plug->stop)
			&& (time_t)plug->data != 0);
	}
	return Bool_TRUE;
}

static char *
get_msg_type_name(msg)
Msg	msg;
{
	AlFieldType	ft;
	char	*type_name,
		*tmp;

	assert(msg);

	if(AlProfile_has_value(ALPROF_DEF_FIELD_TYPE) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_DEF_FIELD_TYPE.");
	ft = AlFieldType_get_field_type(AlProfile_get_CP_val(ALPROF_DEF_FIELD_TYPE));
	if(AlMsg_number_of_type(msg, MT_INDICATOR_FIELD, ft))
	{	tmp = *(AlMsg_get_raw_fields(msg, MT_INDICATOR_FIELD));
		type_name = (char *)Memory_allocate(strlen(tmp));
		strcpy(type_name, tmp);
	}
	else
		type_name = "unknown";
	return type_name;
}

static NORET
msg_activity(type_name)
char	*type_name;
{
	ActHist	*hist;
	short	i;

	assert(type_name);

	if((hist = (ActHist *)Registry_get(msg_act, (VOIDP)type_name)) == NULL)
	{	hist = (ActHist *)Memory_allocate(ALTIME_NUM_TIMES *
						  sizeof(ActHist));
		for(i = 0; i < ALTIME_NUM_TIMES; i++)
		{	hist[i] = (ActHist)Memory_allocate(sizeof(ActHistSt));
			bzero((char *)hist[i], sizeof(ActHistSt));
		}
		Registry_add(msg_act, (VOIDP)type_name, (VOIDP)hist);
	}
	hist[AlTime_get_code()]->test_count++;
}

static NORET
rule_activity(rule, rs, type_name, t_f)
AlRule		rule;
AlRuleSet	rs;
char		*type_name;
int		t_f;
{
	time_t		time_id;
	Registry	rs_rec,
			mt_rec;
	ActHist		*hist;
	char		*rs_name;
	short		i;

	assert(rule);

	if(enabledp(R_ACT) == Bool_FALSE || renew_period(R_ACT) == Bool_FALSE)
		return;

	time_id = AlRule_get_update(rule);
	rs_name = (char *)Memory_allocate(strlen(AlRuleSet_get_internal_name(rs)) + 1);
	strcpy(rs_name, AlRuleSet_get_internal_name(rs));
	if((rs_rec = (Registry)Registry_get(Rule_act, (VOIDP)time_id)) == NULL)
	{	rs_rec = Registry_create(Registry_strcmp, Registry_strhash);
		Registry_add(Rule_act, (VOIDP)time_id, (VOIDP)rs_rec);
	}
	if((mt_rec = (Registry)Registry_get(rs_rec, (VOIDP)rs_name)) == NULL)
	{	mt_rec = Registry_create(Registry_strcmp, Registry_strhash);
		Registry_add(rs_rec, (VOIDP)rs_name, (VOIDP)mt_rec);
	}
	if((hist = (ActHist *)Registry_get(mt_rec, (VOIDP)type_name)) == NULL)
	{	hist = (ActHist *)Memory_allocate(ALTIME_NUM_TIMES *
						  sizeof(ActHist));
		for(i = 0; i < ALTIME_NUM_TIMES; i++)
		{	hist[i] = (ActHist)Memory_allocate(sizeof(ActHistSt));
			bzero((char *)hist[i], sizeof(ActHistSt));
		}
		Registry_add(mt_rec, (VOIDP)type_name, (VOIDP)hist);
	}
	if(t_f == 't')
		hist[AlTime_get_code()]->test_count++;
	else
		hist[AlTime_get_code()]->fire_count++;
}

static NORET
init_set_times_using_default_period()
{
	AlDCinfo	plug;
	EntCode		code;
	time_t		now;

	for(code = 0; code < PLUGS; code++)
	{	plug = &AlDC_plugs[code];
		plug->start = time(&now);
		plug->stop = plug->start + (time_t)plug->data;
	}
}

/**** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ *******/
/**** Beginning of definitions of functions accessible outside module *******/
/**** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *******/

Bool
AlData_init()
{
	FILE		*sf;
	time_t		now;
	struct stat	sbuf;
	const char	*dir;
	const char	*sfn;
	char		wbuf[128];

	if(AlDCinitialized == Bool_TRUE)
		return Bool_TRUE;
	time(&now);
	Rule_act = Registry_create(Registry_ptrcmp, Registry_ptrhash);
	msg_act = Registry_create(Registry_ptrcmp, Registry_ptrhash);
	
	if(AlProfile_has_value(ALPROF_DATADIR) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_DATADIR.");
	if(stat(dir = AlProfile_get_CP_val(ALPROF_DATADIR), &sbuf) == -1)
	{	assert(errno != EFAULT);
		if(errno != ENOENT)
		{	sprintf(wbuf, "AlData_init(): %s: %s; cannot initialize\n",
				dir, sys_errlist[errno]);
			Al_warning(wbuf);
			return Bool_FALSE;
		}
		sprintf(wbuf, "AlData_init(): ruleset directory %s does not exist, creating it.\n",
			dir);
		Al_warning(wbuf);
		if(mkdir(dir, 0777) == -1)
		{	sprintf(wbuf, "AlData_init(): %s: %s; cannot initialize\n",
				dir, sys_errlist[errno]);
			Al_warning(wbuf);
			return Bool_FALSE;
		}
	}
	if(AlProfile_has_value(ALPROF_DATASF) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_DATASF.");
	if(stat(DSf = sfn = AlProfile_get_CP_val(ALPROF_DATASF), &sbuf) == -1)
	{	assert(errno != EFAULT);
		if(errno != ENOENT)
		{	sprintf(wbuf, "AlData_init(): %s: %s; cannot initialize\n",
				sfn, sys_errlist[errno]);
			Al_warning(wbuf);
			return Bool_FALSE;
		}
		if(AlProfile_has_value(ALPROF_DATASSF) == Bool_FALSE)
			Al_fatal_error("DataCollection: cannot get profile component ALPROF_DATASSF.");
		sfn = AlProfile_get_CP_val(ALPROF_DATASSF);
	}
	if(sf = fopen(sfn, "r"))
	{	if(!init_from_without(sf))
			return Bool_FALSE;
		fclose(sf);
	}
	/* the following are for backwards complatability */
	if(AlProfile_has_value(ALPROF_MSGDOUT) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_MSGDOUT.");
	AlDC_plugs[MSG].file = (char *)(Dmsg = (char *)AlProfile_get_CP_val(ALPROF_MSGDOUT));
	if(AlProfile_has_value(ALPROF_RADOUT) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_RADOUT.");
	AlDC_plugs[R_ACT].file = (char *)(Dra = (char *)AlProfile_get_CP_val(ALPROF_RADOUT));
	if(AlProfile_has_value(ALPROF_RLDOUT) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_RLDOUT.");
	AlDC_plugs[R_LIFE].file = (char *)(Drl = (char *)AlProfile_get_CP_val(ALPROF_RLDOUT));
	/***/
	if(AlProfile_has_value(ALPROF_SNAPOUT) == Bool_FALSE)
		Al_fatal_error("DataCollection: cannot get profile component ALPROF_SNAPOUT.");
	Dsnap = AlProfile_get_CP_val(ALPROF_SNAPOUT);
	if(sfn != DSf)
	{	init_set_times_using_default_period();
		if(!AlData_save_state())
			return Bool_FALSE;
	}
	return AlDCinitialized = Bool_TRUE;
}

static NORET 
Al_pmt_act(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	char		*mt_name = (char *)a0;
	ActHist		*hist = (ActHist *)a1;
	FILE		*fp = (FILE *)a2;
	int		i;

	fputs(mt_name, fp);
	for(i = 0; i < ALTIME_NUM_TIMES; i++)
		fprintf(fp, " %d %d", hist[i]->test_count, hist[i]->fire_count);
	fputs("\n", fp);
}

static NORET
Al_prs_act(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	char		*rs_name = (char *)a0;
	Registry	mt_rec = (Registry)a1;
	FILE		*fp = (FILE *)a2;

	fprintf(fp, "%s #%d\n", rs_name, Registry_entry_count(mt_rec));
	Registry_traverse(mt_rec, (Registry_ActionProc)Al_pmt_act, a2);
}

static NORET
Al_pr_act(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	time_t		t_id = (time_t)a0;
	Registry	rs_rec = (Registry)a1;
	FILE		*fp = (FILE *)a2;

	fprintf(fp, "%ld #%d\n", t_id, Registry_entry_count(rs_rec));
	Registry_traverse(rs_rec, (Registry_ActionProc)Al_prs_act, a2);
}

Bool
AlData_save_state()
{
	AlDCinfo	plug;
	FILE	*fp;
	EntCode	id;

	if(!(fp = fopen(DSf, "w")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(fp, "format_version %d\n", DCF_VERSION);
	for(id = 0; id < PLUGS; ++id)
	{	plug = &AlDC_plugs[id];
		fprintf(fp, "**%s:\n%d\n%d\n%ld\n%ld\n%d\n", 
			pname(id), (int)plug->enabled, (int)plug->in_time,
			plug->start, plug->stop, plug->data);
	}
	fputc('\n', fp);
	if(Registry_entry_count(Rule_act))
	{	fprintf(fp, "0: %d\n", Registry_entry_count(Rule_act));
		Registry_traverse(Rule_act, (Registry_ActionProc)Al_pr_act, 
				  (VOIDP)fp);
	}
	if(Registry_entry_count(msg_act))
	{	fprintf(fp, "1: %d\n", Registry_entry_count(msg_act));
		Registry_traverse(msg_act, (Registry_ActionProc)Al_pmt_act,
				  (VOIDP)fp);
	}
	fclose(fp);
	return Bool_TRUE;
}

Al_pp_member(member, f, level)
AlRuleMember	member;
FILE		*f;
unsigned int	level;
{
	AlRuleMember	element;
	AlRuleAssoc	assoc;
	AlRuleField	field;

	if(AlRuleMember_get_type(member) == ALRMT_ASSOC)
	{	assoc = AlRuleMember_get_assoc(member);
		fprintf(f, "(%s ", AlRuleAssoc_atype(AlRuleAssoc_get_type(assoc)));
		for(element = AlRuleAssoc_get_first_member(assoc); element;
		    element = AlRuleMember_get_sibling(element))
			Al_pp_member(element, f, level+1);
		fputs(") ", f);
	}
	else
	{	field = AlRuleMember_get_field(member);
		fprintf(f, "[%s %s %s] ", AlFieldCore_get_key(AlRuleField_get_fc(field)),
					 AlRuleField_get_op_name(field),
					 AlRuleField_get_strvalue(field));
	}
}
		
Al_pp_actions(actions, f)
Darray	actions;
FILE	*f;
{
	unsigned	c_acts = 0,
			c_args = 0,
			len_acts,
			len_args;
	AlAction	act;
	Darray		args;

	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;
		fprintf(f, "(%s", AlVerbReg_get_name(AlAction_verb(act)));
		for(len_args = Darray_len(args); c_args < len_args; c_args++)
			fprintf(f, " %s", (char *)Darray_get(args, c_args));
		fputs(") ", f);
	}
	fputs("\n", f);
}

Bool
AlData_rule_created(rule)
AlRule	rule;
{
	AlDCinfo	plug;
	time_t		t;

	assert(AlDCinitialized);
	assert(rule);

	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	t = AlRule_get_creation(rule);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "Rule Created: (%s) ID: %sIf: ", AlRule_get_name(rule),
							   ctime(&t));
	Al_pp_member(AlRule_get_predicate(rule), plug->fp, (unsigned)0);
	fprintf(plug->fp, "\nThen: ");
	Al_pp_actions(AlRule_get_actions(rule), plug->fp);
	fputs("\n", plug->fp);
	fclose(plug->fp);
	return Bool_TRUE;
}

Bool
AlData_rule_changed(rule, prev_ut)
AlRule	rule;
time_t	prev_ut;
{
	AlDCinfo	plug;
	char		t_str[26];
	time_t		t;

	assert(AlDCinitialized);
	assert(rule);
	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	my_ctime(prev_ut);
	t = AlRule_get_update(rule);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "Rule changed: (%s) (was ID: %s) -> ID: %s\n", 
			   AlRule_get_name(rule),
			   t_str, ctime(&t));
	fclose(plug->fp);
	return Bool_TRUE;
}

Bool
AlData_rule_deleted(rule)
AlRule	rule;
{
	AlDCinfo	plug;
	time_t	t;

	assert(AlDCinitialized);
	assert(rule);
	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	t = AlRule_get_update(rule);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "Rule Deleted: (%s) ID: %s\n", AlRule_get_name(rule),
						   ctime(&t));
	fclose(plug->fp);
	return Bool_TRUE;
}

Bool
AlData_ruleset_created(rs)
AlRuleSet	rs;
{
	AlDCinfo	plug;
	time_t		t;
	Darray		rules;
	AlRule		rule;
	unsigned	len,
			c = 0;

	assert(AlDCinitialized);
	assert(rs);
	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	t = AlRuleSet_get_create(rs);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "RuleSet created: \"%s\": %s", 
				AlRuleSet_get_internal_name(rs), ctime(&t));
	rules = Darray_create();
	AlRuleSet_get_rules(rs, rules);
	for(len = Darray_len(rules); c < len; c++)
	{	t = AlRule_get_update(rule = (AlRule)Darray_get(rules, c));
		fprintf(plug->fp, "\t(%s) %s", AlRule_get_name(rule), ctime(&t));
	}
	Darray_destroy(rules);
	fputs("\n", plug->fp);
	fclose(plug->fp);
	return Bool_TRUE;
}

Bool
AlData_ruleset_changed(rs, dlt, rule, prev_ut)
AlRuleSet	rs;
int		dlt;
AlRule		rule;
time_t		prev_ut;
{
	AlDCinfo	plug;
	char		t_str[26];
	time_t		t,
			r_ut;

	assert(AlDCinitialized);
	assert(rule);
	assert(rs);

	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	my_ctime(prev_ut);
	t = AlRuleSet_get_update(rs);
	r_ut = AlRule_get_update(rule);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "RuleSet modified: \"%s\": (was %s) %s",
		AlRuleSet_get_internal_name(rs), t_str, ctime(&t));
	fprintf(plug->fp, "\tRule %s (%s) %s\n", (dlt == '+') ? "added" : "deleted",
					       AlRule_get_name(rule),
					       ctime(&r_ut));
	fclose(plug->fp);
	return Bool_TRUE;
}

Bool
AlData_ruleset_deleted(rs)
AlRuleSet	rs;
{
	AlDCinfo	plug;
	time_t		t;

	assert(AlDCinitialized);
	assert(rs);
	if(can_rec(R_LIFE) == Bool_FALSE)
		return Bool_TRUE;
	plug = &AlDC_plugs[R_LIFE];
	t = AlRuleSet_get_update(rs);
	if(!(plug->fp = fopen(plug->file, "a")))
		/* set errcode */
		return Bool_FALSE;
	fprintf(plug->fp, "RuleSet deleted:\n\t\"%s\": %s\n",
		AlRuleSet_get_internal_name(rs), ctime(&t));
	fclose(plug->fp);
	return Bool_TRUE;
}

NORET
AlData_general_disable(id)
EntCode	id;
{
	AlDCinfo	plug;
	FILE	*fp;
	time_t	t;

	assert(AlDCinitialized);

	plug = &AlDC_plugs[id];
	plug->enabled = Bool_FALSE;
	if(fp = fopen(plug->file, "a"));
	{	time(&t);
		fprintf(fp, "Disabled recording at %s\n", ctime(&t));
		fclose(fp);
	}
/*
	plug->stop = plug->start = (time_t)0;
*/
}

NORET
AlData_general_enable(id)
EntCode	id;
{
	AlDCinfo	plug;
	FILE	*fp;
	time_t	t;

	assert(AlDCinitialized);
	plug = &AlDC_plugs[id];
	if(plug->enabled == Bool_TRUE)
		return;
	if(fp = fopen(plug->file, "a"));
	{	time(&t);
		fprintf(fp, "Enabling recording at %s\n", ctime(&t));
		fclose(fp);
	}
	plug->enabled = Bool_TRUE;
/*
	plug->stop = plug->start = (time_t)0;
*/
}

NORET
AlData_periodic_col_change(start, code)
time_t	start;
EntCode	code;
{
	AlDCinfo	plug;
	char		t_str[26];
	time_t		t;

	assert(AlDCinitialized);
	plug = &AlDC_plugs[code];
	if(plug->start && plug->stop && past(plug->start))
	{	if(!(plug->fp = fopen(plug->file, "a")))
			plug->fp = fopen("/dev/null", "a");
			/* because still want some of this proc'ing done */
		fputs("Resetting recording time;\n", plug->fp);
		my_ctime(plug->start);
		fprintf(plug->fp, "  History between %s ", t_str);
		time(&t);
		my_ctime(((past(plug->stop)!=0) ? plug->stop : t));
		fprintf(plug->fp, "and %s", t_str);
		if(!past(plug->stop))
		{	my_ctime(plug->stop);
			fprintf(plug->fp, "\n  (instead of %s):", t_str);
		}
		fputc('\n', plug->fp);
		write_and_erase_hist(plug->fp, code);
		fputc('\n', plug->fp);
		fclose(plug->fp);
	}
}

NORET
AlData_periodic_col_int(start, col_for, code)
time_t	start;
char	*col_for;
EntCode	code;
{
	AlData_periodic_col_change(start, code);
	Al_general_col_int(start, col_for, code);
}

Bool
AlData_set_option(opt)
char	*opt;
{
	char	buf[64];
	short	st = -1;

	sscanf(opt, "%31[^-]-%31s", buf, &buf[32]);
	if(!strcmp(buf, "all"))
	{	bcopy("ra-", &buf[29], 3);
		if(AlData_set_option(&buf[29]) != Bool_TRUE)
			return Bool_FALSE;
		bcopy("rl-", &buf[29], 3);
		if(AlData_set_option(&buf[29]) != Bool_TRUE)
			return Bool_FALSE;
		bcopy("msg-", &buf[28], 4);
		if(AlData_set_option(&buf[28]) != Bool_TRUE)
			return Bool_FALSE;
		return Bool_TRUE;
	}
	if(!strcmp(buf, "ra"))
		st = R_ACT;
	else if(!strcmp(buf, "rl"))
		st = R_LIFE;
	else if(!strcmp(buf, "msg"))
		st = MSG;
	else
		return Bool_FALSE;
	if(!strcmp(&buf[32], "off"))
		AlData_general_disable(st);
	else if(!strcmp(&buf[32], "on"))
		AlData_general_enable(st);
	else
		return Bool_FALSE;
	return Bool_TRUE;
}

Bool
AlData_ruleset_snapshot()
{
	FILE		*fp;
	time_t		t;
	unsigned int	num;
	Darray		rss;

	assert(AlDCinitialized);
	if(!(fp = fopen(Dsnap, "a")))
		return Bool_FALSE;
	time(&t);
	fprintf(fp, "\nSnapshot of RuleSets at %s\n", ctime(&t));
	rss = Darray_create();
	Darray_hint(rss, (unsigned)0, num = AlRSreg_entry_count());
	AlRSreg_fetch_rs_objs(rss);
	while(num--)
	{	AlRuleSet	rs;
		Darray		rules;
		unsigned int	rn;

		rs = (AlRuleSet)Darray_reml(rss);
		fprintf(fp, " Ruleset: %s (%s)\n", 
			AlRuleSet_get_internal_name(rs),
			AlRuleSet_get_desc(rs));
		rules = Darray_create();
		Darray_hint(rules, (unsigned)0, rn = AlRuleSet_number_of_rules(rs));
		AlRuleSet_get_rules(rs, rules);
		while(rn--)
		{	AlRule	rule;
			char	t_str[26];

			rule = (AlRule)Darray_reml(rules);
			t = AlRule_get_update(rule);
			my_ctime(t);
			fprintf(fp, "\trule: %s (%s)\n", 
				t_str, AlRule_get_name(rule));
		}
		Darray_destroy(rules);
	}
	Darray_destroy(rss);
	fclose(fp);
	return Bool_TRUE;
}

Al_rsnap_aux(a0, a1, a2)
VOIDP	a0,
	a1,
	a2;
{
	time_t	t_id = (time_t)a0;
	AlRule	rule = (AlRule)a1;
	FILE	*fp = (FILE *)a2;
	char	t_str[26];

	my_ctime(t_id);
	fprintf(fp, "Rule %s (%s):\n", t_str, AlRule_get_name(rule));
	fputs(" IF: ", fp);
	Al_pp_member(AlRule_get_predicate(rule), fp, (unsigned)0);
	fputs("\n THEN: ", fp);
	Al_pp_actions(AlRule_get_actions(rule), fp);
	fputs("\n\n", fp);
}

Bool
AlData_rule_snapshot()
{
	FILE		*fp;
	time_t		t;
	Darray		rss;
	unsigned int	num;
	Registry	rr;

	assert(AlDCinitialized);
	if(!(fp = fopen(Dsnap, "a")))
		return Bool_FALSE;
	time(&t);
	fprintf(fp, "\nSnapshot of Rules at %s\n", ctime(&t));
	rss = Darray_create();
	Darray_hint(rss, (unsigned)0, num = AlRSreg_entry_count());
	AlRSreg_fetch_rs_objs(rss);
	rr = Registry_create(Registry_ptrcmp, Registry_ptrhash);
	while(num--)
	{	Darray		rules;
		AlRuleSet	rs;
		unsigned int	rn;

		rs = (AlRuleSet)Darray_reml(rss);
		rules = Darray_create();
		Darray_hint(rules, (unsigned)0, rn = AlRuleSet_number_of_rules(rs));
		AlRuleSet_get_rules(rs, rules);
		while(rn--)
		{	AlRule	rule;

			rule = (AlRule)Darray_reml(rules);
			Registry_add(rr, (VOIDP)AlRule_get_update(rule), 
				     (VOIDP)rule);
		}
		Darray_destroy(rules);
	}
	Darray_destroy(rss);
	Registry_traverse(rr, (Registry_ActionProc)Al_rsnap_aux, (VOIDP)fp);
	fclose(fp);
	Registry_destroy(rr);
	return Bool_TRUE;
}

NORET
AlData_rule_tested(rule, rs, msg)
AlRule		rule;
AlRuleSet	rs;
Msg		msg;
{
	char	*type_name;

	type_name = get_msg_type_name(msg);
	rule_activity(rule, rs, type_name, 't');
}

NORET
AlData_rule_fired(rule, rs, msg)
AlRule		rule;
AlRuleSet	rs;
Msg		msg;
{
	char	*type_name;

	type_name = get_msg_type_name(msg);
	rule_activity(rule, rs, type_name, 'f');
}

NORET
AlData_ruleset_invoked(rs, msg)
AlRuleSet	rs;
Msg		msg;
{
	assert(rs);
	assert(msg);
}

Bool
AlData_msg_received(msg)
Msg	msg;
{
	char	*type_name;

	assert(AlDCinitialized == Bool_TRUE);

	if(enabledp(MSG) == Bool_FALSE || renew_period(MSG) == Bool_FALSE)
		return Bool_TRUE;

	type_name = get_msg_type_name(msg);
	msg_activity(type_name);
	return Bool_TRUE;
}
