#include "snd.h"

#ifndef NEXT

#if defined(SGI) || defined(SCO5) || defined(UW2)
  #include <dlfcn.h>
#endif

#if defined(LINUX) || defined(SOLARIS)
  /* the dl library exists in Linux, but the header file seems to be missing */
  extern void *dlopen(const char *, int);
  extern void *dlsym(void *, const char *);
  extern int dlclose(void *);
  #define RTLD_LAZY	1
#endif

#if defined(BEOS)
  /* the dl library exists in BeOS, but the header file seems to be missing */
  void *dlopen(const char * hi, int ho) {return(NULL);}
  void *dlsym(void * hi, const char *ho) {return(NULL);}
  int dlclose(void *hi) {return(0);}
  #define RTLD_LAZY	1
#endif

/* user functions are of the form void *func(void *arg) and must be located
 * in a shared object file (for dlopen/dlsym). 
 */

typedef void *ufun(void *ptr);
static ufun **ufuns = NULL;
static char **ufun_names = NULL;
static char **ufun_libraries = NULL;
static void **ufun_handles = NULL;
static int ufun_size = 0;
static int ufun_ctr = -1;

int snd_load_ufun(char *library, char *fun)
{
  /* the documentation leaves me worried that I better not close the shared object files,
   * so, I'll keep track of which ones have been opened already in ufun_libraries and
   * the associated handles in ufun_handles.
   *
   * returns: -1 = no library found (dlopen failed)
   *          -2 = no function found (dlsym failed)
   *           0 = ok
   */
  void *handle;
  int len,i;
  if (!ufuns)
    {
      ufuns = (ufun **)calloc(8,sizeof(ufun *));
      ufun_names = (char **)calloc(8,sizeof(char *));
      ufun_libraries = (char **)calloc(8,sizeof(char *));
      ufun_handles = (void **)calloc(8,sizeof(void *));
      ufun_size = 8;
    }
  ufun_ctr++;
  if (ufun_ctr >= ufun_size)
    {
      len = ufun_size;
      ufun_size += 8;
      ufuns = (ufun **)realloc(ufuns,ufun_size*sizeof(ufun *));
      ufun_names = (char **)realloc(ufun_names,ufun_size*sizeof(char *));
      ufun_libraries = (char **)realloc(ufun_libraries,ufun_size*sizeof(char *));
      ufun_handles = (void **)realloc(ufun_handles,ufun_size*sizeof(void *));
      for (i=len;i<ufun_size;i++)
	{
	  ufuns[i] = NULL;
	  ufun_names[i] = NULL;
	  ufun_libraries[i] = NULL;
	  ufun_handles[i] = NULL;
	}
    }
  handle = NULL;
  for (i=0;i<ufun_ctr;i++)
    {
      if ((ufun_libraries[i]) && (strcmp(library,ufun_libraries[i]) == 0)) {handle = ufun_handles[i]; break;}
    }
  if (!handle)
    { /* library not opened yet */
      len = strlen(library) + 1;
      ufun_libraries[ufun_ctr] = (char *)calloc(len,sizeof(char));
      strcpy(ufun_libraries[ufun_ctr],library);
      ufun_handles[ufun_ctr] = dlopen(library,RTLD_LAZY);
      handle = ufun_handles[ufun_ctr];
    }
  if (handle) 
    {
      ufuns[ufun_ctr] = (ufun *)dlsym(handle,fun);
      if (!ufuns[ufun_ctr]) 
	{ /* can't find function */
	  ufun_ctr--;
	  return(-2);
	}
      else 
	{
	  len = strlen(fun)+1;
	  ufun_names[ufun_ctr] = (char *)calloc(len,sizeof(char));
	  strcpy(ufun_names[ufun_ctr],fun);
	  return(0);
	}
    }
  else 
    {
      ufun_ctr--;
      return(-1);
    }
}

static char ufun_msg[128];
static char ufun_msgs[128];

static float get_next_sample(void *ptr) {return(next_sample((snd_fd *)ptr));}
static float get_previous_sample(void *ptr) {return(previous_sample((snd_fd *)ptr));}
static int get_read_eof(void *ptr) {return(read_sample_eof((snd_fd *)ptr));}

