/* This file is part of the Project Athena Zephyr Notification System.
 * It contains the hostmanager queueing routines.
 *
 * Created by:	Derek Atkins <warlord@MIT.EDU>
 *
 * $Source: /afs/media-lab.mit.edu/user/warlord/C/zephyr/zhm/RCS/newqueue.c,v $
 * $Author: warlord $
 *
 */

#include "zhm.h"
#include "newqueue.h"
#include "ZRemoteRealms.h"

#ifndef lint
#ifndef SABER
static char rcsid_newqueue_c[] = "$Header: /afs/media-lab.mit.edu/user/warlord/C/zephyr/zhm/RCS/newqueue.c,v 1.11 92/05/16 17:14:11 warlord Exp $";
#endif /* SABER */
#endif /* lint */

extern long time();
Qelem *is_in_queue();
extern int timeout_type;

void init_queue(hm_queue)
Qelem *hm_queue;
{
     struct _qelem *forw;

     while (hm_queue->q_forw != hm_queue) {
	  free(hm_queue->q_forw->q_data->z_packet);
	  free((char *)hm_queue->q_forw->q_data);
	  forw = hm_queue->q_forw;
	  remque(hm_queue->q_forw);
	  free((char *)forw);
     }

     hm_queue->q_forw = hm_queue->q_back = hm_queue;
     hm_queue->q_data = NULL;
     DPR ("Queue initialized and flushed.\n");
}

Code_t add_notice_to_queue(hm_queue, notice, packet, repl, len)
Qelem *hm_queue;
ZNotice_t *notice;
caddr_t packet;
struct sockaddr_in *repl;
int len;
{
     Qelem *elem;
     Queue *entry;

     DPR ("Adding notice to queue...\n");
     if (!is_in_queue(hm_queue, notice)) {
	  elem = (Qelem *)malloc(sizeof(Qelem));
	  entry = (Queue *)malloc(sizeof(Queue));
	  entry->timeout = time((time_t *)0) + NOTICE_TIMEOUT;
	  entry->retries = 0;
	  entry->z_packet = (char *)malloc(Z_MAXPKTLEN);
	  bcopy(packet, entry->z_packet, Z_MAXPKTLEN);
	  if (ZParseNotice(entry->z_packet, len, &entry->z_notice)
	      != ZERR_NONE) {
	       syslog(LOG_ERR, "ZParseNotice failed, but succeeded before");
	       free(entry->z_packet);
	  } else {
	       entry->reply = *repl;
	       elem->q_data = entry;
	       elem->q_forw = elem;
	       elem->q_back = elem;
	       insque(elem, hm_queue->q_back);
	  }
     }

     /* Lets get rid of our Stripped Recipient. */
     free(notice->z_recipient); 

#ifdef DEBUG
     if (!is_in_queue(hm_queue, notice))
	  return(ZERR_NONOTICE);
     else
#endif /* DEBUG */
	  return(ZERR_NONE);
}

Code_t remove_notice_from_queue(hm_queue, notice, kind, repl)
Qelem *hm_queue;
ZNotice_t *notice;
ZNotice_Kind_t *kind;
struct sockaddr_in *repl;
{
     Qelem *elem;

     DPR ("Removing notice from queue...\n");
     if ((elem = is_in_queue(hm_queue, notice)) == NULL)
	  return(ZERR_NONOTICE);
     else {
	  *kind = elem->q_data->z_notice.z_kind;
	  *repl = elem->q_data->reply;
	  free(elem->q_data->z_packet);
	  free((char *)elem->q_data);
	  remque(elem);
	  free((char *)elem);
	  clear_alarm();
#ifdef DEBUG
	  dump_queue(hm_queue);
#endif /* DEBUG */
	  return(ZERR_NONE);
     }
}

void retransmit_queue(hm_queue, sin)
Qelem *hm_queue;
struct sockaddr_in *sin;
{
     Qelem *srch;
     Code_t ret;
     time_t this_time;

     this_time = time((time_t *)0);

     DPR ("Retransmitting queue to new server...\n");
     if ((ret = ZSetDestAddr(sin)) != ZERR_NONE) {
	  Zperr (ret);
	  com_err("queue", ret, "setting destination");
     }
     if ((srch = hm_queue->q_forw) != hm_queue) {
	  do {
	       DPR ("notice:\n");
	       DPR2 ("\tz_kind: %d\n", srch->q_data->z_notice.z_kind);
	       DPR2 ("\tz_port: %u\n",
		     ntohs(srch->q_data->z_notice.z_port));
	       DPR2 ("\tz_class: %s\n", srch->q_data->z_notice.z_class);
	       DPR2 ("\tz_clss_inst: %s\n",
		     srch->q_data->z_notice.z_class_inst);
	       DPR2 ("\tz_opcode: %s\n", srch->q_data->z_notice.z_opcode);
	       DPR2 ("\tz_sender: %s\n", srch->q_data->z_notice.z_sender);
	       DPR2 ("\tz_recip: %s\n", srch->q_data->z_notice.z_recipient);
	       if ((ret = send_outgoing(&srch->q_data->z_notice))
		   != ZERR_NONE) {
		    Zperr (ret);
		    com_err("queue", ret, "sending raw notice");
	       }
	       srch->q_data->timeout = this_time + NOTICE_TIMEOUT;
	       srch->q_data->retries = 0;
	       srch = srch->q_forw;
	  } while (srch != hm_queue);
	  set_alarm();
     }
}

