#include "snd.h"
#include <errno.h>

/* extracted from cmus.c and changed slightly for Snd */

static void c_io_bufclr (int *io, int *datai, int beg)
{
  int i,k,end;
  int *j;
  if (io[io_dats+c_aref_block] == 0) clm_printf("attempt to clear deallocated IO buffer");
  else
    {
      end=io[io_bufsiz];
      for (k=0;k<io[io_chans];k++)
	{
	  j=((int *)datai[io[io_dats+c_aref_block]+k]);
	  if (j) {for (i=beg; i<end;i++) j[i]=0;}
	}
    }
}

#if 0
void print_io(int *io, int *datai)
{
  fprintf(stderr,"fd: %d, chans: %d, size: %d, beg: %d, end: %d, bufsiz: %d, data_start: %d, data_end: %d\n",
	 io[io_fd],io[io_chans],io[io_size],io[io_beg],io[io_end],io[io_bufsiz],io[io_data_start],io[io_data_end]);
  fprintf(stderr,"index: %d, dir: %d, loc: %d, hdr: %d, incr: %d, dats: %d, dats[0]: %d\n",
	 io[io_open_index],io[io_dir],io[io_loc],io[io_hdr_end],io[io_incr],io[io_dats],datai[io[io_dats]]);
}
#endif

void clm_file_reset(int loc0, int *io, int *datai)
{
  /* called when loc is outside the current in-core frame for the file pointed to by io */
  /* equivalent to the io.lisp function file-check + read-in */

  int file_end,bytes,loc;
  int bufend,filbytes;
  char *str;
  loc = loc0;
  if (io[io_dir] != io_in_f) 
    {
      if ((io[io_fd] != cl_false) &&
	  (io[io_data_end] != 0) &&
	  (io[io_data_start] != io[io_data_end]))
	{
	  if (io[io_data_end] > io[io_bufsiz])
	    {
	      str=(char *)calloc(256,sizeof(char));
	      sprintf(str,"data end indication in IO buffer is too big: %d > %d",io[io_data_end],io[io_bufsiz]);
	      clm_printf(str);
	      free(str);
	    }
	  else
	    {
	      clm_seek(io[io_fd],io[io_hdr_end]+(2*io[io_chans]*(io[io_beg]+io[io_data_start])),0);
	      if ((io[io_data_start] < 0) || (io[io_data_end] < 0)) 
		{
		  clm_printf("file buffer index is negative! -- will try to fix it...");
		  if (io[io_data_start] < 0) io[io_data_start] = 0; else io[io_data_end] = 0;
		}
	      clm_write(io[io_fd],io[io_data_start],io[io_data_end],io[io_chans],(int **)(datai+io[io_dats+c_aref_block]));
	      io[io_data_start] = io[io_data_end];
	      if (io[io_size] < (io[io_beg] + io[io_data_end])) io[io_size] = (io[io_beg] + 1 + io[io_data_end]);
	    }
	}
    }
  if ((loc < io[io_beg]) && ((loc + (int)(.9*io[io_bufsiz])) > io[io_beg]))
    {
      if ((loc + 10) > io[io_beg]) loc -= (int)(.75*io[io_bufsiz]);
      if (loc < 0) loc = 0;
      if (io[io_chans] == 1) loc = (2 * (int)(loc / 2));
    }
  file_end = io[io_size];
  bytes = file_end - loc;
  if (bytes > io[io_bufsiz]) bytes=io[io_bufsiz];
  if (bytes < 0)                   /* tried to access beyond current end of file */
    {
      if (io[io_dir] == io_in_f) {io[io_beg]=loc; c_io_bufclr(io,datai,0);} /* different from CLM */
      else
	{
	  c_io_bufclr(io,datai,0);
	  bytes = io[io_bufsiz];
	  io[io_data_start] = 0;
	  io[io_data_end] = 0;
	  clm_seek(io[io_fd],0,2);  /* go to end of file */
	  if (io[io_chans] != 1)
	    {
	      clm_write_zeros(io[io_fd],io[io_chans]*(loc-file_end)); 
	      io[io_beg]=loc;
	    }
	  else
	    {
	      clm_write_zeros(io[io_fd],loc-file_end); 
	      if ((loc%2)==0) io[io_beg]=loc; 
	      else io[io_beg]=loc-1;
	    }
	}
    }
  else /* bytes is positive or 0 */
    {
      clm_seek(io[io_fd],io[io_hdr_end]+(2*io[io_chans]*loc),0);
      io[io_beg] = loc;
      if (bytes > 0) 
	{
	  if (bytes > io[io_bufsiz]) 
	    {
	      str=(char *)calloc(256,sizeof(char));
	      sprintf(str,"input request is too big: %d", bytes);
	      clm_printf(str);
	      free(str);
	    }
	  else
	    {
	      clm_read_chans(io[io_fd],0,bytes-1,io[io_chans],
			     (int **)(datai+io[io_dats+c_aref_block]),
			     (int *)(datai+io[io_dats+c_aref_block]));
	      /* too clever -- I'm using the array of pointers to data buffers as the channel chooser as well */
	    }
	}
      if (bytes < io[io_bufsiz]) c_io_bufclr(io,datai,bytes);
      io[io_data_start] = 0;
      io[io_data_end] = 0;
      if (io[io_dir] == io_in_f)
	{
	  bufend = io[io_bufsiz]-1;
	  filbytes = file_end-io[io_beg]-1;
	  if (filbytes < bufend) bufend = filbytes;
	  if (bufend > 0) io[io_data_end] = bufend;
	}
    }
  io[io_end] = io[io_beg]+io[io_bufsiz]-1;
  io[io_loc] = (loc0-io[io_beg]);
}

