/*
 * Copyright 1988, 1989, 1990, 1991 Massachusetts Institute of Technology
 */

/* rpdutil.c */
/* Opening an RPD now presumes that switchers are already
   open */
#include "structs.h"
/* -------------------------------------------------------------- */
RPD_ptr
rpdutil_open( devtbl, num )

	VCONF_DEVTABLE *devtbl;
	int		num;
{
  char *calloc(), *cur_point, *strchr();
  RPD_ptr	rpd;
  char devport[64], chr;
  int res, i, j, switchnum, channum, num_outputs, *outputs, (*proc)();
  int success;
  
  if( !devtbl || num<0 || (num+1)>devtbl->ndevs )
    {
      bsdsyslog(LOG_NOTICE,
		"rpdutil_open: given bad devtable or number, line %d\n", num);
      return NULL;
    }
  if( devtbl->dev[num].type != RPD_DEV )
    {
      bsdsyslog(LOG_NOTICE,
		"rpdutil_open: device is not an RPD, line %d\n", num);
      return NULL;
    }
  
  rpd = (RPD_ptr) calloc( 1, sizeof(RPD) );
  if( rpd == NULL ) {
    bsdsyslog(LOG_CRIT,"rpdutil_open: failed calloc(), line %d\n",num);
    return NULL;
  }
  rpd->extension = (char *)NULL;
  rpd->freelist = (RPD_SEGLIST *)NULL;
  rpd->usedlist = (RPD_SEGLIST *)NULL;

  cur_point = devtbl->dev[num].tty;
  rpd->devnum = atoi(cur_point);
  if ((cur_point = strchr(cur_point, ':') + 1) == (char *)1) {
    rpd->devnum = -1;
    cur_point = devtbl->dev[num].tty;
  }

#ifdef TRAP_BUG
  bsdsyslog(LOG_INFO, "parsed device number");
#endif /* TRAP_BUG */

  strcpy(devport,"/dev/");
  strcat(devport,cur_point);
  
  strcpy(rpd->devname,devtbl->dev[num].name);
  strcpy(rpd->model,devtbl->dev[num].model);
  strcpy(rpd->volume,"???");
  
  res = vconf_find_model( rpd->model );
  proc = vconf_find_set_func(rpd->model);
  switch( res )
    {
    case NETRPD_RECORD:
    case NETRPD:
      strcpy(rpd->port, devtbl->dev[num].tty);
      rpd->serv = devtbl->dev[num].server;
      rpd->fd = GConnection(rpd->serv);
      sscanf(devtbl->dev[num].channel,"%*d-%d",&rpd->volnum);
      break;
    case -1:
      bsdsyslog(LOG_NOTICE,"rpdutil_open: bad model <%s>\n",rpd->model);
      free((char *)rpd);
      return NULL;
    default:
      rpd->fd = -1;
      strcpy(rpd->port, devport);
      if (rpd->devnum != -1) {
	for (i = 0; i < num_players; i++) {
	  if (!strcmp(devport, players[i]->port)) {
	    rpd->fd = players[i]->fd;
	  }
	}
      }
      else rpd->devnum = 0;
      if (rpd->fd == -1) {
	if( (rpd->fd = 
	     ttyutil_open(
			  devport,
			  devtbl->dev[num].baud,
			  devtbl->dev[num].parity) ) == -1 ) {
	  bsdsyslog(LOG_NOTICE,
  "rpdutil_open: unable to open RPD device on tty %s, line %d\n",devport, num);
	  free( (char *) rpd );
	  return NULL;
	}
      }
      break;
    }
#ifdef NEW_VERSION
  rpd->max_errors = 1;   /* default; drivers can raise, up to MAX_ERRORS */
#endif /* NEW_VERSION */
  rpd->devtype = res;
  rpd->proddev = rpd->reset = rpd->cmd = rpd->search = rpd->segplay =
    rpd->getframe = rpd->getspeed = rpd->varspeed = rpd->jog =
      rpd->record = rpd->recordsetup = rpd->recordlen = rpd->buildfreelist =
	(METHOD)NULL;
  if (proc == NULL) {
    bsdsyslog(LOG_CRIT, "%s: %s: no setfuncs!", rpd->devname, rpd->port);
    free((char *)rpd);
    return NULL;
  }
  (*proc)(rpd);
  rpd->frame = -1;
  rpd->speed = 0;
  rpd->pseudo_out = -1;
  rpd->num_errors = 0;
  rpd->avail = -1;
  if (rpd->proddev) success = (*rpd->proddev)(rpd);
  else success = (*rpd->reset)(rpd);
  if (success == -1) success = (*rpd->reset)(rpd);
  if (success != -1) {
    unlock_all();
    for ( i = 0; i < MAXSERVEROUTS; i++) {
      rpd->output_chans[i].switcher = -1;
      rpd->outputnum[i] = -1;
    }
    i = 0;
    cur_point = devtbl->dev[num].channel;
    do {
      if (i > MAXSERVEROUTS) break;
      chr = *cur_point;
      if (chr == 'i' || chr == 'o' || chr == 'I' || chr == 'O') {
	cur_point = strchr(cur_point, ':') + 1;
	switchnum = atoi(cur_point);
      }
      else switchnum = atoi(cur_point);
      cur_point = strchr(cur_point, '-') + 1;
      channum = atoi(cur_point);
      if (chr == 'i' || chr == 'I') {
	rpd->pseudo_out = channum;
/*	if (DEBUG) printf("Pseudo-output is %d\n",rpd->pseudo_out);*/
      }
      else {
	rpd->output_chans[i].switcher = switchnum;
	rpd->output_chans[i].channel = channum;
	outputs = NULL;
	newfindoutputnums(rpd->output_chans[i], &num_outputs, &outputs);
	for (j = 0; j < num_outputs; j++)
	  rpd->outputnum[outputs[j]] = i;
	if (outputs) free(outputs);
	/*  if (DEBUG) printf("Index %d, switch %d, chan %d, outputs %d\n",
	    i, switchnum,
	    channum, num_outputs);*/
	i++;
      }
    } while ((cur_point=strchr(cur_point,',') + 1) != (char *)1);
    return rpd;
  }
  else {
/*    bsdsyslog(LOG_NOTICE,"rpdutil_open: prod failed, line %d\n",num);*/
    rpdutil_close(rpd);
    return(NULL);
  }
}

