/* 
  This file contains code related to the zwgcplus extension to zwgc.
  zwgc is copyrighted by the Massachusetts Institute of Technology.
  This file is public domain.
  Written by Andrew Plotkin, ap1i+@andrew.cmu.edu
  Timequeue code added by Ryan Ingram, ryani+@andrew.cmu.edu
  Rewritten for incorporation into MIT zwgc from 2.0.2 by Derrick Brashear
 */

#include <sysdep.h>
#if (!defined(lint) && !defined(SABER))
static char rcsid_plus_c[] = "$Id: plus.c,v 1.5 2001/12/19 15:52:12 mattiasa Exp $";
#endif

#include <zephyr/mit-copyright.h>
#include <zephyr/zephyr.h>

#include "new_memory.h"
#include "node.h"
#include "exec.h"
#include "eval.h"
#include "buffer.h"
#include "port.h"
#include "variables.h"
#include "notice.h"

#ifndef X_DISPLAY_MISSING
#include "X_gram.h"
#include "xrevstack.h"
#endif /* X_DISPLAY_MISSING */

#include "plus.h"

int get_full_names = 0;

#define HASHSIZE (251)

typedef struct timenode_s {
  ZNotice_t *notice;
  struct timenode_s *next;
  time_t when;
  char *event_name;
} TimeNode;

typedef struct _notnode {
    ZNotice_t *notice;
    int fake_notice; /* if TRUE, do not call ZFreeNotice() */
    int refcount;
    struct _notnode *next;
    char *opcode;
    char *hname;
} notnode;

static ZNotice_t *stored_notice;
static notnode *notlist[HASHSIZE];
TimeNode *timeq_head = NULL;

TimeNode 
*addtimenode(head, node)
  TimeNode *head;
  TimeNode *node;
{
  if(head == NULL) {
#ifdef DEBUG_TIMEQUEUE
    fprintf(stderr, "adding new timenode; creating queue\n");
#endif
    node->next = NULL;
    return node;
  }
  
  if(head->when > node->when) {
#ifdef DEBUG_TIMEQUEUE
    fprintf(stderr, "adding new timenode at start of queue\n");
#endif
    node->next = head;
    return node;
  }

  head->next = addtimenode(head->next, node);
  return head;
}

void 
handle_timeq_event(event)
  TimeNode *event;
{
  char *tmp;

  char buf[128];
  notnode *pt;
  int bx = list_hash_fun(event->notice);

  for (pt=notlist[bx]; pt && pt->notice!=event->notice; pt=pt->next);

  /* "time-" + event_name + '\0' */
#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "handle_timeq_event()\n");
#endif

  if (strlen(event->event_name)<123)
    sprintf(buf, "time-%s", event->event_name);
  else
    sprintf(buf, "time-bogus", event->event_name);

#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "opcode: %s\n", buf);
#endif

  event->notice->z_version = "zwgcplus-repeat";
  event->notice->z_opcode = buf;
 
  reprocess_notice(event->notice, pt->hname);

#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "end handle_timeq_event()\n");
#endif
}

void 
schedule_event(secs, name, notice)
  long secs;
  char *name;
  ZNotice_t *notice;
{
  time_t eventtime = (time(NULL)) + secs;
  TimeNode *newnode;
  char *buf;

#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "schedule_event(%ld, %ld, %s)\n", eventtime, secs, name);
#endif

  if(!notice || !name) return;

  list_add_notice(notice);

  newnode = (TimeNode *)malloc(sizeof(TimeNode));
  buf = (char *)malloc(strlen(name) + 1);

  strcpy(buf, name);

#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "name: %s\n", buf);
#endif

  newnode->when = eventtime;
  newnode->event_name = buf;
  newnode->notice = notice;

  timeq_head = addtimenode(timeq_head, newnode);
#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "end schedule_event()\n");
#endif
}

void 
free_timenode(node)
  TimeNode *node;
{
#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "free_timenode(%s)\n", node->event_name);
#endif

  free(node->event_name);
  free(node);
}

