/*
 * Copyright 1988, 1989, 1990, 1991 Massachusetts Institute of Technology
 */
#ifdef SEAMLESS   /* entire file in this #ifdef */
/* listfuncs.c */

#include "structs.h"
#include "seamless.h"

#define MALLOC(ptr, type, num, errmsg, retval) \
  if ((ptr = (type *) malloc( num * sizeof(type))) == NULL) {  \
    if (DEBUG) printf("malloc error: %s\n",errmsg);\
    return (retval);\
  }

#define ELAPSED(start,cur,elapsed) \
  elapsed->tv_sec = cur->tv_sec - start->tv_sec; \
  elapsed->tv_usec = cur->tv_usec - start->tv_usec; \
  if (elapsed->tv_usec < 0) { \
				printf ("."); \
				  elapsed->tv_usec += 1000000; \
				    elapsed->tv_sec--; }

#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#define PREROLL    5
#define POSTROLL   5
#define PRESWITCH  1
#define PRESEARCH  90
#define SETUP      100
#define FUDGE 	   4

#define SWITCH_RETRIES 5

#define FR_USEC (1000000 / 30)


command *compile_script();

extern Output listvolumeoutput();

List **lists;

int play[30];

seamless_ext_ctrl(cmd, data)
     int cmd;
     caddr_t data;
{
  switch (cmd) {
  case EXT_INIT:  /* Initialize */
    lists = (List **) calloc (sizeof (List *), MAXIMUM_LISTS);
    return 0;
    break;
  case EXT_PREBUILD:   /* Just before a volume list rebuild */
    return 0;
    break;
  case EXT_POSTBUILD:   /* Just after a volume list rebuild */
    return 0;
    break;
  case EXT_PERIODIC:       /* Every QUERYTIME seconds of idle time */
    return 0;
    break;
  case EXT_SHUTOFF:      /* Just before server shutoff */
    return 0;
    break;
  default:
    bsdsyslog(LOG_NOTICE, "seamless_ext_ctl: bad command: %d", cmd);
    return -1;
  }
}

seamless_ext_dofuncs(msgsock, hostcommand, clinum)
     int msgsock, clinum;
     Galatea_packet *hostcommand;
{
  /* Seamless extension switches on the last parameter in order to remain
     backward compatibility. */
  int cc;
  switch (hostcommand->parm[9]) {
  case G_Download:
    cc = DoDownload(msgsock, hostcommand, clinum);
    break;
  case G_PlayList:
    cc = DoPlayList(msgsock, hostcommand, clinum);
    break;
  case G_FreeList:
    cc = DoFreeList(msgsock, hostcommand, clinum);
    break;
  default:
    bsdsyslog(LOG_NOTICE,
	      "seamless_ext_dofuncs: ERROR: called with minor # %d\n",
		      hostcommand->parm[9]);
    return -1;
  }
  return cc;
}

DoPlayList(msgsock,hostcommand,i)
  int msgsock;
  Galatea_packet *hostcommand;
  int i;
{

  int rval;
  command *list;

  if (DEBUG) printf("DoPlayList %d called by client %d on connection %d\n",
		    hostcommand->parm[1],i,msgsock);

  list = compile_script (lists[hostcommand->parm[1]],i);
  runlist(msgsock, list, clients[i].output);
  free_list (list);
  return(-2);
}

  
DoFreeList(msgsock,hostcommand,i)
  int msgsock;
  Galatea_packet *hostcommand;
  int i;
{
  int rval;

  if (DEBUG) printf("DoFreeList %dcalled by client %d on connection %d\n", 
		    hostcommand->parm[1],i,msgsock);
  return(-2);
}

  
DoDownload(msgsock,hostcommand,i)
  int msgsock;
  Galatea_packet *hostcommand;
  int i;
{
  int script;
  int j;

  for (j = 0; j < 30; j++) 
    play[j] = -999;

  if (DEBUG) printf("DoDownLoad called by client %d on connection %d (%d)\n",
		    i,msgsock,clients[i].sock);

  if (DEBUG) printf("DoDownload: reading new LIST. . .\n");

  script = read_script(msgsock, hostcommand->parm[1], hostcommand->parm[2],i);

  if (script >= 0) {
    if (DEBUG) {
      printf("DoDownload: printing action script . . .\n");
      SeamlessPrintList(lists[script]);
    }
    if (DEBUG) printf("DoDownload: returning KEY %d . . .\n",script);
    return(sendint(msgsock,script));
  } else {
    if (DEBUG) printf("DoDownload: returning COULDNT_DO_FUNC . . .\n");
    return(sendint(msgsock,COULDNT_DO_FUNC));
  }
}

