/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2009 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * alarm [-r] [varname [+]when]
 *
 *   David Korn
 *   AT&T Labs
 *
 */

#include	"defs.h"
#include	<error.h>
#include	<stak.h>
#include	"builtins.h"
#include	"FEATURE/time"

#define R_FLAG	1
#define L_FLAG	2

struct	tevent
{
	Namfun_t	fun;
	Namval_t	*node;
	Namval_t	*action;
	struct tevent	*next;
	long		milli;
	int		flags;
	void            *timeout;
	Shell_t		*sh;
};

static const char ALARM[] = "alarm";

static void	trap_timeout(void*);

/*
 * insert timeout item on current given list in sorted order
 */
static void *time_add(struct tevent *item, void *list)
{
	register struct tevent *tp = (struct tevent*)list;
	if(!tp || item->milli < tp->milli)
	{
		item->next = tp;
		list = (void*)item;
	}
	else
	{
		while(tp->next && item->milli > tp->next->milli)
			tp = tp->next;
		item->next = tp->next;
		tp->next = item;
	}
	tp = item;
	tp->timeout = (void*)sh_timeradd(tp->milli,tp->flags&R_FLAG,trap_timeout,(void*)tp);
	return(list);
}

/*
 * delete timeout item from current given list, delete timer
 */
static 	void *time_delete(register struct tevent *item, void *list)
{
	register struct tevent *tp = (struct tevent*)list;
	if(item==tp)
		list = (void*)tp->next;
	else
	{
		while(tp && tp->next != item)
			tp = tp->next;
		if(tp)
			tp->next = item->next;
	}
	if(item->timeout)
		timerdel((void*)item->timeout);
	return(list);
}

static void	print_alarms(void *list)
{
	register struct tevent *tp = (struct tevent*)list;
	while(tp)
	{
		if(tp->timeout)
		{
			register char *name = nv_name(tp->node);
			if(tp->flags&R_FLAG)
			{
				double d = tp->milli;
				sfprintf(sfstdout,e_alrm1,name,d/1000.);
			}
			else
				sfprintf(sfstdout,e_alrm2,name,nv_getnum(tp->node));
		}
		tp = tp->next;
	}
}

static void	trap_timeout(void* handle)
{
	register struct tevent *tp = (struct tevent*)handle;
	tp->sh->trapnote |= SH_SIGALRM;
	if(!(tp->flags&R_FLAG))
		tp->timeout = 0;
	tp->flags |= L_FLAG;
	tp->sh->sigflag[SIGALRM] |= SH_SIGALRM;
	if(sh_isstate(SH_TTYWAIT))
		sh_timetraps();
}

void	sh_timetraps(void)
{
	register struct tevent *tp, *tpnext;
	register struct tevent *tptop;
	while(1)
	{
		sh.sigflag[SIGALRM] &= ~SH_SIGALRM;
		tptop= (struct tevent*)sh.st.timetrap;
		for(tp=tptop;tp;tp=tpnext)
		{
			tpnext = tp->next;
			if(tp->flags&L_FLAG)
			{
				tp->flags &= ~L_FLAG;
				if(tp->action)
					sh_fun(tp->action,tp->node,(char**)0);
				tp->flags &= ~L_FLAG;
				if(!tp->flags)
				{
					nv_unset(tp->node);
					nv_close(tp->node);
				}
			}
		}
		if(!(sh.sigflag[SIGALRM]&SH_SIGALRM))
			break;
	}
}


/*
 * This trap function catches "alarm" actions only
 */
static char *setdisc(Namval_t *np, const char *event, Namval_t* action, Namfun_t
 *fp)
{
        register struct tevent *tp = (struct tevent*)fp;
	if(!event)
		return(action?"":(char*)ALARM);
	if(strcmp(event,ALARM)!=0)
	{
		/* try the next level */
		return(nv_setdisc(np, event, action, fp));
	}
	if(action==np)
		action = tp->action;
	else
		tp->action = action;
	return(action?(char*)action:"");
}

/*
 * catch assignments and set alarm traps
 */
static void putval(Namval_t* np, const char* val, int flag, Namfun_t* fp)
{
	register struct tevent *tp;
	register double d;
	if(val)
	{
		double now;
#ifdef timeofday
		struct timeval tmp;
		timeofday(&tmp);
		now = tmp.tv_sec + 1.e-6*tmp.tv_usec;
#else
		now = (double)time(NIL(time_t*));
#endif /* timeofday */
		nv_putv(np,val,flag,fp);
		d = nv_getnum(np);
		tp = (struct tevent*)fp;
		if(*val=='+')
		{
			double x = d + now;
			nv_putv(np,(char*)&x,NV_INTEGER,fp);
		}
		else
			d -= now;
		tp->milli = 1000*(d+.0005);
		if(tp->timeout)
			sh.st.timetrap = time_delete(tp,sh.st.timetrap);
		if(tp->milli > 0)
			sh.st.timetrap = time_add(tp,sh.st.timetrap);
	}
	else
	{
		tp = (struct tevent*)nv_stack(np, (Namfun_t*)0);
		sh.st.timetrap = time_delete(tp,sh.st.timetrap);
		if(tp->action)
			nv_close(tp->action);
		nv_unset(np);
		free((void*)fp);
	}
}

static const Namdisc_t alarmdisc =
{
	sizeof(struct tevent),
	putval,
	0,
	0,
	setdisc,
};

int	b_alarm(int argc,char *argv[],void *extra)
{
	register int n,rflag=0;
	register Namval_t *np;
	register struct tevent *tp;
	register Shell_t *shp = ((Shbltin_t*)extra)->shp;
	while (n = optget(argv, sh_optalarm)) switch (n)
	{
	    case 'r':
		rflag = R_FLAG;
		break;
	    case ':':
		errormsg(SH_DICT,2, "%s", opt_info.arg);
		break;
	    case '?':
		errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg);
		break;
	}
	argc -= opt_info.index;
	argv += opt_info.index;
	if(error_info.errors)
		errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0));
	if(argc==0)
	{
		print_alarms(shp->st.timetrap);
		return(0);
	}
	if(argc!=2)
		errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0));
	np = nv_open(argv[0],shp->var_tree,NV_NOARRAY|NV_VARNAME|NV_NOASSIGN);
	if(!nv_isnull(np))
		nv_unset(np);
	nv_setattr(np, NV_DOUBLE);
	if(!(tp = newof(NIL(struct tevent*),struct tevent,1,0)))
		errormsg(SH_DICT,ERROR_exit(1),e_nospace);
	tp->fun.disc = &alarmdisc;
	tp->flags = rflag;
	tp->node = np;
	tp->sh = shp;
	nv_stack(np,(Namfun_t*)tp);
	nv_putval(np, argv[1], 0);
	return(0);
}

