/*\
 *	DISTRIBUTION: HNMS v2.0
 *	FILE: hnmslib/hnms.c
 *
 *	Set up initialization and periodic functions.
 *
 *	Jude George
 *	NAS Facility, NASA Ames Research Center
 *
 *	Copyright (c) 1994 Jude George
 *
 *	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 1, or (at your option)
 *	any later version.
 *
 *	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.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
\*/

#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/time.h>

#include "stdhnms.h"

static struct hnms_task {
    char			name[STRBUFLEN];
    int				(*init_func)();
    int				(*seq_func)();
    int				interval;
    int				running;
    unsigned int		timestamp;
    struct timeval		tv_total;
    struct timeval		tv_cycle;
    struct hnms_task		*next;
};
typedef struct hnms_task	*Task;

static struct hnms_microtask {
    int				(*func)();
    int				arg1;
    int				arg2;
    struct hnms_microtask	*next;
};
typedef struct hnms_microtask	*Microtask;

int				hnms_errno = HNMS_err_noerr;

static int			module_type = 0;
static Task			tasks = NULL;
static Microtask		microtasks = NULL;

/*\
 *  Set my module type and default module id.
\*/
static void HNMS_set_module_defaults()
{
    PARAM_set_int(oid_hnmsModuleType, module_type);
    if (module_type == MODULE_SERVER)
	PARAM_set_int(oid_hnmsModuleId, 0);
    else
	PARAM_set_int(oid_hnmsModuleId, -1);
}

/*\
 *  For debugging.  Display the amount of time taken by each task
 *  of HNMS.
\*/
static void HNMS_printstats()
{
    Task		t;

    printf("  Total Time (s)  This Cycle (s)\n");
    for (t = tasks; t; t = t->next)
	if (t->seq_func) {
	    if (t->running)
		printf("  %7d.%06d  %7d.%06d  %s\n", t->tv_total.tv_sec,
		       t->tv_total.tv_usec, t->tv_cycle.tv_sec,
		       t->tv_cycle.tv_usec, t->name);
	    else
		printf("  %7d.%06d                  %s\n", t->tv_total.tv_sec,
		       t->tv_total.tv_usec, t->name);
	}
}

/*\
 *  Execute microtasks on the microtask stack.  Take at most 50 at
 *  a time, LIFO.  We don't care what order they get executed in,
 *  as long as they get executed without burning up a lot of CPU.
\*/
static void HNMS_process_microtasks()
{
    int			n;
    Microtask		mt;

    n = 0;
    for (;;) {
	if (!microtasks || (n++ > 50))
	    break;
	mt = microtasks;
	mt->func(mt->arg1, mt->arg2);
	microtasks = mt->next;
	free(mt);
    }
    
}

/*\
 *  Place one microtask in the microtask stack.  The microtask may be
 *  a function that returns an integer and takes two integer arguments.
 *  It will be executed once and only once.
\*/
void HNMS_create_microtask(func, arg1, arg2)
    int			(*func)();
    int			arg1;
    int			arg2;
{
    Microtask		mt;

    mt = HNMS_alloc(1, sizeof *mt);
    mt->func = func;
    mt->arg1 = arg1;
    mt->arg2 = arg2;
    mt->next = microtasks;
    microtasks = mt;
}

/*\
 *  Insert a task at the _end_ of the task list.  If a task has the
 *  same initialization function as an existing task, do not enter
 *  the initialization function for the new task.
\*/
void HNMS_create_task(name, init_func, seq_func, interval)
    const char		*name;
    int			(*init_func)();
    int			(*seq_func)();
    const int		interval;
{
    Task		*t, i;
    int			(*init_func_aux)();

    init_func_aux = init_func;
    for (i = tasks; i; i = i->next)
	if (init_func == i->init_func) {
	    init_func_aux = NULL;
	    break;
	}

    for (t = &tasks; *t; t = &((*t)->next));
    *t = HNMS_alloc(1, sizeof **t);
    strcpy((*t)->name, name);
    (*t)->init_func = init_func_aux;
    (*t)->seq_func = seq_func;
    (*t)->interval = interval;
}