runlist (msg, list, output)
     int msg;
     command *list;
     int output;

{

  struct timeval *starttime;
  struct timeval *elapsedtime;
  struct timeval *curtime;
  struct timeval *waittime;
  fd_set *readfds;
  fd_set *temp;
  int netints[1], newspeed;
  register int swtch;
  command *tl = list;
  Output out;

  RPD_ptr rpd;
  INPUT_ptr input;
  SWTR_ptr swtr;

  MALLOC (starttime, struct timeval, 1, "runlist", -2);
  MALLOC (elapsedtime, struct timeval, 1, "elapsedlist", -2);
  MALLOC (curtime, struct timeval, 1, "curlist", -2);
  MALLOC (waittime, struct timeval, 1, "waitlist", -2);
  MALLOC (readfds, fd_set, 1, "runlist readfs:", -2);
  MALLOC (temp, fd_set, 1, "runlist temp:", -2);
  FD_ZERO(readfds);
  FD_SET(msg,readfds);
  
  /* set elapsed time to 0 */
  gettimeofday(starttime,0);
  while (tl->next != NULL) {
    tl = tl->next;
    /* copy the descriptor set so we don't trash it */
    bcopy( (char *) readfds, (char *) temp, sizeof(fd_set) );
    gettimeofday(curtime,0);
    ELAPSED(starttime,curtime,elapsedtime);
    ELAPSED(elapsedtime,(&(tl->time)),waittime);
    if (waittime->tv_sec < 0) {
      waittime->tv_sec = 0;
      waittime->tv_usec = 0;
    }
    
    if (DEBUG) {
      printf ("starting at %d.%d seconds -- ",
	      tl->time.tv_sec, tl->time.tv_usec);
      printf ("waiting for %d.%d seconds -- ",
	      waittime->tv_sec, waittime->tv_usec);
      fflush(stdout);
    }
    
    if(select(msg+1,temp,NULL,NULL,waittime)) {
      if (DEBUG) printf("runlist: interrupted by client\n");
      all_stop();
      return(NO_ERROR);
    } else {
      /* do the action */
      switch (tl->action) {

      case SEARCH:
	rpd = players[volumes[tl->volume].resource[tl->resource]];
	if ((*rpd->search)(rpd,tl->in_arg,0) == -1)
	  return(sendint(msg,COULDNT_SEARCH));
	if (DEBUG) printf ("searching %d",tl->in_arg);
	break;

      case ROLL:
	rpd = players[volumes[tl->volume].resource[tl->resource]];
	(*rpd->varspeed)(rpd,tl->spec_arg); 
	if (DEBUG) printf ("rolling %d speed %d",
			   tl->volume,tl->spec_arg);
	break;

      case RELEASE:
	rpd = players[volumes[tl->volume].resource[tl->resource]];
	(*rpd->varspeed)(rpd,STOP);
	if (DEBUG) printf ("releasing %d",tl->volume);
	break;

      case SWITCH:
	out = listvolumeoutput (tl->in_arg, tl->out_arg, output);
	if (tl->spec_arg) {
	  for (swtch = 0; 
	       swtch < SWITCH_RETRIES &&
	       (doswitchtree(out,tl->spec_arg,output) < 0);
	       swtch++);
	  if (DEBUG) 
	    printf ("\n	switching switcher:%d channel:%d mask:%d",
		    out.switcher, out.channel,
			     tl->spec_arg);
	} else if (DEBUG) printf ("omitting switch...");
	break;
      default:
	if (DEBUG) printf("bad action in timing list: %d\n",tl->action);
	break;
      }

      if (DEBUG) {
	gettimeofday (curtime,0); 
	ELAPSED (starttime,curtime,curtime);
	printf (" at %d %d\n", curtime->tv_sec,curtime->tv_usec );
      }
    }
  }
  return(NO_ERROR);
}  

free_list (list)
     command *list;

{
  if (list == NULL) return;
  free_list (list->next);
  free(list);
}

void print_timing_list (list)
     command *list;

