/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1985, 1997 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Vuid (Virtual User Input Device) queue management.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/vuid_event.h>
#include <sys/vuid_queue.h>

static Vuid_q_node *vq_alloc_node(Vuid_queue *vq);
static void vq_free_node(Vuid_queue *vq, Vuid_q_node *vqn);

static int tv_equal(struct timeval32 a, struct timeval32 b);
static struct timeval32 tv_subt(struct timeval32 atv, struct timeval32 btv);
static struct timeval32 tv_divide(struct timeval32 tv, int dividend);
static struct timeval32 tv_mult(struct timeval32 tv, int multiplier);
#define	tv_to_usec(tv)	(((tv).tv_sec * 1000000) + (tv).tv_usec)
		/* Works for small numbers (< 1000) of seconds */
static struct timeval32 usec_to_tv(int usec);

void
vq_initialize(Vuid_queue *vq, caddr_t data, u_int bytes)
{
	Vuid_q_node *new_vqns, *vqn;

	/* Initialize vq */
	vq->top = vq->bottom = vq->free = VUID_Q_NODE_NULL;
	vq->size = 1 + (bytes - sizeof (Vuid_q_node)) / sizeof (Vuid_q_node);
	/* Place in pool by freeing all nodes (fudge vq->num for this) */
	new_vqns = (Vuid_q_node *)data;
	vq->num = vq->size;
	for (vqn = new_vqns; vqn < new_vqns + vq->size; vqn++)
		vq_free_node(vq, vqn);
}

Vuid_q_code
vq_put(Vuid_queue *vq, Firm_event *firm_event)
{
	Vuid_q_node *vp;

	/* Merge into existing events based on time stamp */
	for (vp = vq->bottom; vp; vp = vp->prev) {
		/* Put later times closer to the bottom than earlier ones */
		if (timercmp(&vp->firm_event.time, &firm_event->time,
		    < /* comment to make cstyle happy - heinous! */) ||
		    tv_equal(vp->firm_event.time, firm_event->time)) {
			Vuid_q_node *vqn = vq_alloc_node(vq);

			if (vqn == VUID_Q_NODE_NULL)
				return (VUID_Q_OVERFLOW);
			vqn->firm_event = *firm_event;
			/* Insert vqn before vq (neither are null) */
			/* Initialize vqn's next and prev */
			vqn->next = vp->next;
			vqn->prev = vp;
			/* Fix up vp next's prev */
			if (vp->next != VUID_Q_NODE_NULL)
				vp->next->prev = vqn;
			/* Fix up vp's next */
			vp->next = vqn;
			/* Change bottom */
			if (vp == vq->bottom)
				vq->bottom = vqn;
			/* Change top */
			if (vq->top == VUID_Q_NODE_NULL)
				vq->top = vqn;
			return (VUID_Q_OK);
		}
	}
	/* Place at top of queue */
	return (vq_putback(vq, firm_event));
}

Vuid_q_code
vq_get(Vuid_queue *vq, Firm_event *firm_event)
{
	Vuid_q_node *vqn = vq->top;

	if (vqn == VUID_Q_NODE_NULL)
		return (VUID_Q_EMPTY);
	/* Conditionally copy data */
	if (firm_event != FIRM_EVENT_NULL)
		*firm_event = vqn->firm_event;
	/* Change top */
	vq->top = vqn->next;
	/* Null new top's prev */
	if (vq->top != VUID_Q_NODE_NULL)
		vq->top->prev = VUID_Q_NODE_NULL;
	/* Change bottom */
	if (vq->bottom == vqn)
		vq->bottom = VUID_Q_NODE_NULL;
	/* Release storage */
	vq_free_node(vq, vqn);
	return (VUID_Q_OK);
}

Vuid_q_code
vq_peek(Vuid_queue *vq, Firm_event *firm_event)
{
	if (vq->top == VUID_Q_NODE_NULL)
		return (VUID_Q_EMPTY);
	*firm_event = vq->top->firm_event;
	return (VUID_Q_OK);
}

Vuid_q_code
vq_putback(Vuid_queue *vq, Firm_event *firm_event)
{
	Vuid_q_node *vqn = vq_alloc_node(vq);

	if (vqn == VUID_Q_NODE_NULL)
		return (VUID_Q_OVERFLOW);
	vqn->firm_event = *firm_event;
	/* Change new top's next */
	vqn->next = vq->top;
	/* Null new top's prev */
	vqn->prev = VUID_Q_NODE_NULL;
	/* Change old top's prev */
	if (vq->top != VUID_Q_NODE_NULL)
		vq->top->prev = vqn;
	/* Change top */
	vq->top = vqn;
	/* Change bottom */
	if (vq->bottom == VUID_Q_NODE_NULL)
		vq->bottom = vqn;
	return (VUID_Q_OK);
}

