/
/*
 * IMC2 - an inter-mud communications protocol
 *
 * imc-events.c: IMC event handling
 *
 * Copyright (C) 1996,1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

#include "imc.h"

/*
 * I needed to split up imc.c (2600 lines and counting..) so this file now
 * contains:
 *
 * - event support functions
 * - event callbacks
 */


/*
 *  event support functions
 */

/* we use size-limited recycle lists, since events will be being
 * queued/unqueued quite often
 */

imc_event *imc_event_list, *imc_event_free;
static int event_freecount;

imc_event *imc_new_event(void)
{
  imc_event *p;

  if (imc_event_free)
  {
    p=imc_event_free;
    imc_event_free=p->next;
    event_freecount--;
  }
  else
    p=imc_malloc(sizeof(imc_event));

  p->when=0;
  p->callback=NULL;
  p->data=NULL;
  p->next=NULL;
  p->timed=0;

  return p;
}

void imc_free_event(imc_event *p)
{
  if (event_freecount>10) /* pick a number, any number */
    imc_free(p, sizeof(imc_event));
  else
  {
    p->next=imc_event_free;
    imc_event_free=p;
    event_freecount++;
  }
}

void imc_add_event(int when, void (*callback)(void *), void *data, int timed)
{
  imc_event *p, *scan, **last;

  p=imc_new_event();
  p->when=imc_now+when;
  p->callback=callback;
  p->data=data;
  p->timed=timed;

  for (last=&imc_event_list, scan=*last;
       scan;
       last=&scan->next, scan=*last)
    if (scan->when >= p->when)
      break;

  p->next=scan;
  *last=p;
}

void imc_cancel_event(void (*callback)(void *), void *data)
{
  imc_event *p, *p_next, **last;

  for (last=&imc_event_list, p=*last; p; p=p_next)
  {
    p_next=p->next;

    if ((!callback || p->callback==callback) && p->data==data)
    {
      *last=p_next;
      imc_free_event(p);
    }
    else
      last=&p->next;
  }
}

void imc_run_events(time_t newtime)
{
  imc_event *p;
  void (*callback)(void *);
  void *data;

  while(imc_event_list)
  {
    p=imc_event_list;

    if (p->when > newtime)
      break;

    imc_event_list=p->next;
    callback=p->callback;
    data=p->data;
    imc_now=p->when;

    imc_free_event(p);

    if (callback)
      (*callback)(data);
    else
      imc_logerror("imc_run_events: NULL callback");
  }

  imc_now=newtime;
}

int imc_next_event(void (*callback)(void *), void *data)
{
  imc_event *p;

  for (p=imc_event_list; p; p=p->next)
    if (p->callback==callback && p->data==data)
      return p->when - imc_now;

  return -1;
}



/*
 *  Events
 */

/* expire the imc_reminfo entry pointed at by data */
void ev_expire_reminfo(void *data)
{
  imc_reminfo *r=(imc_reminfo *)data;

  r->type=IMC_REMINFO_EXPIRED;
  imc_cancel_event(NULL, data);
  imc_add_event(IMC_DROP_TIMEOUT, ev_drop_reminfo, data, 0);
}

/* drop the imc_reminfo entry */
void ev_drop_reminfo(void *data)
{
  imc_cancel_event(NULL, data);
  imc_delete_reminfo((imc_reminfo *)data);
}

/* send a keepalive, and queue the next keepalive event */
void ev_keepalive(void *data)
{
  imc_send_keepalive();
  imc_add_event(IMC_KEEPALIVE_TIME, ev_keepalive, NULL, 1);
}

/* maybe shrink the input buffer of the connection 'data' */
void ev_shrink_input(void *data)
{
  imc_connect *c=(imc_connect *)data;
  int len;
  int newsize;
  void *newbuf;

  len=strlen(c->inbuf)+1;
  newsize=c->insize/2;

  if (len<=newsize)
  {
    if (newsize<IMC_MINBUF) /* huh? should never happen.. */
      return;

    /* shrink the buffer one step */
    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->inbuf);
    imc_free(c->inbuf, c->insize);
    c->inbuf=newbuf;
    c->insize=newsize;

    if (newsize<=IMC_MINBUF)
      return;
  }
  
  imc_add_event(IMC_SHRINKTIME, ev_shrink_input, c, 0);
}

/* maybe shrink the output buffer of the connection 'data' */
void ev_shrink_output(void *data)
{
  imc_connect *c=(imc_connect *)data;
  int len;
  int newsize;
  void *newbuf;

  len=strlen(c->outbuf)+1;
  newsize=c->outsize/2;

  if (len<=newsize)
  {
    if (newsize<IMC_MINBUF) /* huh? should never happen.. */
      return;

    /* shrink the buffer one step */
    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->outbuf);
    imc_free(c->outbuf, c->outsize);
    c->outbuf=newbuf;
    c->outsize=newsize;

    if (newsize<=IMC_MINBUF)
      return;
  }
  
  imc_add_event(IMC_SHRINKTIME, ev_shrink_output, c, 0);
}

/* try a reconnect to the given imc_info */
void ev_reconnect(void *data)
{
  imc_info *info=(imc_info *)data;

  if (!info->connection &&
      (info->flags & IMC_RECONNECT))
  {
    if (!imc_connect_to(info->name))
      imc_setup_reconnect(info);
  }
}

/* handle a spam event for counter 1 */
void ev_spam1(void *data)
{
  imc_connect *c=(imc_connect *)data;

  if (c->spamcounter1 > IMC_SPAM1MAX)
  {
    if (c->spamtime1 < 0 || ++c->spamtime1 >= IMC_SPAM1TIME)
      c->spamtime1=-IMC_SPAM1TIME;
  }
  else
  {
    if (c->spamtime1<0)
      c->spamtime1++;
  }

  c->spamcounter1=0;
  if (c->spamtime1>0)
    imc_add_event(IMC_SPAM1INTERVAL, ev_spam1, c, 0);
  else if (c->spamtime1<0)
    imc_add_event(IMC_SPAM1INTERVAL, ev_spam1, c, 1);
}

/* handle a spam event for counter 2 */
void ev_spam2(void *data)
{
  imc_connect *c=(imc_connect *)data;

  if (c->spamcounter2 > IMC_SPAM2MAX)
  {
    if (c->spamtime2 < 0 || ++c->spamtime2 >= IMC_SPAM2TIME)
      c->spamtime2=-IMC_SPAM2TIME;
  }
  else
    if (c->spamtime2<0)
      c->spamtime2++;

  c->spamcounter2=0;
  if (c->spamtime2>0)
    imc_add_event(IMC_SPAM1INTERVAL, ev_spam2, c, 0);
  else if (c->spamtime2<0)
    imc_add_event(IMC_SPAM1INTERVAL, ev_spam2, c, 1);
}