{

  command *cmds = list->next;
  printf ("Print TIMING LIST...\n");
  while (cmds != NULL) {
    switch (cmds->action) {
    case ROLL:
      printf ("%5d   ROLL    %5d %5d   %5d %5d %5d\n",
	      cmds->frame, cmds->volume, cmds->resource, 
	      cmds->in_arg, cmds->out_arg, cmds->spec_arg);
      break;
    case RELEASE:
      printf ("%5d   RELEASE %5d %5d   %5d %5d %5d\n",
	      cmds->frame, cmds->volume, cmds->resource, 
	      cmds->in_arg, cmds->out_arg, cmds->spec_arg);
      break;
    case SEARCH:
      printf ("%5d   SEARCH  %5d %5d   %5d\n",
	      cmds->frame, cmds->volume, cmds->resource, 
	      cmds->in_arg);
      break;
    case SWITCH:
      printf ("%5d   SWITCH  %5d %5d ->%5d %5d:%5d\n",
	      cmds->frame, cmds->volume, cmds->resource, 
	      cmds->in_arg, cmds->out_arg, cmds->spec_arg);
      break;
    }
    cmds = cmds->next;
  }
}


command *compile_script (script,i)
     List *script;
     int i;

{
  
  Clip *c = script->clips;
  Swtch *s = script->switches;
  command *list = (command *) calloc (1,sizeof (command));

  while (c != NULL) {
    assign_clip (list, c, i);
    c = c->next;
  }

  while (s != NULL) {
    assign_switch (list, s, i);
    s = s->next;
  }
  if (DEBUG) print_timing_list (list);
  return (list);
}

assign_clip (l,s,i)
     command *l;
     Clip *s;
     int i;

{
  command *new = (command *) calloc (1,sizeof (command));
  int res, assigned = 0;

  s->volume = match_client_volume (i,s->volumename);    
  
  for (res = 0; !assigned && res < volumes[s->volume].num_resources; res++)
    switch (volumes[s->volume].type) {
    case INPUT_DEV:
      s->resource = 0;
      assigned = 1;
      return;
      break;
    case RPD_DEV:
      if (s->frame - PRESEARCH > play[volumes[s->volume].resource[res]]) {
	assigned = 1;
	s->resource = res;
	play[volumes[s->volume].resource[res]] = 
	  s->frame + POSTROLL + (s->out - s->in) * 30 / s->speed;
      }
      break;
    default:
      printf ("Bad type\n");      
    }

  if (!assigned) {
    printf ("OOPS. out of resources!!! picking 0.");
    s->resource = 0;
  }

  new->frame = s->frame - PREROLL;
  new->action = ROLL;
  new->spec_arg = s->speed;
  new->in_arg = s->in - PREROLL * s->speed / 30;
  new->out_arg = s->out;
  new->volume = s->volume;
  new->resource = s->resource;
  frame_to_time (new->frame + SETUP, &new->time);

  add_to_list (l,new);

  new = (command *) calloc (1,sizeof (command));

  new->frame = s->frame + (s->out - s->in + POSTROLL) * 30 / s->speed;
  new->action = RELEASE;
  new->spec_arg = s->speed;
  new->out_arg = s->out + POSTROLL * s->speed / 30;
  new->volume = s->volume;
  new->resource = s->resource;
  frame_to_time (new->frame + SETUP, &new->time);

  add_to_list (l,new);

  new = (command *) calloc (1,sizeof (command));

  new->frame = s->frame - PRESEARCH;
  new->action = SEARCH;
  new->in_arg = s->in - (PREROLL + FUDGE) * s->speed / 30;
  new->volume = s->volume;
  new->resource = s->resource;
  frame_to_time (new->frame + SETUP, &new->time);

  add_to_list (l,new);
}


assign_switch (l, s)
     command *l;
     Swtch *s;

{
  command *new = (command *) calloc (1,sizeof (command));

  new->frame = s->frame - PRESWITCH;
  new->spec_arg = s->mask;
  new->action = SWITCH;
  frame_to_time (new->frame + SETUP, &new->time);
  
  if (s->in != NULL) {
    new->volume = s->in->volume;
    new->resource = s->in->resource;
  }

  if (s->out != NULL) {
    new->in_arg = s->out->volume;
    new->out_arg = s->out->resource;
  }

  add_to_list (l, new);
}


int read_script(msgsock, nc, ns,i)
     int msgsock;
     int nc, ns,i;