/* -------------------------------------------------------------- */

RPD_ptr
rpdutil_close( rpd )

	RPD_ptr rpd;
{
  int res;

  res = vconf_find_model( rpd->model );
  if (res != NETRPD && res != NETRPD_RECORD) ttyutil_close( rpd->fd );
  recurse_free_seglist(rpd->freelist);
  recurse_free_seglist(rpd->usedlist);
  if( rpd->extension )
    free( (char *) rpd->extension );
  free( (char *) rpd );
  return NULL;
}

/* ------------------------------------------------------------------------ */

/* vid_alloc() function; allocates blocks of frames from the free list */

vid_alloc(rpd,frames)
     
     RPD_ptr	rpd;
     int	frames;
{
  int start;
  RPD_SEGLIST *fl;
  RPD_SEGLIST *prev;
  
  if( !rpd || frames > rpd->lastframe || frames < 1)
    {
      bsdsyslog(LOG_INFO,"valloc: %s: rpd is not configured correctly\n",
	      rpd->devname);
      return -1;
    }

  if (rpd->devtype == NETRPD_RECORD) {
    if (add_lock(rpd->serv) == -1) return(-1);  
    start = GAlloc(rpd->serv, rpd->volnum, frames);
    if (start < LOWEST_ERROR) return(start);
    if (start == GIO_ERROR) {
      bsdsyslog(LOG_NOTICE,"vid_alloc: GIO_ERROR: %s",rpd->devname);
      need_to_rebuild_list = 1;
      return -1;
    }
    return -1;
  }
  
  if (!rpd->freelist) {
    if ((*rpd->buildfreelist)(rpd) == -1) {
      bsdsyslog(LOG_NOTICE,"%s: %s: vid_alloc: couldn't build freelist");
      return(-1);
    }
  }

  if (!rpd->freelist) return(-1);

  for( prev=NULL, fl = rpd->freelist; fl; prev=fl, fl=fl->next )
    {
      if( fl->nframes >= frames )	/* big enough */
	{
	  if( fl->nframes == frames ) /* exactly */
	    {
	      if( prev )
		prev->next = fl->next;
	      else	/* must reset head of list */
		{
		  rpd->freelist = fl->next;
		}
	      start = fl->start;
	      free( (char *) fl );
	      return start;
	    }
	  else		/* allocate at head end */
	    {
	      start = fl->start;
	      fl->start += frames;
	      fl->nframes -= frames;
	      return start;
	    }
	}
    }
  
  /* if get to here, not enough room for block */
/*  if (DEBUG) printf("%s: %s: vid_alloc: not enough room in free list\n",
		    rpd->devname, rpd->port);*/
  return -1;
  
}