/* returns the number of notices destroyed */
int 
destroy_timeq_notice(notice, name)
  ZNotice_t *notice;
  char *name;
{
  TimeNode *curr = timeq_head;
  TimeNode *prev = NULL;
  TimeNode *tmp;

  int ct = 0;

#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "destroy_timeq_notice(%s)\n", name);
#endif

  while(curr != NULL) {
    if(curr->notice == notice &&
       (!name || !strcmp(curr->event_name, name)))
      {
	ct++;
	if(!prev) {
	  timeq_head = curr->next;
	} else {
	  prev->next = curr->next;
	}
	tmp = curr;
	curr = curr->next;
	free_timenode(tmp);
      } else {
	prev = curr;
	curr = curr->next;
      }
  }
  
  return ct;
}

/*
 * Modifices the tv structure to the time its left before next
 * event. Return non-zero in case there is a event to be scheduled otherwize 
 * zero is returned.
 */

int
plus_timequeue_events(struct timeval *tv)
{ 

  time_t timenow = time(NULL);
  TimeNode *curr;

  while(timeq_head != NULL && timeq_head->when <= timenow) {
#ifdef DEBUG_TIMEQUEUE
    fprintf(stderr, "handling event\n");
#endif
    handle_timeq_event(timeq_head);
    curr = timeq_head;
    timeq_head = timeq_head->next;
    free_timenode(curr);
  }

#ifdef DEBUG_TIMEQUEUE
  if(timeq_head != NULL)
    fprintf(stderr, "next event in %ld seconds.\n",
       (timeq_head->when) - timenow);
#endif

  if (timeq_head == NULL)
      return 0;
  tv->tv_sec = timeq_head->when - timenow;
  tv->tv_usec = 0;
  return 1;
}

void
plus_set_hname(notice, hname) 
  ZNotice_t *notice;
  char *hname;
{
  notnode *pt;
  int bx;

  if (hname) {
    bx = list_hash_fun(notice);
    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);
    pt->hname=(char *)malloc(strlen(hname)+1);
    strcpy(pt->hname, hname);
  }
  return;
}

void 
plus_queue_notice(notice)
  ZNotice_t *notice;
{
  char *val;
  int howlong = 0;
  
#ifdef DEBUG_TIMEQUEUE
  fprintf(stderr, "plus_queue_notice()\n");
#endif

  val = var_get_variable("event_time");
  if(val) {
    if(strcmp(val, "kill")) {
      howlong = atoi(val);
#ifdef DEBUG_TIMEQUEUE
      fprintf(stderr, "$event_time %d\n", howlong);
#endif
    } else {
      val = var_get_variable("event_name");
      if(!val || strcmp(val, "all"))
	destroy_timeq_notice(notice, (val && val[0]) ? val : "event");
      else
	destroy_timeq_notice(notice, (char *)NULL);
    }
  }
  
  if(howlong > 0) {
    val = var_get_variable("event_name");
#ifdef DEBUG_TIMEQUEUE
    fprintf(stderr, "$event_name = %s\n", val);
#endif
    schedule_event(howlong, (val && val[0]) ? val : "event", notice);
  }
}

int 
list_hash_fun(notice)
  ZNotice_t *notice;
{
    int ix;
    int res = 0, val = 1, ptval;
    char *pt = (char *)(&notice);

    for (ix=0; ix<sizeof(ZNotice_t *); ix++) {
	ptval = (int)pt[ix];
	if (ptval<0) ptval = (-ptval);
	res += val * ptval;
	res %= HASHSIZE;
	val *= 7;
    };

    return res;
}

/* initialize hash table */
void 
init_noticelist()
{
    int ix;

    stored_notice = NULL;

    for (ix=0; ix<HASHSIZE; ix++) {
	notlist[ix] = NULL;
    }
}

void 
dump_noticelist()
{
    notnode *pt;
    int bx;

    for (bx=0; bx<HASHSIZE; bx++) {
	for (pt=notlist[bx]; pt; pt=pt->next) {
	    fprintf(stderr, "Not %p: %d [%d]\n", pt->notice, pt->refcount, bx);
	}
    }
}

/* add notice to table. Either generate a new entry, or increment ref count. */
void 
list_add_notice(notice)
  ZNotice_t *notice;
{
    notnode *pt;
    int bx = list_hash_fun(notice);

    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (pt) {
	/* found entry */
	pt->refcount++;
    }
    else {
	/* no entry */
	pt = (notnode *)malloc(sizeof(notnode));
	pt->notice = notice;
	pt->refcount = 1;
	pt->fake_notice = 0;
	pt->next = notlist[bx];
	pt->opcode = notice->z_opcode;
	pt->hname = NULL;
	notlist[bx] = pt;
    }

    /*fprintf(stderr, "list_add_notice(%p)\n", notice);
    dump_noticelist();*/
}   

