/* This file is part of the Project Athena Zephyr Notification System.
 * It contains the hostmanager queueing routines.
 *
 *      Created by:     David C. Jedlinsky
 *
 *      $Id: 333bf3e87dd53e32d2ce4260ad71111ba86c907f $
 *
 *      Copyright (c) 1987 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h". 
 */

#include "zhm.h"

#ifndef lint
#ifndef SABER
static const char rcsid_queue_c[] = "$Id: 333bf3e87dd53e32d2ce4260ad71111ba86c907f $";
#endif /* SABER */
#endif /* lint */

typedef struct _Queue {
    Timer *timer;
    int retries;
    ZNotice_t notice;
    void *packet;
    int len;
    struct sockaddr_in reply;
    struct _Queue *next, **prev_p;
} Queue;

static Queue *hm_queue;
static int retransmits_enabled = 0;

static Queue *find_notice_in_queue(ZNotice_t *notice);
static void queue_timeout(void *arg);

int rexmit_times[] = { 2, 2, 4, 4, 8, -1 };

#ifdef DEBUG
static void dump_queue(void);
#endif

void
init_queue(void)
{
    Queue *q;

    while (hm_queue) {
	q = hm_queue;
	if (q->timer)
	    timer_reset(q->timer);
	free(q->packet);
	hm_queue = q->next;
	free(q);
    }

    DPR("Queue initialized and flushed.\n");
}

Code_t
add_notice_to_queue(ZNotice_t *notice,
		    char *packet,
		    struct sockaddr_in *repl,
		    int len)
{
    Queue *entry;

    DPR("Adding notice to queue...\n");
    if (!find_notice_in_queue(notice)) {
	entry = (Queue *) malloc(sizeof(Queue));
	if (entry == NULL)
	    return(ZERR_NONOTICE);
	entry->retries = 0;
	entry->packet = (char *) malloc(Z_MAXPKTLEN);
	if (entry->packet == NULL) {
	    free(entry);
	    return(ZERR_NONOTICE);
	}
	memcpy(entry->packet, packet, Z_MAXPKTLEN);
	entry->len = len;
	if (ZParseNotice(entry->packet, len, &entry->notice) != ZERR_NONE) {
	    syslog(LOG_ERR, "ZParseNotice failed, but succeeded before");
	    free(entry->packet);
	} else {
	    entry->reply = *repl;
	    /*LIST_INSERT(&hm_queue, entry);*/

	    (entry)->next = *(&hm_queue);
	    if (*&hm_queue) ((*(&hm_queue))->prev_p = &(entry)->next);
	    (*&hm_queue) = (entry);
	    (entry)->prev_p = (&hm_queue);
		
	}
	entry->timer = (retransmits_enabled) ?
	    timer_set_rel(rexmit_times[0], queue_timeout, entry) : NULL;
    }
    return(ZERR_NONE);
}

Code_t
remove_notice_from_queue(ZNotice_t *notice,
			 ZNotice_Kind_t *kind,
			 struct sockaddr_in *repl)
{
    Queue *entry;

    DPR("Removing notice from queue...\n");
    entry = find_notice_in_queue(notice);
    if (entry == NULL)
	return(ZERR_NONOTICE);

    *kind = entry->notice.z_kind;
    *repl = entry->reply;
    if (entry->timer)
	timer_reset(entry->timer);
    free(entry->packet);
    /*LIST_DELETE(entry);*/
    *(entry)->prev_p = (entry)->next;
    if((entry)->next) ((entry)->next->prev_p = (entry)->prev_p);
#ifdef DEBUG
    dump_queue();
#endif /* DEBUG */
    free(entry);
    return(ZERR_NONE);
}