/* ------------------------------------------------------------------------ */

vid_allocatframe(rpd,startframe,frames)
     
     RPD_ptr	rpd;
     int	startframe,frames;
{
  int start;
  RPD_SEGLIST *fl, *f2;
  RPD_SEGLIST *prev;
  
  if (frames < 1) return -1;

  if( startframe + frames > rpd->lastframe ||
     startframe < 1 || start > rpd->lastframe)
    {
      bsdsyslog(LOG_INFO,"vallocatframe: %s: rpd is not configured correctly\n",
	      rpd->devname);
      return -1;
    }
  
  if (rpd->devtype == NETRPD_RECORD) {
    if (add_lock(rpd->serv) == -1) return(-1);  
    start = GAllocAtFrame(rpd->serv, rpd->volnum, startframe, frames);
    if (start < LOWEST_ERROR) return(start);
    if (start == GIO_ERROR) {
      bsdsyslog(LOG_NOTICE,"vid_allocatframe: GIO_ERROR: %s",rpd->devname);
      need_to_rebuild_list = 1;
      return -1;
    }
    return -1;
  }
  
  if (!rpd->freelist) {
    if ((*rpd->buildfreelist)(rpd) == -1) {
      bsdsyslog(LOG_NOTICE,
		"%s: %s: vid_allocatframe: couldn't build freelist");
      return(-1);
    }
  }

  if (!rpd->freelist) return(-1);

  for( prev=NULL, fl = rpd->freelist; fl; prev=fl, fl=fl->next )
    {
      if (startframe >= fl->start && startframe < fl->start + fl->nframes ) {
	if(fl->start + fl->nframes >= startframe + frames ) /* big enough */
	  {
	    if(fl->start == startframe && fl->nframes == frames ) /* exactly */
	      {
		if( prev )
		  prev->next = fl->next;
		else	/* must reset head of list */
		  {
		    rpd->freelist = fl->next;
		  }
		free( (char *) fl );
		return startframe;
	      }
	    else if (fl->start == startframe)	/* allocate at head end */
	      {
		fl->start += frames;
		fl->nframes -= frames;
		return startframe;
	      }
	    else if (fl->start + fl->nframes == startframe + frames) {
	      /* at tail*/
	      fl->nframes -= frames;
	      return startframe;
	    }
	    else {  /* FROM THE MIDDLE */
	      f2 = (RPD_SEGLIST *)calloc(1, sizeof(RPD_SEGLIST));
	      if (f2 == (RPD_SEGLIST *)NULL) return(-1);
	      f2->start = startframe+frames;
	      f2->nframes = fl->start + fl->nframes - (startframe+frames);
	      f2->next = fl->next;
	      fl->next = f2;
	      fl->nframes = startframe - fl->start;
	      return startframe;
	    }
	  }
      }
    }
  
  /* if get to here, not enough room for block */
/*if (DEBUG) printf("%s: %s: vid_allocatframe: not enough room in free list\n",
		    rpd->devname, rpd->port);*/
  return -1;
}

/* vfree() function */

