/* automatic persistence setting for ax.25 interfaces */

#include "global.h"
#include "ax25.h"
#include "iface.h"
#include "persist.h"
#include "timer.h"

static struct persist *pblk;		/* list of persistence cb's */
static struct timer p_timer;
static void tick_ptimer();
extern char nospace[],notax25[];

/* set/show automatic persistence calculation parameters */
int
dopersist(argc,argv)
int argc;
char *argv[];
{
	register struct persist *pp;
	struct interface *ifp;

	if (argc == 1){			/* no args - show parms */
	    printf("IF    Users    P   P0  Pmin  Pmax  Tinc  Tmax\n");

	    for (pp = pblk; pp != NULLPERSIST; pp = pp->next)
		printf("%-6s%5d%5d%5d%6d%6d%6d%6d\n",pp->iface->name,
			pp->n_users,pp->p,pp->p0,pp->p_min,pp->p_max,
			pp->pt_incr * PT_INTER,pp->pt_max * PT_INTER);

	    return 0;
	}

	if ((ifp = ifunit(argv[1])) == NULLIF)
	    return 1;

	if (!(ifp->flags & IF_AX25)){
	    printf(notax25,argv[1]);
	    return 1;
	}

	stop_timer(&p_timer);

	for (pp = pblk; pp != NULLPERSIST; pp = pp->next)
	    if (pp->iface == ifp)	/* lookup by interface */
		break;

	if (pp == NULLPERSIST){		/* if not in list? */
	    if ((pp = (struct persist *) calloc(1,sizeof(struct persist))) == NULLPERSIST){
		printf(nospace);
		if (pblk != NULLPERSIST)
		    start_timer(&p_timer);
		return 1;
	    }

	    if ((pp->next = pblk) != NULLPERSIST)
		pp->next->prev = pp;

	    pblk = pp;
	    pp->iface = ifp;
	    pp->p0 = P0;
	    pp->p_min = P_MIN;
	    pp->p_max = pp->p = P_MAX;
	    pp->pt_incr = PT_INCR;
	    pp->pt_max = PT_MAX;
	}

	/* set control values, delete cb when p0 value is 0 (or "off") */

	if (argc > 2)
	    if ((pp->p0 = atoi(argv[2])) == 0){
		if (pp->prev != NULLPERSIST)
		    pp->prev->next = pp->next;
		else
		    pblk = pp->next;

		if (pp->next != NULLPERSIST)
		    pp->next->prev = pp->prev;

		free(pp);
	    }

	if (argc > 3)
	    pp->p_min = atoi(argv[3]);

	if (argc > 4)
	    pp->p_max = atoi(argv[4]);

	if (argc > 5)
	    pp->pt_incr = atoi(argv[5]) / PT_INTER;

	if (argc > 6)
	    pp->pt_max = atoi(argv[6]) / PT_INTER;

	/* (re)start the timer only if any persistence cb's left */

	if (pblk != NULLPERSIST){
	    p_timer.start = SEC2TICK(PT_INTER);
	    p_timer.func = tick_ptimer;
	    tick_ptimer();
	}
	return 0;
}

/* update the "number of users" for an interface when a callsign is heard */
/* the ssid is not considered significant */

int
upd_nusers (ifp,call)
struct interface *ifp;
register unsigned char *call;

{
	register struct persist *pp;
	register unsigned hashval;
	register int i;

	for (pp = pblk; pp != NULLPERSIST; pp = pp->next)
	    if (pp->iface == ifp)	/* lookup by interface */
		break;

	if (pp == NULLPERSIST)		/* if not in list? */
	    return -1;			/* return soft failure */

	hashval = 0;
	for (i = 0; i < ALEN; i++)	/* scan callsign (not SSID) */
	    hashval = (hashval << 1) ^ (*call++ & 0xfe);

	hashval %= (unsigned) SLOTS;

	if ((pp->heard[hashval] += pp->pt_incr) > pp->pt_max)
	    pp->heard[hashval] = pp->pt_max;

	return 0;			/* ok */
}

/* tick the "automatic persistence" timer, decrementing the timers and */
/* re-computing the number of users on each channel.  when changed, set */
/* the persistence value for that channel. */

static void
tick_ptimer ()

{
	register struct persist *pp;
	register unsigned i,n_users,p;
	static char *argv[3] = {"2","###",NULLCHAR};

	for (pp = pblk; pp != NULLPERSIST; pp = pp->next){
	    n_users = 0;
	    for (i = 0; i < SLOTS; i++)
		if (pp->heard[i]){
		    pp->heard[i]--;
		    n_users++;		/* count the active users */
		}

	    if ((pp->n_users = n_users) == 0)
		p = pp->p0;
	    else
		p = pp->p0 / n_users;

	    if (p > pp->p_max)		/* keep p within p_min .. p_max */
		p = pp->p_max;

	    if (p < pp->p_min)
		p = pp->p_min;

	    if (pp->p != p){		/* a change in p value? */
		sprintf(argv[1],"%d",pp->p = p); /* convert to string */
		if (pp->iface->ioctl != NULLFP)
		   (*pp->iface->ioctl)(pp->iface,2,argv); /* set p value */
	    }
	}

	start_timer(&p_timer);		/* restart the timer */
}