{
  int ints[5], netints[5];
  List *list;
  int ln;

  ln = initialize_list (i);
  if (ln < 0) return (-1);
  
  list = lists[ln];
  
  printf ("downloading list of %d, %d\n",nc,ns);
  /* begin by reading the clips!!! */
  while (list->numClips < nc) {
    serv_full_read(msgsock,netints,sizeof(int)*5,MAX_BLOCK);
    nettohostcom(netints,ints,5);
    if (ints[1] > LOWEST_ERROR) ints[1] = 0;
    SeamlessAddClip ( list,
		      SeamlessCreateServerClip (ints[0], 
						volumes[ints[1]].volume,
						ints[1], -1, 
						ints[2], ints[3], ints[4]));
  }
  
  while (list->numSwitches < ns) {
    serv_full_read(msgsock,netints,sizeof(int)*4, MAX_BLOCK);
    nettohostcom(netints,ints,4);
    SeamlessAddSwitch (list,
		       SeamlessCreateSwitch (ints[0], 
					     SeamlessFindClip (list, ints[1]),
					     SeamlessFindClip (list, ints[2]),
					     ints[3]));
  }
  return (ln);
}


all_stop()
     
{
  int i;  
  for (i = 0; i < num_players; i++) 
    if (players[i] != NULL) {
      (*players[i]->varspeed)(players[i],STOP);
    }
}


/* Hal's hacked volumeoutput function which does NOT rely on the Galatea notion
 * of current resource. Important for precalculated lists 
 */

Output listvolumeoutput(whichvol, whichres, output)
     int whichvol, whichres;
     int output;

{

  RPD_ptr rpd;
  INPUT_ptr inputter;
  
 if (DEBUG)
   printf ("\n	Volume %d, %d on output %d ",whichvol, whichres, output);

  if (volumes[whichvol].type == RPD_DEV) {
    rpd = players[volumes[whichvol].resource[whichres]];
    return(rpd->output_chans[rpd->outputnum[output]]);
  }
  if (volumes[whichvol].type == INPUT_DEV) {
    inputter = 
      inputs[volumes[whichvol].resource[whichres]];
    return(inputter->output_chans[inputter->outputnum[output]]);
  }
}


int frame_to_time(frame,time)
     int frame;
     struct timeval *time;
{
  time->tv_sec = frame/30;
  time->tv_usec = (int) (((float)frame/30.0 - (float)time->tv_sec)
			 * 30 * FR_USEC);
}


int find_free_list()

{
  int i;

  for (i = 0; i < MAXIMUM_LISTS; i++)
    if (lists[i] == NULL) return i;

  return -1;
}


int add_to_list (list, elem)
     command *list, *elem;

{
  command *l = list->next, *p = list;

  if (list->next == NULL) {
    list->next = elem;
    elem->next = NULL;
    return (0);
  }

  while (l->frame <= elem->frame) {
    p = l;
    l = l->next;
    if (l == NULL) break;
  }
  elem->next = l;
  p->next = elem;
}

print_list (list)
     command *list;

{
  command *l = list->next;
  printf ("PRINT TIMING LIST\n");

  while (l != NULL) {
    printf ("%d\t:",l->frame);
    switch (l->action) {
    case SEARCH:
      printf ("SEARCH	%-3d %-3d %-5d\n",
	      l->volume, l->resource, l->in_arg);
      break;
    case SWITCH:
      printf ("SWITCH	%-3d %-3d %-5d\n",
	      l->volume, l->resource, l->spec_arg);
      printf ("	  (rev)	%-3d %-3d %-5d\n",
	      l->in_arg, l->out_arg, l->spec_arg);
      break;
    case ROLL:
      printf ("ROLL	%-3d %-3d %-5d %-5d %-5d\n",
	      l->volume, l->resource, l->in_arg, l->out_arg, l->spec_arg);
      break;
    case RELEASE:
      printf ("REL	%-3d %-3d %-5d %-5d %-5d\n",
	      l->volume, l->resource, l->in_arg, l->out_arg, l->spec_arg);
      break;
    default:
      printf ("BAD DATA IN LIST!!!\n");
    }
    l = l->next;
  }
}

int match_client_volume (i, vol)
     int i;
     char *vol;

{
  int j;

  for (j = 0; j < num_volumes; j++) 
    if (!strcmp (vol,volumes[j].volume) &&
	(clients[i].output == volumes[j].output))
      return (j);

  return (-1);
}

int initialize_list (client_num)

{
  int i;
  
  for (i = 0; i < MAXIMUM_LISTS; i++) 
    if (lists[i] == NULL) {
      lists[i] = SeamlessCreateList();
      lists[i]->key = client_num;
      return (i);
    }
  return (-1);
}

#endif /* SEAMLESS */