vfree(rpd,start,frames)
     
     RPD_ptr	rpd;
     int	start;
     int	frames;
{
  RPD_SEGLIST *fl, *new;
  RPD_SEGLIST *prev, *temp;
  RPD_SEGLIST old;
  RPD_SEGLIST *op = &old;
  char *calloc();

  if( !rpd || frames > rpd->lastframe || frames < 1 || 
     start < 1 || start > rpd->lastframe )
    {
      bsdsyslog(LOG_NOTICE,"vfree: %s: bad parameters\n",
	      rpd->devname);
      return -1;
    }
  
  if (rpd->devtype == NETRPD_RECORD) {
    if (add_lock(rpd->serv) == -1) return(-1);  
    start = GFree(rpd->serv, rpd->volnum, start, frames);
    if (start < LOWEST_ERROR) return(0);
    if (start == GIO_ERROR) {
      bsdsyslog(LOG_NOTICE,"vfree: GIO_ERROR: %s",rpd->devname);
      need_to_rebuild_list = 1;
      return -1;
    }
    return -1;
  }
  
  if( !rpd->freelist )	/* make returned block the entire list */
    {
      fl = (RPD_SEGLIST *) calloc(1,sizeof(RPD_SEGLIST));
      fl->nframes = frames;
      fl->start = start;
      rpd->freelist = fl;
      return 0;
    }
  
  op->start = start;
  op->nframes = frames;
  
#define OVERLAPS(a,b) \
  ((((a)->start+(a)->nframes)<=(b)->start) ? 0 : \
   ((((b)->start+(b)->nframes)<=(a)->start) ? 0 : 1))
    
#define TOUCHES_HEAD(s,h) \
    ((((h)->start+(h)->nframes)==(s)->start) ? 1 : 0)
      
#define TOUCHES_TAIL(s,t) \
      ((((s)->start+(s)->nframes)==(t)->start) ? 1 : 0)
	
	for( prev=NULL, fl = rpd->freelist; fl; prev=fl, fl=fl->next )
	  {
	    /*
	      for each node, do:
	      
	      if block overlaps this one, reject
	      
	      if block fits before this one, 
	      (just check start; know that didn't overlap
	      previous, and that doesn't overlap current)
	      if it touches head of this one,
	      ADD to this one;
	      else
	      INSERT new node before.
	      (if no prev, make new head)
	      
	      else if it touches tail of this one,
	      if next 
	      if overlaps next, reject
	      ADD to this one;
	      if touches head of next,
	      JOIN next and this one.
	      else
	      ADD to this one
	      
	      else if no next,
	      (must be after)
	      INSERT new node after.
	      */
	    
	    if( OVERLAPS(fl,op) )
	      return -1;
	    
	    if( start < fl->start )
	      {
		if( TOUCHES_HEAD(fl,op) )
		  {
		    fl->nframes += frames;
		    fl->start = start;
		    return 0;
		  }
		else
		  {
		    new = (RPD_SEGLIST *)
		      calloc(1,sizeof(RPD_SEGLIST));
		    new->start = start;
		    new->nframes = frames;
		    new->next = fl;
		    
		    if( prev )
		      {
			prev->next = new;
		      }
		    else	/* make new head of free list */
		      {
			rpd->freelist = new;
		      }
		    return 0;
		  }
	      }
	    else if( TOUCHES_TAIL(fl,op) )
	      {
		if( fl->next )
		  {
		    if( OVERLAPS(fl->next,op) )
		      return -1;
		    fl->nframes += frames;
		    if( TOUCHES_HEAD(fl->next,op) )
		      {
			fl->nframes += fl->next->nframes;
			temp = fl->next;
			fl->next = fl->next->next;
			free( (char *) temp );
		      }
		    return 0;
		  }
		else
		  {
		    fl->nframes += frames;
		    return 0;
		  }
	      }
	    else if( !fl->next )
	      {
		new = (RPD_SEGLIST *)
		  calloc(1,sizeof(RPD_SEGLIST));
		new->start = start;
		new->nframes = frames;
		new->next = NULL;
		
		fl->next = new;
		return 0;
	      }
	  }
  return(0);
}

recurse_free_seglist(top_seglist)
     RPD_SEGLIST *top_seglist;
{
  if (top_seglist == (RPD_SEGLIST *)NULL) return;
  if (top_seglist->next != (RPD_SEGLIST *)NULL)
    recurse_free_seglist(top_seglist->next);
  free(top_seglist);
  return;
}

int used_segment(rpd, start, nframes)
     RPD_ptr rpd;
     int start, nframes;
{
  RPD_SEGLIST *temp_seglist, *cur_seglist;
  
  temp_seglist = (RPD_SEGLIST *)calloc(1, sizeof(RPD_SEGLIST));
  if (temp_seglist == (RPD_SEGLIST *)NULL) return(-1);
  
  temp_seglist->start = start;
  temp_seglist->nframes = nframes;
  temp_seglist->recorded = 1;
  temp_seglist->next = (RPD_SEGLIST *)NULL;

  if (rpd->usedlist == (RPD_SEGLIST *)NULL)
    rpd->usedlist = temp_seglist;
  else {
    cur_seglist = rpd->usedlist;
    while(cur_seglist->next != NULL)
      cur_seglist = cur_seglist->next;
    cur_seglist->next = temp_seglist;
  }
  return(0);
}

rpd_getspeed_f(rpd)
     RPD_ptr rpd;
{
  return(rpd->speed);
}
#ifndef rpd_getspeed
#define rpd_getspeed rpd_getspeed_f
#endif /* rpd_getspeed */
