#ifndef	lint
static char sccsid[] = "@(#)writer.c 1.6 88/12/21";
#endif

#include <stdio.h>
#include "cbratp.h"
#include <sys/time.h>

/*
 * writer_proc()
 *	takes a packet from the output queue and sends it to the rs232
 *	if we have timed out and a packet has not been ack'd then
 *	resend the packet.
 *	if we have no packets to send then run idle_writer() which may
 *	give us some.
 */
writer_proc()
{
    register struct frame	*o;
    register u_char	control;

    if (rs232oready())
	return;

    if (timer() > timeout)
    {
	if (writer.frame != writer.lastack)
	    retransmit();
	should_sync = 1;
    }
    /* if we have a spare frame slot and there is something to send */
    if (NEXT(writer.frame) != writer.lastack
	&& (out_queue.size > 0 || draining == 0)) {
	if (out_queue.size || idle_writer()) {
	    writer.frame = NEXT(writer.frame);
	    o = &out_going[writer.frame];
	    copyin(o);
	    control = (writer.frame << 6);	/* SEQ */
	    control |= (reader.lastack << 4);	/* ACK */
	    control |= o->f_flags;
	    o->f_state = SENT;
	    send_pkt(control);
	}
    }

    if (should_sync || draining)
	send_pkt((writer.frame << 6)|(reader.lastack << 4)|SYNC);
}
/*
 * send_pkt(control)
 *	constructs a packet with the control byte being 'control'
 * 	and sends it to the rs232 connection
 */
send_pkt(control)
    u_char control;
{
    register struct frame *f;
    register u_char	*p;
    register int	i;
    u_short		crc;

    rs232out(control);
    if (control&SYNC) 
	crc = calculate_crc(&control, 1);
    else {
	f = &out_going[SEQ(control)];
	p = f->f_buf;
	crc = adjust_crc((u_short) f->f_crc, control);
	for (i = 0; i < f->f_len; i++)
	    rs232out(*p++);
    }
    rs232out((crc >> 8) & 0xff);
    rs232out(crc & 0xff);
    rs232rawemit(START_STOP);
    rs232flush();
    if (rs232_stats != NULL) {
	(void) fprintf(rs232_stats, "%s\t%d\t%d\n",
		control&SYNC ? "S": control&RETRY ? "R": "e",
		control&SYNC ? 0: f->f_len,
		control&SYNC ? 0: f->f_ulen);
	(void) fflush(rs232_stats);
    }
    should_sync = 0;
}

/*
 * copyin(o)
 *	moves a packet from the output queue to the out_going frame
 *	pointed to by 'o'.
 *	The data is compressed is large and partial CRC is calculated.
 */
copyin(o)
    register struct frame *o;
{
    register struct qdata *q = out_queue.head;

    o->f_flags = q->q_flags;
    o->f_len = q->q_len;
    o->f_ulen = q->q_len;

    if (q->q_len > COMPRESSSIZE) {
	o->f_len = compress(q->q_buf, q->q_len);
	o->f_flags |= CMP_FLAG;
    }
    bcopy((char *) q->q_buf, (char *) o->f_buf + 1, o->f_len);
    *o->f_buf = q->q_chan;
    o->f_crc = calculate_crc(o->f_buf, ++o->f_len);
    out_queue.head++;
    if (out_queue.head == out_queue.data + MAXQSIZE)
	out_queue.head = out_queue.data;
    out_queue.size--;
}

/*
 * enqueued(c, p, len, flags)
 *	Tries to enqueue data pointed to by 'p' of length 'len' for channel 'c'
 *	onto the output queue.  Writes on identical channels are reduced
 *	into one packet if possible.
 *	Returns 1 if successful and 0 if no room in queue.
 */
static
enqueued(c, p, len, flags)
    int	c;
    u_char *p;
    int	len;
    int	flags;
{
    if (out_queue.size == MAXQSIZE)
	return 0;

    if (chan[c]->type == WRITE_CHN && flags != CNT_FLAG) {
	register int i = out_queue.size;
	register struct qdata *q = out_queue.tail;

	for (i = 0; i < out_queue.size; i++) {
	    if (--q == out_queue.data - 1)
		q = out_queue.data + MAXQSIZE - 1;
	    if (q->q_chan == c) {
		if (QBUFSIZE - q->q_len >= len) {
		    bcopy((char *) p, (char *) &q->q_buf[q->q_len], len);
		    q->q_len += len;
		    q->q_flags = flags;
		    return 1;
		}
		break;
	    }
	}
    }

    bcopy((char *) p, (char *) out_queue.tail->q_buf, len);
    out_queue.tail->q_chan = c;
    out_queue.tail->q_len = len;
    out_queue.tail->q_flags = flags;
    out_queue.size++;
    out_queue.tail++;
    if (out_queue.tail == out_queue.data + MAXQSIZE)
	out_queue.tail = out_queue.data;

    return 1;
}

/*
 * retransmit()
 *	retransmits the next packet after the last one that was received
 */
retransmit()
{
    register u_char control;

    if (out_going[NEXT(writer.lastack)].f_state != IDLE) {
	control = (NEXT(writer.lastack) << 6);	/* SEQ */
	control |= (reader.lastack << 4);	/* ACK */
	control |= out_going[NEXT(writer.lastack)].f_flags;
	control |= RETRY;
	send_pkt(control);
    }
}

/*
 * enqueue(c, buf, len)
 *	enqueues data 'buf' for channel 'c' on the output queue
 *	data is split if too large for a single packet.
 *	if the queue fills then wakeup_io() is called to drain 
 *	some of the packets
 */
void
enqueue(c, buf, len)
    u_char c;
    u_char *buf;
    int	len;
{
    register int i = 0;
    register int size;

    do {
	if ((size = len) > QBUFSIZE) {
	    size = QBUFSIZE;
	    while (!enqueued((int) c, buf + i, size, CNT_FLAG))
		wakeup_io();
	}
	else
	    while (!enqueued((int) c, buf + i, size, 0))
		wakeup_io();
	len -= size;
	i += size;
    } while (len > 0);
}

/*
 * timer()
 *	returns the current time in milli seconds
 */
timer()
{
    struct timeval t;

    (void) gettimeofday(&t, (struct timezone *) 0);

    return(t.tv_sec * 1000 + t.tv_usec / 1000);
}