int invoke_ufun_with_ptr(chan_info *cp, void *ptr, float *args, int nargs, int count, int reg)
{
  int i,j,res_op,free_data,plausible_srate,do_list,resctr = 0;
  float ymin,ymax,val;
  sync_info *si;
  ufun_info *up;
  char *tempfile;
  snd_fd **sfs;
  ufun_to_snd *res;
  ufun_list_to_snd *reslst = NULL;
  snd_to_ufun *pars;
  snd_info *sp;
  snd_state *ss;
  axis_info *ap;
  chan_info *ncp;
  int *data = NULL;
  ufun *fp = (ufun *)ptr;
  int **cd;
  si = NULL;
  free_data = 1;
  sp = cp->sound;
  ss = cp->state;
  /* set up all sync'd chans etc */
  if (reg) 
    {
      if (selection_is_ours()) si = region_sync(0); /* regions[count] here? */
    }
  else
    {
      if (sp->syncing) 
	si = snd_sync(ss); 
      else si = make_simple_sync(cp,cp->cursor);
    }
  if (!si) return(0);
  plausible_srate = snd_SRATE(si->cps[0]);
  pars = (snd_to_ufun *)calloc(1,sizeof(snd_to_ufun));
  pars->snd_next_sample = get_next_sample;
  pars->snd_previous_sample = get_previous_sample;
  pars->snd_eof = get_read_eof;
  pars->chans = si->chans;
  pars->count = count;
  pars->args = args;
  pars->nargs = nargs;
  sfs = (snd_fd **)calloc(si->chans,sizeof(snd_fd *));
  pars->wbegs = (int *)calloc(si->chans,sizeof(int));
  pars->wends = (int *)calloc(si->chans,sizeof(int));
  pars->wcursors = (int *)calloc(si->chans,sizeof(int));
  pars->srates = (int *)calloc(si->chans,sizeof(int));
  for (i=0;i<si->chans;i++) 
    {
      ncp = si->cps[i];
      ap = ncp->axis;
      if (reg)
	sfs[i] = init_sample_read(si->begs[i],ncp,READ_FORWARD);
      else sfs[i] = init_sample_read(ap->losamp,ncp,READ_FORWARD);
      pars->wcursors[i] = ncp->cursor;
      pars->wbegs[i] = ap->losamp;
      pars->wends[i] = ap->hisamp;
      pars->srates[i] = plausible_srate;
    }
  pars->snd_ptrs = (void **)sfs;
  res = (ufun_to_snd *)((*fp)((void *)pars)); /* call ufun */
  /* res might be either ufun_to_snd pointer or ufun_list_to_snd */
  free(pars->wbegs);
  free(pars->wends);
  free(pars->wcursors);
  free(pars->srates);
  free(pars);
  pars = NULL;
  for (i=0;i<si->chans;i++) free_snd_fd(sfs[i]);
  free(sfs);
  sfs = NULL;
  res_op = 0;
  if (res)
    {
      do_list = (res->result_type == SND_UFUN_LIST);
      if (do_list) 
	{
	  reslst = (ufun_list_to_snd *)res;
	  resctr = 0;
	  res = reslst->ops[0];
	}
      while (res)
	{
	  res_op = res->op;
	  if ((res->op == SND_REGIONIFY_RESULTS) || (res->op == SND_CHANGE_AND_REGIONIFY_RESULTS) ||
	      (res->op == SND_INSERT_AND_REGIONIFY_RESULTS) || (res->op == SND_ADD_AND_REGIONIFY_RESULTS))
	    {
	      if (res->result_type == UFUN_ARRAY)
		receive_ufun_selection(res->chans,plausible_srate,res->results,res->lens);
	      else receive_ufun_file(res->filename);
	    }
	  if (res->result_type == UFUN_FILE) free_data = 0;
	  switch (res->op)
	    {
	    case SND_NO_OP: 
	    case SND_REGIONIFY_RESULTS:
	      break;
	    case SND_REPORT_RESULTS:
	      /* results are in the array of float arrays res->results, res->lens = length of each, res->chans = size of outer array */
	      if (res->result_type == UFUN_FILE) 
		{
		  snd_printf(ss,"can't report ufun file results");
		}
	      else
		{
		  sp = NULL;
		  for (i=0;i<si->chans;i++)
		    {
		      ncp = si->cps[i];
		      if (sp != ncp->sound)
			{
			  if (sp) report_in_minibuffer(sp,ufun_msgs);
			  ufun_msgs[0] = '\0';
			  sp = ncp->sound;
			}
		      else strcat(ufun_msgs,"|");
		      sprintf(ufun_msg,"%d: ",ncp->chan);
		      strcat(ufun_msgs,ufun_msg);
		      for (j=0;j<res->lens[i];j++)
			{
			  sprintf(ufun_msg,"%.3f ",res->results[i][j]);
			  strcat(ufun_msgs,ufun_msg);
			}
		    }
		}
	      if (sp) report_in_minibuffer(sp,ufun_msgs);
	      break;
	    case SND_CHANGE_RESULTS:
	    case SND_CHANGE_AND_REGIONIFY_RESULTS:
	      for (i=0;i<si->chans;i++)
		{
		  ncp = si->cps[i];
		  if (res->result_type == UFUN_ARRAY)
		    {
		      data = (int *)calloc(res->lens[i],sizeof(int));
		      for (j=0;j<res->lens[i];j++) data[j] = res->results[i][j]*clm_sndfix;
		      change_samples(res->begs[i],res->lens[i],data,ncp,LOCK_MIXES);
		      free(data);
		    }
		  else
		    {
		      tempfile = tempnam(ss->temp_dir,"snd_");
		      copy_file(res->filename,tempfile,sp);
		      file_change_samples(res->begs[i],res->lens[i],tempfile,ncp,i,DELETE_ME,LOCK_MIXES);
		    }
		  check_for_first_edit(ncp);
		  display_channel_data(ncp,ncp->sound,ncp->state);
		}
	      break;
	    case SND_INSERT_RESULTS:
	    case SND_INSERT_AND_REGIONIFY_RESULTS:
	      for (i=0;i<si->chans;i++)
		{
		  ncp = si->cps[i];
		  if (res->result_type == UFUN_ARRAY)
		    {
		      data = (int *)calloc(res->lens[i],sizeof(int));
		      for (j=0;j<res->lens[i];j++) data[j] = res->results[i][j]*clm_sndfix;
		      insert_samples(res->begs[i],res->lens[i],data,ncp);
		      free(data);
		    }
		  else
		    {
		      tempfile = tempnam(ss->temp_dir,"snd_");
		      copy_file(res->filename,tempfile,sp);
		      file_insert_samples(res->begs[i],res->lens[i],tempfile,ncp,i,DELETE_ME);
		    }
		  check_for_first_edit(ncp);
		  display_channel_data(ncp,ncp->sound,ncp->state);
		}
	      break;
	    case SND_ADD_RESULTS:
	    case SND_ADD_AND_REGIONIFY_RESULTS:
	      /* here we're going to assume the mix is at the same loc, same len, same type across entire set */
	      /* the mixing side wants int data not float, so we convert here */
	      cd = (int **)calloc(res->chans,sizeof(int *));
	      for (i=0;i<res->chans;i++)
		{
		  data = (int *)calloc(res->lens[i],sizeof(int));
		  cd[i] = data;
		  for (j=0;j<res->lens[i];j++) data[j] = (int)(res->results[i][j] * clm_sndfix);
		}
	      if (res->result_type == UFUN_ARRAY)
		mix_array(res->begs[0],res->lens[0],cd,si->cps,res->chans,si->chans,FROM_UFUN,snd_SRATE(si->cps[0]));
	      mix_file(res->begs[0],res->lens[0],res->filename,si->cps,si->chans,FROM_UFUN);
	      for (i=0;i<res->chans;i++) free(cd[i]);
	      free(cd);
	      cd = NULL;
	      data = NULL;
	      break;
	    case SND_DELETE:
	      for (i=0;i<si->chans;i++)
		{
		  ncp = si->cps[i];
		  delete_samples(res->begs[i],res->lens[i],ncp);
		  check_for_first_edit(ncp);
		  display_channel_data(ncp,ncp->sound,ncp->state);
		}
	      break;
	    case SND_DISPLAY_RESULTS:
	      /* display results via cp->ui pointer -- this is deleted after window update */
	      /* make_axis_info here also */
	      if (res->result_type == UFUN_FILE)
		{
		  snd_printf(ss,"can't display ufun file results"); 
		}
	      else
		{
		  for (i=0;i<si->chans;i++)
		    {
		      ncp = si->cps[i];
		      if ((up = (ufun_info *)(ncp->ui)))
			{
			  if (up->data) free(up->data);
			  up->data = NULL;
			}
		      else 
			{
			  up = (ufun_info *)calloc(1,sizeof(ufun_info));
			  ncp->ui = up;
			  up->type = 0;
			}
		      up->data = res->results[i];
		      up->length = res->lens[i];
		      up->peaks_ok = res->peaks_ok;
		      up->scaler = res->peak_scaler;
		      free_data = 0; /* will be freed later (see above) */
		      ncp->ufuning = 1;
		      /* now decide about y axis bounds */
		      ymin = up->data[0]; 
		      ymax = up->data[0]; 
		      for (j=1;j<up->length;j++)
			{
			  val = up->data[j];
			  if (ymin > val) ymin = val;
			  if (ymax < val) ymax = val;
			}
		      /* if (up->axis) up->axis = free_axis_info(up->axis); */
		      up->axis = make_axis_info(ncp,0.0,1.0,ymin,ymax,res->xlabel,0.0,1.0,ymin,ymax,up->axis); /* copies label */
		      display_channel_data(ncp,ncp->sound,ncp->state);
		    }
		}
	      break;
	    default: break;
	    }
	  if ((free_data) && (res->results)) {for (i=0;i<res->chans;i++) if (res->results[i]) free(res->results[i]);}
	  if (res->xlabel) free(res->xlabel);
	  if (res->results) free(res->results);
	  if (res->lens) free(res->lens);
	  if (res->begs) free(res->begs);
	  free(res);
	  res = NULL;
	  if (do_list) 
	    {
	      resctr++;
	      if (resctr < reslst->len) res = reslst->ops[resctr];
	    }
	}
      if ((do_list) && (reslst)) free(reslst);
    }
  if (si) free_sync_info(si);
  return(res_op);
}
  