#ifdef DEBUG
Code_t dump_queue(hm_queue)
Qelem *hm_queue;
{
     Qelem *srch;
     caddr_t mp;
     int ml;

     DPR ("Dumping queue...\n");
     if ((srch = hm_queue->q_forw) == hm_queue)
	  printf("Queue is empty.\n");
     else do {
	  printf("notice:\n");
	  printf("\tz_kind: %d\n", srch->q_data->z_notice.z_kind);
	  printf("\tz_port: %u\n", ntohs(srch->q_data->z_notice.z_port));
	  printf("\tz_class: %s\n", srch->q_data->z_notice.z_class);
	  printf("\tz_clss_inst: %s\n", srch->q_data->z_notice.z_class_inst);
	  printf("\tz_opcode: %s\n", srch->q_data->z_notice.z_opcode);
	  printf("\tz_sender: %s\n", srch->q_data->z_notice.z_sender);
	  printf("\tz_recip: %s\n", srch->q_data->z_notice.z_recipient);
	  printf("\tMessage:\n");
	  mp = srch->q_data->z_notice.z_message;
	  for (ml = strlen(mp)+1;
	       ml <= srch->q_data->z_notice.z_message_len; ml++) {
	       printf("\t%s\n", mp);
	       mp += strlen(mp)+1;
	       ml += strlen(mp);
	  }
	  srch = srch->q_forw;
     } while (srch != hm_queue);
}
#endif /* DEBUG */

int queue_len(hm_queue)
Qelem *hm_queue;
{
     int length = 0;
     Qelem *srch;

     if ((srch = hm_queue->q_forw) != hm_queue) {
	  do {
	       length++;
	       srch = srch->q_forw;
	  } while (srch != hm_queue);
     }
     return(length);
}

Qelem *is_in_queue(hm_queue, notice)
Qelem *hm_queue;
ZNotice_t *notice;
{
     Qelem *srch;

     srch = hm_queue->q_forw;
     if (srch == hm_queue)
	  return(NULL);
     do {
	  if (ZCompareUID(&(srch->q_data->z_notice.z_uid), &(notice->z_uid)))
	       return(srch);
	  srch = srch->q_forw;
     } while (srch != hm_queue);
     return(NULL);
}

void resend_notices(hm_queue, sin)
Qelem *hm_queue;
struct sockaddr_in *sin;
{
     Qelem *srch;
     Code_t ret;
     RealmEntry *Realm;
     time_t this_time;
     int changed_server = 0;
     ZNotice_Kind_t kind;
     struct sockaddr_in repl;

     DPR ("Resending notices...\n");
     if ((ret = ZSetDestAddr(sin)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("queue", ret, "setting destination");
     }
     if ((srch = hm_queue->q_forw) == hm_queue) {
	  syslog (LOG_INFO, "No notices, shouldn't have happened!");
     } else do {
	  if (srch->q_data->timeout <= (this_time = time((time_t *)0))) {
	       if ((++(srch->q_data->retries) >= MAXRETRIES) &&
		    ((srch->q_data->retries % MAXRETRIES) == 0) &&
		    (changed_server == 0)) {

		    if ((srch->q_data->z_notice.z_time.tv_sec +
			 ZHM_NOTICE_TIMEOUT) <= this_time) {

			remove_notice_from_queue(hm_queue,
						 &srch->q_data->z_notice,
						 &kind, &repl);
		    } else {
			Realm = ZGetRealmFromAddr((char *)&(sin->sin_addr));
			new_server(Realm, (char *)NULL);
			changed_server = 1;
		    }
		    break;
		} else {
		    DPR ("notice:\n");
		    DPR2 ("\tz_kind: %d\n", srch->q_data->z_notice.z_kind);
		    DPR2 ("\tz_port: %u\n",
			  ntohs(srch->q_data->z_notice.z_port));
		    DPR2 ("\tz_class: %s\n",
			  srch->q_data->z_notice.z_class);
		    DPR2 ("\tz_clss_inst: %s\n",
			  srch->q_data->z_notice.z_class_inst);
		    DPR2 ("\tz_opcode: %s\n",
			  srch->q_data->z_notice.z_opcode);
		    DPR2 ("\tz_sender: %s\n", 
			  srch->q_data->z_notice.z_sender);
		    DPR2 ("\tz_recip: %s\n",
			  srch->q_data->z_notice.z_recipient);
		    if ((ret = send_outgoing(&srch->q_data->z_notice)) 
			!= ZERR_NONE) {
			 Zperr(ret);
			 com_err("queue", ret, "sending raw notice");
		    }
		    srch->q_data->timeout = this_time + NOTICE_TIMEOUT;
		}
	   }
	  srch = srch->q_forw;
     } while (srch != hm_queue);
     set_alarm();
     return;
}