int
vq_compress(Vuid_queue *vq, int factor)
{
	struct timeval32 tv_interval, tv_avg_diff, tv_diff; /* Intermediates */
	struct timeval32 tv_threshold;
	Vuid_q_node *base, *victim;
	Vuid_q_node *victim_next;
	int num_start;

	if (vq->top == VUID_Q_NODE_NULL)
		return (0);
	num_start = vq->num;
	/*
	 * Determine the threshold time interval under which events of
	 * the same type (valuator only) are collapsed.
	 * min_time_betvqnen_values = ((first_entry_time - last_entry_time) /
	 * max_number_of_queue_entries) * factor_by_which_to_compress_queue;
	 */
	tv_interval = tv_subt(vq->bottom->firm_event.time,
	    vq->top->firm_event.time);
	tv_avg_diff = tv_divide(tv_interval, vq->num);
	tv_threshold = tv_mult(tv_avg_diff, factor);
	/* March down list */
	for (base = vq->top; base; base = base->next) {
		/* See if valuator event */
		if (!vq_is_valuator(base))
			continue;
		/* Run down list looking for a collapse victim */
		for (victim = base->next; victim; victim = victim_next) {
			/* Remember next victim incase axe victim below */
			victim_next = victim->next;
			/* Fail if not valuator event */
			if (!vq_is_valuator(victim))
				goto Advance_Base;
			/*
			 * May peek ahead and do the collapse as long as the
			 * intervening times of other valuator event types
			 * are the same.  Fail if intervening event's time
			 * differs from victim's.
			 */
			if (victim->prev != base) {
				if (!tv_equal(victim->prev->firm_event.time,
				    victim->firm_event.time))
					goto Advance_Base;
			}
			/* Fail if time difference is above threshold */
			tv_diff = tv_subt(victim->firm_event.time,
			    base->firm_event.time);
			/* Zero factor means collapse regardless of threshold */
			if ((factor > 0) &&
			    (timercmp(&tv_diff, &tv_threshold, > /* cstyle */)))
				goto Advance_Base;
			/* Do collapse if same event id */
			if (victim->firm_event.id == base->firm_event.id) {
				/* Collapse value into base event */
				switch (base->firm_event.pair_type) {
				case FE_PAIR_ABSOLUTE:
					/* id is delta */
					base->firm_event.value +=
					    victim->firm_event.value;
					break;
				case FE_PAIR_DELTA:
					/* id is absolute */
					/* Fall through */
				default:
					/* Assume id is absolute */
					base->firm_event.value =
					    victim->firm_event.value;
					break;
				}
				/* Remove victim node */
				vq_delete_node(vq, victim);
			}
		}
Advance_Base:
	{}
	}
	return (num_start - vq->num);
}

int
vq_is_valuator(Vuid_q_node *vqn)
{
	return ((vqn->firm_event.value < 1 && vqn->firm_event.value > -1) ||
	    (vqn->firm_event.pair_type == FE_PAIR_DELTA) ||
	    (vqn->firm_event.pair_type == FE_PAIR_ABSOLUTE));
}

void
vq_delete_node(Vuid_queue *vq, Vuid_q_node *vqn)
{
	/* Use get if removing off top of queue */
	if (vqn == vq->top) {
		(void) vq_get(vq, FIRM_EVENT_NULL);
		return;
	}
	/* Update previous next link (not null else vqn would be top) */
	vqn->prev->next = vqn->next;
	/* Change bottom */
	if (vq->bottom == vqn)
		vq->bottom = vqn->prev;
	else
		/* Update next previous link (if null else vqn is bottom) */
		vqn->next->prev = vqn->prev;
	/* Release storage */
	vq_free_node(vq, vqn);
}

/*
 * Caller must initialize data returned from vq_alloc_node.
 * VUID_Q_NODE_NULL is possible.
 */
static Vuid_q_node *
vq_alloc_node(Vuid_queue *vq)
{
	Vuid_q_node *vqn;

	if (vq->free == VUID_Q_NODE_NULL)
		return (VUID_Q_NODE_NULL);
	vqn = vq->free;
	vq->free = vq->free->next;
	vq->num++;
	vqn->next = VUID_Q_NODE_NULL;
	return (vqn);
}

static void
vq_free_node(Vuid_queue *vq, Vuid_q_node *vqn)
{
	vqn->next = vq->free;
	vqn->prev = VUID_Q_NODE_NULL;
	vq->free = vqn;
	vq->num--;
}

static int
tv_equal(struct timeval32 a, struct timeval32 b)
{
	return (a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec);
}

/* atv-btv */
static struct timeval32
tv_subt(struct timeval32 atv, struct timeval32 btv)
{
	if ((atv.tv_usec < btv.tv_usec) && atv.tv_sec) {
		atv.tv_usec += 1000000;
		atv.tv_sec--;
	}
	if (atv.tv_usec > btv.tv_usec)
		atv.tv_usec -= btv.tv_usec;
	else
		atv.tv_usec = 0;
	if (atv.tv_sec > btv.tv_sec)
		atv.tv_sec -= btv.tv_sec;
	else {
		if (atv.tv_sec < btv.tv_sec)
			atv.tv_usec = 0;
		atv.tv_sec = 0;
	}
	return (atv);
}

/* tv / dividend */
static struct timeval32
tv_divide(struct timeval32 tv, int dividend)
{
	int usecs;

	if (dividend == 0)
		return (tv);
	usecs = tv_to_usec(tv);
	usecs /= dividend;
	tv = usec_to_tv(usecs);
	return (tv);
}

/* tv * multiplier (works for small multipliers * seconds, < 1000) */
static struct timeval32
tv_mult(struct timeval32 tv, int multiplier)
{
	int usecs;

	usecs = tv_to_usec(tv);
	usecs *= multiplier;
	tv = usec_to_tv(usecs);
	return (tv);
}

static struct timeval32
usec_to_tv(int usec)
{
	struct timeval32 tv;

	tv.tv_sec = usec / 1000000;
	tv.tv_usec = usec % 1000000;
	return (tv);
}