void *find_ufun(snd_info *sp, char *fun)
{
  int i;
  ufun *fp;
  fp = NULL;
  for (i=0;i<=ufun_ctr;i++) /* ctr preincremented */
    {
      if (strcmp(fun,ufun_names[i]) == 0) {fp=ufuns[i]; break;}
    }
  if (!fp)
    {
      sprintf(ufun_msg,snd_string_cant_find,fun);
      report_in_minibuffer(sp,ufun_msg);
      return(NULL);
    }
  return((void *)fp);
}

ufun_info *free_ufun_info(chan_info *cp)
{
  ufun_info *up;
  up = (ufun_info *)(cp->ui);
  if (up)
    {
      if (up->axis) free_axis_info(up->axis);
      if (up->data) free(up->data);
      free(up);
    }
  return(NULL);
}

void save_ufun_state(snd_state *ss, int fd)
{
  int i;
  char *pbuf;
  if (ufun_ctr >= 0)
    {
      pbuf = (char *)calloc(512,sizeof(char));
      for (i=0;i<=ufun_ctr;i++) 
	{
	  if (ufun_names[i])
	    {
	      sprintf(pbuf,"(load-function %s %s)\n",ufun_libraries[i],ufun_names[i]);
	      write(fd,pbuf,strlen(pbuf));
	    }
	}
      free(pbuf);
    }
}

#else
/* NeXT stubs */
int snd_load_ufun(char *library, char *fun) {}
int invoke_ufun_with_ptr(chan_info *cp, void *ptr, float *args, int nargs, int count, int reg) {}
ufun_info *free_ufun_info(chan_info *cp) {}
void *find_ufun(snd_info *sp, char *fun) {}
void save_ufun_state(snd_state *ss, int fd) {}
#endif