/* remove notice from table. If refcount reaches 0, return 1; if refcount is 
   still positive, return 0; if notice not there, return -1. */
int 
list_del_notice(notice)
  ZNotice_t *notice;
{
    notnode *pt, **ppt;
    int bx = list_hash_fun(notice);

    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (!pt) {
	/* no entry */
	/*fprintf(stderr, "list_del_notice(%p): ERROR\n", notice);
	dump_noticelist();*/
	return (-1);
    }

    pt->refcount--;
    if (pt->refcount > 0) {
	/*fprintf(stderr, "list_del_notice(%p): count %d\n", notice, pt->refcount);
	dump_noticelist();*/
	return 0;
    }

    for (ppt = &(notlist[bx]); (*ppt)!=pt; ppt = &((*ppt)->next));

    *ppt = (*ppt)->next;

    if (!pt->fake_notice)
	ZFreeNotice(pt->notice);
    if (pt->hname)
      free(pt->hname);
    free(pt->notice);
    free(pt);

    /*fprintf(stderr, "list_del_notice(%p): count 0, gone\n", notice);*/
    /*dump_noticelist();*/
    return 1;
}

void 
set_notice_fake(notice, val)
  ZNotice_t *notice;
  int val;
{
    notnode *pt;
    int bx = list_hash_fun(notice);

    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (pt) {
	pt->fake_notice = val;
    }
}

int 
get_notice_fake(notice)
  ZNotice_t *notice;
{
    notnode *pt;
    int bx = list_hash_fun(notice);

    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (pt) {
	return pt->fake_notice;
    }
    else 
	return 0;
}

int 
get_list_refcount(notice)
  ZNotice_t *notice;
{
    notnode *pt;
    int bx = list_hash_fun(notice);

    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (pt) {
	/*fprintf(stderr, "get_list_refcount(%p): count %d\n", notice, pt->refcount);*/
	return pt->refcount;
    }
    else {
	/*fprintf(stderr, "get_list_refcount(%p): count 0\n", notice);*/
	return 0;
    }
}

/* export a reference to the current notice. */
char 
*get_stored_notice()
{
    if (!stored_notice)
	return NULL;

    list_add_notice(stored_notice);

    return (char *)stored_notice;
}

void 
set_stored_notice(notice)
  ZNotice_t *notice;
{
    stored_notice = notice;
}

void 
plus_retry_notice(notice, ch, metaflag)
  ZNotice_t *notice;
  char ch;
  int metaflag;
{
    char buf[128];
    char *tmp;
    notnode *pt;
    int bx;

    if (!notice)
	return;

    bx = list_hash_fun(notice);
    for (pt=notlist[bx]; pt && pt->notice!=notice; pt=pt->next);

    if (metaflag) tmp = "-meta";
    else tmp = "";

    if (ch==' ')
	sprintf(buf, "key%s-space", tmp);
    else if (ch==127)
	sprintf(buf, "key%s-delete", tmp);
    else if (ch==0)
	sprintf(buf, "key%s-ctrl-@", tmp);
    else if (ch==27)
	sprintf(buf, "key%s-esc", tmp);
    else if (isprint(ch))
	sprintf(buf, "key%s-%c", tmp, ch);
    else if (ch>=1 && ch<=26)
	sprintf(buf, "key%s-ctrl-%c", tmp, ch+'a'-1);
    else if (iscntrl(ch))
	sprintf(buf, "key%s-ctrl-%c", tmp, ch+'A'-1);
    else
	sprintf(buf, "key%s-unknown", tmp);

    /* concat the old opcode if they're running in "new" mode */
    if (zwgcplus == 2 && pt && pt->opcode[0] && 
	strcmp(pt->opcode, "") != 0) 
      {
	strcat(buf, " ");
	strncat(buf, pt->opcode, sizeof(buf)-strlen(buf));
      }
      
    notice->z_version = "zwgcplus-repeat";
    notice->z_opcode = buf;

    reprocess_notice(notice, NULL);
}