/* now wrappers for low level clm open/close/access functions -- we can't use
 * the clm versions directly because in some cases, Snd has more than FOPEN_MAX
 * files nominally open and accessible (mix temps in with-sound explode for example).
 * these wrappers provide checks for EMFILE as errno from open and try to close
 * temps to make room.  Also, we want to handle errno in other cases through
 * strerror rather than perror.
 *
 * there is a hidden limit that might come into play if FOPEN_MAX > CLM_FILE_DESCRIPTORS (see io.c)
 * on the SGI, FOPEN_MAX is 100, but we can open many more files than that without hitting the EMFILE error.
 */

void snd_file_reset(snd_state *ss, snd_data *sd, int index)
{
  int fd = 0;
  int reclose = 0;
  file_info *hdr;
  /* fprintf(stderr,"    reset sd: %d ind: %d %s  io: %d\n ",(int)sd,index,(sd->open == FD_CLOSED) ? "c" : "o",(int)(sd->io)); */
  if (sd->open == FD_CLOSED)
    {
      /* try to open it with clm descriptors */
      if (sd->io[io_dir] == io_in_f) 
	fd = clm_open_read(sd->filename); 
      else fd = clm_reopen_write(sd->filename);
      hdr = sd->hdr;
      /* use snd_clm here to get temp closures -- these need to flush active data before hidden close and fixup the datai indices */
      open_clm_file_descriptors(fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
      /* fix up io[io_fd] and whatever else is clobbered by clm_close */
      sd->io[io_fd] = fd;
      sd->open = FD_OPEN;
      reclose = 1;
    }

  clm_file_reset(index,sd->io,sd->io);

  if (reclose)
    {
      snd_close(fd); 
      sd->open = FD_CLOSED; 
      sd->io[io_fd] = -1;
    }
}

static int close_temp_files(chan_info *cp, void *closed)
{
  int i,rtn;
  snd_data *sd;
  if (cp)
    {
      if (cp->sounds)
	{
	  rtn = (*((int *)closed));
	  for (i=0;i<cp->sound_size;i++)
	    {
	      sd = cp->sounds[i];
	      if ((sd) && (sd->type == SND_DATA_FILE) && (sd->io) && (sd->open == FD_OPEN))
		{
		  snd_close(sd->io[io_fd]);
		  sd->open = FD_CLOSED;
		  sd->io[io_fd] = -1;
		  rtn++;
		}
	    }
	  (*((int *)closed)) = rtn;
	}
    }
  return(0);
}

static int too_many_files_cleanup(snd_state *ss)
{
  int *closed;
  int rtn;
  rtn = -1;
  closed = (int *)calloc(1,sizeof(int));
  (*closed) = 0;
  map_over_chans(ss,close_temp_files,(void *)closed);
  if ((*closed) == 0) rtn = -1; else rtn = (*closed);
  free(closed);
  return(rtn);
}

int snd_open_read(snd_state *ss, char *arg) 
{
  int fd;
  fd = open(arg,O_RDONLY,0);
  if ((fd == -1) && (errno == EMFILE))
    {
      fd = too_many_files_cleanup(ss);
      if (fd != -1) fd = open(arg,O_RDONLY,0);
      if (fd == -1) snd_printf((void *)ss,strerror(errno));
    }
  return(fd);
}

int snd_open_write(snd_state *ss, char *arg)
{ /* not currently used anywhere */
  int fd;
  if ((fd = open(arg,O_RDWR,0)) == -1)
    {
      fd = creat(arg,0666);
      if ((fd == -1) && (errno == EMFILE))
	{
	  fd = too_many_files_cleanup(ss);
	  if (fd != -1) fd = creat(arg,0666);
	  if (fd == -1) snd_printf((void *)ss,strerror(errno));
	}
    }
  else lseek(fd,0L,2);
  return(fd);
}

int snd_create(snd_state *ss, char *arg)
{
  int fd;
  fd = creat(arg,0666);
  if ((fd == -1) && (errno == EMFILE))
    {
      fd = too_many_files_cleanup(ss);
      if (fd != -1) fd = creat(arg,0666);
      if (fd == -1) snd_printf((void *)ss,strerror(errno));
    }
  return(fd);
}

int snd_reopen_write(snd_state *ss, char *arg)
{
  int fd;
  fd = open(arg,O_RDWR,0);
  if ((fd == -1) && (errno == EMFILE))
    {
      fd = too_many_files_cleanup(ss);
      if (fd != -1) fd = open(arg,O_RDWR,0);
      if (fd == -1) snd_printf((void *)ss,strerror(errno));
    }
  return(fd);
}

void snd_close(int fd)
{
  close_clm_file_descriptors(fd);
  close(fd);
}

int snd_write(chan_info *cp, int tfd, int beg, int end, int chans, int **bufs, int datum_size)
{ /* not currently used anywhere */
  int err;
  err = disk_space_p(cp->sound,tfd,((end-beg)*chans*datum_size),0);
  if (err != GIVE_UP) clm_write(tfd,beg,end,chans,bufs);
  return(err);
}

int snd_write_header(snd_state *ss, char *name, int type, int srate, int chans, int loc, int size, int format, char *comment, int len)
{
  int fd;
  fd = c_write_header(name,type,srate,chans,loc,size,format,comment,len);
  if ((fd == -1) && (errno == EMFILE)) /* 0 => no error (fd not actually returned unless it's -1) */
    {
      fd = too_many_files_cleanup(ss);
      if (fd != -1) fd = c_write_header(name,type,srate,chans,loc,size,format,comment,len);
      if (fd == -1) snd_printf((void *)ss,strerror(errno));
    }
  return(fd);
}


/* extracted from merge.c and changed slightly (this is actually for the clm-snd debugger linkage) */

int file_maxamps(char *ifile, float *vals, int *changes)
{
  int ifd,ichans,idataloc,bufnum,n,cursamples,idatasize,loc,i,samples,chn,fc;
  int *buffer,*amps;
  int **ibufs;
  if ((ifd=clm_open_read(ifile)) == -1) return(0);
  /* c_read_header_with_fd(ifd); */
  open_clm_file_descriptors(ifd,c_snd_header_format(),c_snd_header_datum_size(),c_snd_header_data_location());
  idataloc = c_snd_header_data_location();
  ichans = c_snd_header_chans();
  idatasize = c_snd_header_data_size(); /* total samples (not per channel) */
  samples = (idatasize / ichans);
  if (samples <= 0) {clm_close(ifd); return(0);}
  loc=clm_seek(ifd,idataloc,0);
  if (loc<idataloc) {clm_close(ifd); return(0);}
  ibufs = (int **)calloc(ichans,sizeof(int *));
  for (i=0;i<ichans;i++) ibufs[i] = (int *)calloc(FILE_BUFFER_SIZE,sizeof(int));
  amps = (int *)calloc(ichans,sizeof(int));
  bufnum = (FILE_BUFFER_SIZE);
  for (n=0;n<samples;n+=bufnum)
    {
      if ((n+bufnum)<samples) cursamples = bufnum; else cursamples = (samples-n);
      clm_read(ifd,0,cursamples-1,ichans,ibufs);
      for (chn=0;chn<ichans;chn++)
	{
	  buffer = (int *)(ibufs[chn]);
	  fc=amps[chn];
	  for (i=0;i<cursamples-1;i++) 
	    {
	      if ((buffer[i] > fc) || (fc < -buffer[i])) 
		{
		  fc=buffer[i]; 
		  if (changes[chn] == 0) {if ((n+i) > 0) changes[chn] = 1;}
		  if (fc<0) fc = -fc;
		}
	    }
	  amps[chn]=fc;
	}
    }
  clm_close(ifd);
  for (chn=0;chn<ichans;chn++) vals[chn]=(float)(clm_sndflt*amps[chn]);
  return(1);
}

#if defined(NEXT) || defined(BEOS)
char *tempnam(char *ignored, char *tmp)
{
  return(copy_string(tmpnam(NULL)));
}
#endif