/* We have a server; transmit all of our packets. */
void
retransmit_queue(struct sockaddr_in *sin)
{
    Queue *entry;
    Code_t ret;

    DPR("Retransmitting queue to new server...\n");
    ret = ZSetDestAddr(sin);
    if (ret != ZERR_NONE) {
	Zperr (ret);
	com_err("queue", ret, "setting destination");
    }
    for (entry = hm_queue; entry; entry = entry->next) {
	DPR("notice:\n");
	DPR2("\tz_kind: %d\n", entry->notice.z_kind);
	DPR2("\tz_port: %u\n", ntohs(entry->notice.z_port));
	DPR2("\tz_class: %s\n", entry->notice.z_class);
	DPR2("\tz_clss_inst: %s\n", entry->notice.z_class_inst);
	DPR2("\tz_opcode: %s\n", entry->notice.z_opcode);
	DPR2("\tz_sender: %s\n", entry->notice.z_sender);
	DPR2("\tz_recip: %s\n", entry->notice.z_recipient);
	ret = ZSendPacket(entry->packet, entry->len, 0);
	if (ret != ZERR_NONE) {
	    Zperr(ret);
	    com_err("queue", ret, "sending raw notice");
	}
	entry->timer = timer_set_rel(rexmit_times[0], queue_timeout, entry);
	entry->retries = 0;
    }
    retransmits_enabled = 1;
}

/* We lost our server; nuke all of our timers. */
void
disable_queue_retransmits(void)
{
    Queue *entry;

    for (entry = hm_queue; entry; entry = entry->next) {
	if (entry->timer)
	    timer_reset(entry->timer);
	entry->timer = NULL;
    }
    retransmits_enabled = 0;
}

#ifdef DEBUG
static void
dump_queue(void)
{
    Queue *entry;
    char *mp;
    int ml;

    DPR("Dumping queue...\n");
    if (!hm_queue) {
	printf("Queue is empty.\n");
	return;
    }

    for (entry = hm_queue; entry; entry = entry->next) {
	printf("notice:\n");
	printf("\tz_kind: %d\n", entry->notice.z_kind);
	printf("\tz_port: %u\n", ntohs(entry->notice.z_port));
	printf("\tz_class: %s\n", entry->notice.z_class);
	printf("\tz_clss_inst: %s\n", entry->notice.z_class_inst);
	printf("\tz_opcode: %s\n", entry->notice.z_opcode);
	printf("\tz_sender: %s\n", entry->notice.z_sender);
	printf("\tz_recip: %s\n", entry->notice.z_recipient);
	printf("\tMessage:\n");
	mp = entry->notice.z_message;
	for (ml = strlen(mp) + 1; ml <= entry->notice.z_message_len; ml++) {
	    printf("\t%s\n", mp);
	    mp += strlen(mp)+1;
	    ml += strlen(mp);
	}
    }
}
#endif /* DEBUG */

int
queue_len(void)
{
    int length = 0;
    Queue *entry;

    for (entry = hm_queue; entry; entry = entry->next)
	length++;
    return length;
}

static Queue *
find_notice_in_queue(ZNotice_t *notice)
{
    Queue *entry;

    for (entry = hm_queue; entry; entry = entry->next) {
	if (ZCompareUID(&entry->notice.z_uid, &notice->z_uid))
	    return entry;
    }
    return NULL;
}

static void
queue_timeout(void *arg)
{
    Queue *entry = (Queue *) arg;
    Code_t ret;

    entry->timer = NULL;
    ret = ZSetDestAddr(&serv_sin);
    if (ret != ZERR_NONE) {
	Zperr(ret);
	com_err("queue", ret, "setting destination");
    }
    entry->retries++;
    if (rexmit_times[entry->retries] == -1) {
	new_server(NULL);
	return;
    }
    DPR("Resending notice:\n");
    DPR2("\tz_kind: %d\n", entry->notice.z_kind);
    DPR2("\tz_port: %u\n", ntohs(entry->notice.z_port));
    DPR2("\tz_class: %s\n", entry->notice.z_class);
    DPR2("\tz_clss_inst: %s\n", entry->notice.z_class_inst);
    DPR2("\tz_opcode: %s\n", entry->notice.z_opcode);
    DPR2("\tz_sender: %s\n", entry->notice.z_sender);
    DPR2("\tz_recip: %s\n", entry->notice.z_recipient);
    ret = ZSendPacket(entry->packet, entry->len, 0);
    if (ret != ZERR_NONE) {
	Zperr(ret);
	com_err("queue", ret, "sending raw notice");
    }
    entry->timer = timer_set_rel(rexmit_times[entry->retries], queue_timeout,
				 entry);
}