/*\
 *  Initialize tasks.  Don't change the order of initialization.
 *  Don't create any new tasks after HNMS_process() is called for
 *  the first time.
 *
 *  1.  Set up the oids that were compiled into the code.  These must
 *	include the oids for all parameters that are set at startup.
 *  2.  Read in vaules of "bootstrap" parameters (e.g. hnmsModuleHnmsHome,
 *	hnmsModuleHnmsCommunity).  Set default values for all other
 *	operational parameters.
 *  3.	Set my module type.
 *  4-6 Initialize HNMP.
\*/
void HNMS_init(mod_type)
    const int		mod_type;
{
    module_type = mod_type;

    HNMS_create_task("Init Built-In MIB", OID_init, NULL, 0);
    HNMS_create_task("Init Operational Params", PARAM_init, NULL, 0);
    HNMS_create_task("Set Module Type", HNMS_set_module_defaults, NULL, 0);
    if (getenv("HNMS_STATS"))
	HNMS_create_task("HNMS Task Info", NULL, HNMS_printstats, 1);
    HNMS_create_task("HNMS Microtasks", NULL, HNMS_process_microtasks, 1);
    HNMS_create_task("HNMP Maintain Sessions", HNMP_init, HNMP_sessions, 1);
    HNMS_create_task("HNMP Send Messages", HNMP_init, HNMP_send, 1);
    HNMS_create_task("HNMP Receive Messages", HNMP_init, HNMP_listen, 1);
    if (getenv("HNMP_STATS"))
	HNMS_create_task("HNMP Window Info", HNMP_init, HNMP_printstats, 1);
}

/*\
 *  Call this routine often (read: once per second) to take care of
 *  HNMS tasks.  All of the initialization routines are executed once,
 *  in sequence, before _any_ of the sequential routines are executed
 *  for the first time.
\*/
void HNMS_process()
{
    static int		initialized = 0;
    unsigned int	current_time;
    Task		t;
    struct timeval	tv_start, tv_stop;
    struct timezone	tz;

    if (!initialized) {
	initialized = 1;
	for (t = tasks; t; t = t->next)
	    if (t->init_func) {
		if (t->init_func() < 0)
		    HNMS_debug(DEBUG_ERRORS,
			       "%s: initialization error\n", t->name);
	    }
    }
    current_time = get_int_time();
    for (t = tasks; t; t = t->next) {
	t->running = 0;
	if (t->seq_func && (current_time - t->interval > t->timestamp)) {
	    t->timestamp = current_time;
	    t->running = 1;
	    gettimeofday(&tv_start, &tz);
	    if (t->seq_func() < 0)
		HNMS_debug(DEBUG_ERRORS, "%s: execution error\n", t->name);
	    gettimeofday(&tv_stop, &tz);
	    t->tv_cycle.tv_sec = tv_stop.tv_sec - tv_start.tv_sec;
	    t->tv_cycle.tv_usec = tv_stop.tv_usec - tv_start.tv_usec;
	    if (t->tv_cycle.tv_usec < 0) {
		t->tv_cycle.tv_usec += 1000000;
		t->tv_cycle.tv_sec--;
	    }
	    t->tv_total.tv_sec += t->tv_cycle.tv_sec;
	    t->tv_total.tv_usec += t->tv_cycle.tv_usec;
	    if (t->tv_total.tv_usec >= 1000000) {
		t->tv_total.tv_sec++;
		t->tv_total.tv_usec -= 1000000;
	    }
	}
    }
}

/*\
 *  For debugging.  Display an HNMS event to stdout.
\*/
void HNMS_debug(unsigned int event_type, char *format, ...)
{
    va_list			args;
    char			*cp;
    static unsigned int		debug_flags;
    static int			firsttime = 1;

    if (firsttime) {
	debug_flags = 0;
	cp = getenv("HNMS_DEBUG");
	if (cp) {
	    if (strstr(cp, "DEBUG_ERRORS"))
		debug_flags = debug_flags | DEBUG_ERRORS;
	    if (strstr(cp, "DEBUG_HNMP"))
		debug_flags = debug_flags | DEBUG_HNMP;
	    if (strstr(cp, "DEBUG_SNMP"))
		debug_flags = debug_flags | DEBUG_SNMP;
	    if (strstr(cp, "DEBUG_ICMP"))
		debug_flags = debug_flags | DEBUG_ICMP;
	    if (strstr(cp, "DEBUG_OTHER"))
		debug_flags = debug_flags | DEBUG_OTHER;
	    if (strstr(cp, "DEBUG_STANDARD"))
		debug_flags = DEBUG_STANDARD;
	    if (strstr(cp, "DEBUG_ALL"))
		debug_flags = DEBUG_ALL;
	}
    }
    if (debug_flags & event_type) {
	va_start(args, format);
	vprintf(format, args);
	va_end(args);
    }
}
