#include "snd.h"
#include "clm2xen.h"


static bool cp_has_selection(chan_info *cp)
{
  ed_list *ed;
  ed = cp->edits[cp->edit_ctr];
  return((ed) && (ed->selection_beg != NO_SELECTION));
}


static bool map_over_chans(bool (*func)(chan_info *ncp))
{
  /* non-zero = abort map, skips inactive sounds */
  int i;
  bool val = false;

  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    if (cp)
	      {
		val = (*func)(cp);
		if (val) return(val);
	      }
      }
  return(val);
}


bool selection_is_active(void)
{
  /* is selection active in any channel */
  return(map_over_chans(cp_has_selection));
}


bool selection_is_active_in_channel(chan_info *cp)
{
  return((cp) && (cp_has_selection(cp)));
}


static bool selection_is_visible(chan_info *cp)
{
  ed_list *ed;
  axis_info *ap;

  ed = cp->edits[cp->edit_ctr];
  if (ed->selection_beg == NO_SELECTION) return(false);

  ap = cp->axis;
  return((ed) && 
	 (ap->losamp < ed->selection_end) && 
	 (ap->hisamp > ed->selection_beg));
}


bool selection_is_visible_in_channel(chan_info *cp)
{
  return((cp_has_selection(cp)) && 
	 (selection_is_visible(cp)));
}


static mus_long_t mus_long_t_map_over_chans(mus_long_t (*func)(chan_info *, mus_long_t *), mus_long_t *userptr)
{
  int i;
  mus_long_t val = 0;

  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;

      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    {
	      val = (*func)(cp, userptr);
	      if (val) return(val);
	    }
    }
  return(val);
}


static mus_long_t cp_selection_beg(chan_info *cp, mus_long_t *beg) 
{
  ed_list *ed;

  ed = cp->edits[cp->edit_ctr];
  if (ed->selection_beg != NO_SELECTION)
    {
      beg[0] = ed->selection_beg;
      return(1); /* i.e. stop map_over_chans */
    }

  return(0);
}


mus_long_t selection_beg(chan_info *cp)
{
  mus_long_t beg[1];
  beg[0] = 0;
  if (cp)
    cp_selection_beg(cp, beg);
  else mus_long_t_map_over_chans(cp_selection_beg, beg);
  return(beg[0]);
}


static int xen_selection_counter = 0;

static void cp_set_selection_beg(chan_info *cp, mus_long_t beg)
{
  ed_list *ed;
  mus_long_t len;

  ed = cp->edits[cp->edit_ctr];
  len = current_samples(cp);
  if (beg < len)
    ed->selection_beg = beg;
  else ed->selection_beg = len - 1;
  xen_selection_counter++;

  ed->selection_maxamp = -1.0;
  ed->selection_maxamp_position = -1;
}


mus_long_t selection_end(chan_info *cp) /* never called without selection_member check in advance */
{
  return(cp->edits[cp->edit_ctr]->selection_end);
}


static mus_long_t cp_selection_len(chan_info *cp, mus_long_t *ptr)
{
  ed_list *ed;
  ed = cp->edits[cp->edit_ctr];
  if ((ed) && (ed->selection_beg != NO_SELECTION))
    return(ed->selection_end - ed->selection_beg + 1); 
  return(0);
}


mus_long_t selection_len(void)
{
  return(mus_long_t_map_over_chans(cp_selection_len, NULL));
}


static void cp_set_selection_len(chan_info *cp, mus_long_t len)
{
  ed_list *ed;
  mus_long_t cplen;
  ed = cp->edits[cp->edit_ctr];
  cplen = current_samples(cp);
  ed->selection_end = ed->selection_beg + len - 1;
  if (ed->selection_end >= cplen) ed->selection_end = cplen - 1;
  ed->selection_maxamp = -1.0;
  ed->selection_maxamp_position = -1;
}


static void selection_chans_1(chan_info *cp, int *counter)
{
  if (cp_has_selection(cp)) counter[0]++;
}


int selection_chans(void)
{
  int count[1];
  count[0] = 0;
  for_each_normal_chan_with_refint(selection_chans_1, count);
  return(count[0]);
}


static mus_long_t selection_srate_1(chan_info *cp, mus_long_t *ignored)
{
  if (cp_has_selection(cp)) 
    return((mus_long_t)snd_srate(cp->sound));
  return(0);
}


int selection_srate(void)
{
  if (selection_is_active())
    return((int)mus_long_t_map_over_chans(selection_srate_1, NULL));
  return(0);
}


mus_float_t selection_maxamp(chan_info *cp)
{
  mus_float_t val = 0.0;
  if (selection_is_active_in_channel(cp))
    {
      mus_long_t maxpos = 0;
      val = ed_selection_maxamp(cp);
      if (val >= 0.0) return(val);
      val = channel_local_maxamp(cp, 
				 selection_beg(cp), 
				 selection_end(cp) - selection_beg(cp) + 1,
				 cp->edit_ctr,
				 &maxpos);
      set_ed_selection_maxamp(cp, val);
      set_ed_selection_maxamp_position(cp, maxpos);
    }
  return(val);
}


static mus_long_t selection_maxamp_position(chan_info *cp)
{
  selection_maxamp(cp);
  return(ed_selection_maxamp_position(cp));
}


void cp_delete_selection(chan_info *cp)
{
  ed_list *ed;
  ed = cp->edits[cp->edit_ctr];
  if ((ed) && (ed->selection_beg != NO_SELECTION))
    {
      delete_samples(ed->selection_beg, cp_selection_len(cp, NULL), cp, cp->edit_ctr);
      ed = cp->edits[cp->edit_ctr];
      ed->selection_beg = NO_SELECTION;
    }
}


bool delete_selection(cut_selection_regraph_t regraph)
{
  if (selection_is_active())
    {
      for_each_normal_chan(cp_delete_selection);
      if (regraph == UPDATE_DISPLAY) 
	for_each_normal_chan(update_graph);
      enved_reflect_selection(false);

      if (Xen_hook_has_list(ss->effects_hook))
	run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);

      return(true);
    }
  return(false);
}


static void cp_deactivate_selection(chan_info *cp)
{
  ed_list *ed;
  ed = cp->edits[cp->edit_ctr];
  if (ed) ed->selection_beg = NO_SELECTION;
}


static sync_info *syncd_chans = NULL;

void deactivate_selection(void)
{
  for_each_normal_chan(cp_deactivate_selection);
  for_each_normal_chan(update_graph);
  enved_reflect_selection(false);

  if (Xen_hook_has_list(ss->effects_hook))
    run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);

  if (syncd_chans) 
    syncd_chans = free_sync_info(syncd_chans);
}


void reactivate_selection(chan_info *cp, mus_long_t beg, mus_long_t end)
{
  ed_list *ed;
  mus_long_t len;

  ed = cp->edits[cp->edit_ctr];
  len = current_samples(cp) - 1;
  if (beg < 0) beg = 0;
  if (end < 0) end = 0;
  if (beg > len) beg = len;
  if (end > len) end = len;
  if (beg > end) end = beg;

  ed->selection_beg = beg;
  ed->selection_end = end;
  cp->selection_visible = false;
  ed->selection_maxamp = -1.0;
  ed->selection_maxamp_position = -1;
  xen_selection_counter++;
  enved_reflect_selection(true);
  reflect_selection_in_save_as_dialog(true);

  if (Xen_hook_has_list(ss->effects_hook))
    run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);
}


void ripple_selection(ed_list *ed, mus_long_t beg, mus_long_t num)
{
  /* beg = insert or delete begin point (snd-edits.c), num = samps inserted (num positive) or deleted (num negative) at beg */
  if (ed->selection_beg != NO_SELECTION)
    {
      if (beg < ed->selection_beg) 
	{
	  ed->selection_beg += num;
	  if (beg >= ed->selection_beg) 
	    ed->selection_beg = NO_SELECTION; /* deletion included some of current selection from outside */
	  else ed->selection_end += num;
	}
      else
	{
	  if (beg < ed->selection_end)
	    {
	      ed->selection_end += num;
	      if (ed->selection_end < beg)
		ed->selection_beg = NO_SELECTION; /* same as above but from end */
	    }
	}
    }
}


static void next_selection_chan(chan_info *cp, void *sidata)
{
  if (cp_has_selection(cp))
    {
      sync_info *si = (sync_info *)sidata;
      int chan;
      chan = si->chans;
      si->chans++;
      si->begs[chan] = selection_beg(cp);
      si->cps[chan] = cp;
    }
}


sync_info *selection_sync(void)
{
  sync_info *si;
  if (!(selection_is_active())) return(NULL);
  si = (sync_info *)calloc(1, sizeof(sync_info));
  si->chans = selection_chans();
  si->cps = (chan_info **)calloc(si->chans, sizeof(chan_info *));
  si->begs = (mus_long_t *)calloc(si->chans, sizeof(mus_long_t));
  si->chans = 0;
  for_each_normal_chan_with_void(next_selection_chan, (void *)si);
  return(si);
}


static int mix_selection(chan_info *cp, sync_info *si_out, mus_long_t beg, io_error_t *err, int start_chan)
{
  char *tempfile;
  int id = INVALID_MIX_ID;
  io_error_t io_err;

  tempfile = snd_tempnam();
  io_err = save_selection(tempfile, snd_srate(cp->sound), MUS_OUT_SAMPLE_TYPE, MUS_NEXT, NULL, SAVE_ALL_CHANS);
  if (io_err == IO_NO_ERROR)
    {
      char *origin = NULL;
#if HAVE_FORTH
      origin = mus_format("%" print_mus_long " snd chn %s", beg, S_mix_selection);
#else
  #if HAVE_SCHEME
      origin = mus_format("-mix-selection- %" print_mus_long " %" print_mus_long " %" print_mus_long " snd chn",
                          beg, selection_beg(cp), selection_len());
  #else
      origin = mus_format("%s" PROC_OPEN "%" print_mus_long, to_proc_name(S_mix_selection), beg);
  #endif
#endif
      if (si_out->chans > 1)
	remember_temp(tempfile, si_out->chans);

      id = mix_file(beg, selection_len(), si_out->chans, si_out->cps, tempfile, 
		    (si_out->chans > 1) ? MULTICHANNEL_DELETION : DELETE_ME, 
		    origin, with_mix_tags(ss), 
		    start_chan);
      free(origin);
    }
  if (tempfile) free(tempfile);
  (*err) = io_err;

  return(id);
}


void add_selection_or_region(int reg, chan_info *cp)
{
  /* in all cases, this has a local sound to report in (kbd, xmenu) */
  if (cp) 
    {
      if (is_editable(cp))
	{
	  io_error_t io_err = IO_NO_ERROR;
	  bool got_selection;
	  got_selection = ((reg == 0) && (selection_is_active()));
	  if (got_selection)
	    {
	      sync_info *si_out;
	      si_out = sync_to_chan(cp);
	      mix_selection(cp, si_out, cursor_sample(cp), &io_err, 0);
	      free_sync_info(si_out);
	    }
	  else io_err = add_region(reg, cp);
	  if (io_err != IO_NO_ERROR)
	    status_report(cp->sound, "can't mix %s: %s",
				 (got_selection) ? "selection" : "region",
				 io_error_name(io_err));
	}
    }
  else snd_error_without_format("no channel to mix into?");
}


static io_error_t insert_selection(chan_info *cp, sync_info *si_out, mus_long_t beg)
{
  char *tempfile = NULL;
  mus_sample_t out_format = MUS_OUT_SAMPLE_TYPE;
  io_error_t io_err = IO_NO_ERROR;

  if (mus_header_writable(MUS_NEXT, cp->sound->hdr->sample_type))
    out_format = cp->sound->hdr->sample_type;
  tempfile = snd_tempnam();

  io_err = save_selection(tempfile, snd_srate(cp->sound), out_format, MUS_NEXT, NULL, SAVE_ALL_CHANS);
  if (io_err == IO_NO_ERROR)
    {
      int i;
      sync_info *si_in;
      si_in = selection_sync();
      if (si_in)
	{
	  if (si_in->chans > 1) 
	    remember_temp(tempfile, si_in->chans);
	  for (i = 0; ((i < si_in->chans) && (i < si_out->chans)); i++)
	    {
	      char *origin;
	      chan_info *cp_in, *cp_out;
	      mus_long_t len;
	      cp_out = si_out->cps[i]; /* currently syncd chan that we might paste to */
	      cp_in = si_in->cps[i];   /* selection chan to paste in (no wrap-around here) */
	      len = cp_selection_len(cp_in, NULL);
#if HAVE_FORTH
	      origin = mus_format("%" print_mus_long " %s", beg, S_insert_selection);
#else
	      origin = mus_format("%s" PROC_OPEN "%" print_mus_long, to_proc_name(S_insert_selection), beg);
#endif
	      if (file_insert_samples(beg, len,
				      tempfile, cp_out, i,
				      (si_in->chans > 1) ? MULTICHANNEL_DELETION : DELETE_ME,
				      origin, cp_out->edit_ctr))
		update_graph(cp_out);
	      free(origin);
	    }
	  free_sync_info(si_in);
	}
    }
  if (tempfile) free(tempfile);
  return(io_err);
}


static void insert_selection_or_region(int reg, chan_info *cp)
{
  if (cp) 
    {
      io_error_t err = IO_NO_ERROR;
      bool got_selection;
      got_selection = ((reg == 0) && (selection_is_active()));
      if (got_selection)
	{
	  sync_info *si_out;
	  si_out = sync_to_chan(cp);
	  err = insert_selection(cp, si_out, cursor_sample(cp));
	  free_sync_info(si_out);
	}
      else err = paste_region(reg, cp);
      if (err != IO_NO_ERROR)
	status_report(cp->sound, "can't insert %s: %s",
			     (got_selection) ? "selection" : "region",
			     io_error_name(err));
    }
}


void insert_selection_from_menu(void)
{
  insert_selection_or_region(0, selected_channel());
}


/* we're drawing the selection in one channel, but others may be sync'd to it */

void start_selection_creation(chan_info *cp, mus_long_t samp)
{  
  int i;
  if ((syncd_chans) && 
      (selection_creates_region(ss)))
    /* hmmm -- if keyboard selection in progress, then mouse press? */
    make_region_from_selection();
  deactivate_selection();
  syncd_chans = sync_to_chan(cp);
  syncd_chans->begs[0] = samp;           /* begs not otherwise used here, so treat as pivot point */
  for (i = 0; i < syncd_chans->chans; i++)
    reactivate_selection(syncd_chans->cps[i], samp, samp);
}


void restart_selection_creation(chan_info *cp, bool right)
{
  syncd_chans = sync_to_chan(cp);
  if (right)
    syncd_chans->begs[0] = selection_beg(cp);
  else syncd_chans->begs[0] = selection_end(cp);
}


bool selection_creation_in_progress(void) {return(syncd_chans != NULL);}


void finish_selection_creation(void)
{
  if (syncd_chans)
    {
      if (selection_creates_region(ss)) 
	make_region_from_selection();
      enved_reflect_selection(true);
      reflect_selection_in_save_as_dialog(true);

      if (Xen_hook_has_list(ss->effects_hook))
	run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);

      syncd_chans = free_sync_info(syncd_chans);      
    }
}


static void show_selection_triangle(chan_info *cp, graphics_context *ax, int x0, int x1, mus_long_t beg, mus_long_t end);

static void cp_redraw_selection(chan_info *cp)
{
  int x0, x1;
  mus_long_t beg, end;
  axis_info *ap;
  double sp_srate;
  graphics_context *ax;

  ap = cp->axis;
  beg = selection_beg(cp);
  end = selection_end(cp);
  sp_srate = (double)snd_srate(cp->sound);

  if (ap->losamp < beg)
    x0 = grf_x((double)beg / sp_srate, ap);
  else x0 = ap->x_axis_x0;

  if (ap->hisamp > end)
    x1 = grf_x((double)end / sp_srate, ap);
  else x1 = ap->x_axis_x1;

  ax = selection_context(cp);

  if (cp->selection_visible)
    {
      fill_rectangle(ax,
		     cp->old_x0,
		     ap->y_axis_y1,
		     cp->old_x1 - cp->old_x0,
		     (int)(ap->y_axis_y0 - ap->y_axis_y1));
      show_selection_triangle(cp, ax, cp->old_x0, cp->old_x1, beg, end);
    }

  fill_rectangle(ax,
		 x0,
		 ap->y_axis_y1,
		 x1 - x0,
		 (int)(ap->y_axis_y0 - ap->y_axis_y1));
  show_selection_triangle(cp, ax, x0, x1, beg, end);

  cp->old_x0 = x0;
  cp->old_x1 = x1;
  cp->selection_visible = true;
}


static void redraw_selection(void)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if (sp)
	{
	  if (sp->inuse == SOUND_NORMAL)
	    {
	      uint32_t j;
	      for (j = 0; j < sp->nchans; j++)
		{
		  chan_info *cp;
		  cp = sp->chans[j];
		  if ((cp) && (selection_is_visible(cp)))
		    {
		      cp_redraw_selection(cp);
		      if ((cp->graph_transform_on) && 
			  (!(chan_fft_in_progress(cp))) &&
			  (show_selection_transform(ss)))
			calculate_fft(cp);
		      if (sp->channel_style == CHANNELS_SUPERIMPOSED)
			break;
		    }
		}
	    }
	}
    }
}


void display_selection(chan_info *cp)
{ 
  if (selection_is_visible(cp))
    cp_redraw_selection(cp); /* draw just this chan */
}


void update_possible_selection_in_progress(mus_long_t samp)
{
  if (syncd_chans)
    {
      int i;
      mus_long_t original_beg;

      if (samp < 0) samp = 0;
      original_beg = syncd_chans->begs[0];
      for (i = 0; i < syncd_chans->chans; i++)
	{
	  chan_info *cp;
	  ed_list *ed;
	  mus_long_t new_end;

	  cp = syncd_chans->cps[i];
	  ed = cp->edits[cp->edit_ctr];
	  ed->selection_maxamp = -1.0;
	  ed->selection_maxamp_position = -1;
	  if (samp >= current_samples(cp))
	    new_end = current_samples(cp) - 1;
	  else new_end = samp;

	  if (new_end < original_beg)
	    {
	      ed->selection_beg = new_end;
	      ed->selection_end = original_beg;
	    }
	  else
	    {
	      ed->selection_beg = original_beg;
	      ed->selection_end = new_end;
	    }
	}
      redraw_selection();
    }
}


int make_region_from_selection(void)
{
  mus_long_t *ends = NULL;
  int i, id = -1;
  bool happy = false;
  sync_info *si;

  if (!(selection_is_active())) return(-1);
  if (max_regions(ss) == 0) return(-1);

  si = selection_sync();
  ends = (mus_long_t *)calloc(si->chans, sizeof(mus_long_t));
  for (i = 0; i < si->chans; i++) 
    {
      ends[i] = selection_end(si->cps[i]);
      if (ends[i] > si->begs[i]) happy = true;
      /* C-space followed by mouse click causes a bogus selection-creation event */
    }

  if (happy) id = define_region(si, ends);
  free_sync_info(si);
  if (ends) free(ends);
  return(id);
}


int select_all(chan_info *cp)
{
  if (cp) 
    {
      sync_info *si;
      int i;
      deactivate_selection();
      si = sync_to_chan(cp);
      for (i = 0; i < si->chans; i++)
	{
	  if (current_samples(si->cps[i]) > 0)
	    {
	      reactivate_selection(si->cps[i], 0, current_samples(si->cps[i]));
	      update_graph(si->cps[i]);
	    }
	}
      free_sync_info(si);
      if ((selection_is_active()) && (selection_creates_region(ss)))
	return(make_region_from_selection());
    }
  return(-1);
}


/* ---------------- selection mouse motion ---------------- */

static int last_selection_x = 0;
static timeout_result_t watch_selection_button = 0;

void cancel_selection_watch(void)
{
#if (!USE_NO_GUI)
  if (watch_selection_button)
    TIMEOUT_REMOVE(watch_selection_button);
#endif
  watch_selection_button = 0;
}

static void move_selection_1(chan_info *cp, int x);

#if (!USE_NO_GUI)
static TIMEOUT_TYPE watch_selection(TIMEOUT_ARGS)
{
  chan_info *cp = (chan_info *)context;
  if (watch_selection_button)
    {
      move_selection_1(cp, last_selection_x);
      watch_selection_button = CALL_TIMEOUT(watch_selection, 50, cp);
    }
  TIMEOUT_RESULT
}
#endif

static void move_selection_1(chan_info *cp, int x)
{
  axis_info *ap;
  ap = cp->axis;
  if ((x > ap->x_axis_x1) || (x < ap->x_axis_x0)) 
    {
      if (((x > ap->x_axis_x1) && (ap->x1 == ap->xmax)) ||
	  ((x < ap->x_axis_x0) && (ap->x0 == ap->xmin)))
	return;
      move_axis(cp, x);
      if (!watch_selection_button) 
	watch_selection_button = CALL_TIMEOUT(watch_selection, 50, cp);
    }
  else 
    if (watch_selection_button) 
      cancel_selection_watch();
  redraw_selection();
}


void move_selection(chan_info *cp, int x)
{
  last_selection_x = x; /* called in snd-xchn -- sets last_selection_x */
  move_selection_1(cp, x);
}


static void show_selection_triangle(chan_info *cp, graphics_context *ax, int x0, int x1, mus_long_t beg, mus_long_t end)
{
  int y0;

  y0 = ((axis_info *)(cp->axis))->y_axis_y0;
  if ((cp->axis->losamp <= beg) &&
      (cp->axis->hisamp > beg))
    {
      fill_polygon(ax, 4,
		   x0, y0,
		   x0 + play_arrow_size(ss), y0 + play_arrow_size(ss),
		   x0, y0 + 2 * play_arrow_size(ss),
		   x0, y0);
    }

  if ((cp->axis->losamp < end) &&
      (cp->axis->hisamp >= end))
    {
      fill_polygon(ax, 4,
		   x1, y0,
		   x1 - play_arrow_size(ss), y0 + play_arrow_size(ss),
		   x1, y0 + 2 * play_arrow_size(ss),
		   x1, y0);
    }
}

#define HIT_SLOP 4

bool hit_selection_triangle(chan_info *cp, int x, int y)
{
  axis_info *ap;
  mus_long_t beg;
  int mx;

  beg = selection_beg(cp);
  ap = cp->axis;
  if (beg < ap->losamp) return(false);
  if (beg > ap->hisamp) return(false);

  mx = grf_x((double)beg / (double)snd_srate(cp->sound), ap);

  if (mx > (x + HIT_SLOP)) return(false);                                /* click point is to the left of the triangle */
  if ((mx + play_arrow_size(ss) + HIT_SLOP) < x) return(false);  /* click point is to the right of the triangle */

  y = y - ap->y_axis_y0 - play_arrow_size(ss);
  if (y < 0) y = -y;
  return((mx + play_arrow_size(ss) - y + HIT_SLOP) >= x);
}


bool hit_selection_loop_triangle(chan_info *cp, int x, int y)
{
  axis_info *ap;
  mus_long_t end;
  int mx;

  end = selection_end(cp);
  ap = cp->axis;
  if (end < ap->losamp) return(false);
  if (end > ap->hisamp) return(false);

  mx = grf_x((double)end / (double)snd_srate(cp->sound), ap);

  if ((mx - play_arrow_size(ss) - HIT_SLOP) > x) return(false); 
  if (mx < (x - HIT_SLOP)) return(false);

  y = y - ap->y_axis_y0 - play_arrow_size(ss);
  if (y < 0) y = -y;

  return((mx - play_arrow_size(ss) - y - HIT_SLOP) <= x);
}




/* ---------------------------------------- selection object ---------------------------------------- */

typedef struct {
  int n;
} xen_selection;


#define Xen_to_xen_selection(arg) ((xen_selection *)Xen_object_ref(arg))

static Xen_object_type_t xen_selection_tag;

bool xen_is_selection(Xen obj) 
{
  return(Xen_c_object_is_type(obj, xen_selection_tag));
}

#if (!HAVE_SCHEME)
static void xen_selection_free(xen_selection *v) {if (v) free(v);}

Xen_wrap_free(xen_selection, free_xen_selection, xen_selection_free)
#else
static s7_pointer s7_xen_selection_free(s7_scheme *sc, s7_pointer obj)
{
  xen_selection *v;
  v = (xen_selection *)s7_c_object_value(obj);
  if (v) free(v);
  return(NULL);
}
#endif


static char *xen_selection_to_string(xen_selection *v)
{
  #define xen_is_selectionRINT_BUFFER_SIZE 64
  char *buf;
  if (!v) return(NULL);
  buf = (char *)calloc(xen_is_selectionRINT_BUFFER_SIZE, sizeof(char));
  snprintf(buf, xen_is_selectionRINT_BUFFER_SIZE, "#<selection %d>", v->n);
  return(buf);
}


#if HAVE_FORTH || HAVE_RUBY
Xen_wrap_print(xen_selection, print_xen_selection, xen_selection_to_string)

static Xen g_xen_selection_to_string(Xen obj)
{
  char *vstr;
  Xen result;
  #define S_xen_selection_to_string "selection->string"
  Xen_check_type(xen_is_selection(obj), obj, 1, S_xen_selection_to_string, "a selection");
  vstr = xen_selection_to_string(Xen_to_xen_selection(obj));
  result = C_string_to_Xen_string(vstr);
  free(vstr);
  return(result);
}
#else
#if HAVE_SCHEME
static s7_pointer g_xen_selection_to_string(s7_scheme *sc, s7_pointer args)
{
  char *vstr;
  Xen result;
  vstr = xen_selection_to_string(Xen_to_xen_selection(s7_car(args)));
  result = C_string_to_Xen_string(vstr);
  free(vstr);
  return(result);
}
#endif
#endif


#if (!HAVE_SCHEME)
static bool xen_selection_equalp(xen_selection *v1, xen_selection *v2) 
{
  return((v1 == v2) ||
	 (v1->n == v2->n));
}


static Xen equalp_xen_selection(Xen obj1, Xen obj2)
{
  if ((!(xen_is_selection(obj1))) || (!(xen_is_selection(obj2)))) return(Xen_false);
  return(C_bool_to_Xen_boolean(xen_selection_equalp(Xen_to_xen_selection(obj1), Xen_to_xen_selection(obj2))));
}
#endif


static xen_selection *xen_selection_make(int n)
{
  xen_selection *new_v;
  new_v = (xen_selection *)malloc(sizeof(xen_selection));
  new_v->n = n;
  return(new_v);
}


static Xen g_selection(void)
{
  #define H_selection "(" S_selection" ) returns an object representing the current selection, or " PROC_FALSE " if there is no active selection"
  if (selection_is_active())
    {
      xen_selection *mx;
      mx = xen_selection_make(xen_selection_counter);
      return(Xen_make_object(xen_selection_tag, mx, 0, free_xen_selection));
    }
  return(Xen_false);
}


#if HAVE_SCHEME
static Xen s7_xen_selection_length(s7_scheme *sc, Xen args)
{
  return(g_selection_framples(Xen_undefined, Xen_undefined));
}

static s7_pointer s7_xen_selection_is_equal(s7_scheme *sc, s7_pointer args)
{
  s7_pointer p1, p2;
  p1 = s7_car(args);
  p2 = s7_cadr(args);
  if (p1 == p2) return(s7_t(sc));
  if (s7_c_object_type(p2) == xen_selection_tag)
    return(s7_make_boolean(sc, (((xen_selection *)s7_c_object_value(p1))->n == ((xen_selection *)s7_c_object_value(p2))->n)));
  return(s7_f(sc));
}

static Xen s7_xen_selection_copy(s7_scheme *sc, Xen args)
{
  if (selection_is_active())
    {
      snd_info *sp;
      char *name;
      name = snd_tempnam();
      save_selection(name, selection_srate(), MUS_OUT_SAMPLE_TYPE, MUS_NEXT, NULL, SAVE_ALL_CHANS);
      sp = snd_open_file(name, FILE_READ_WRITE);
      free(name);
      return(new_xen_sound(sp->index));
    }
  return(Xen_false);
}


static Xen s7_xen_selection_fill(s7_scheme *sc, Xen args)
{
  sync_info *si;
  mus_float_t valf;
  s7_pointer val;

  val = s7_cadr(args);
  valf = Xen_real_to_C_double(val);
  if (valf == 0.0)
    {
      mus_float_t vals[1] = {0.0};
      scale_by(NULL, vals, 1, true); /* 1 entry in vals array, true = over selection */
      return(Xen_false);
    }

  si = selection_sync();
  if (si)
    {
      int i;
      for (i = 0; i < si->chans; i++)
	{
	  mus_long_t beg, end, len, j;
	  mus_float_t *data;
	  beg = selection_beg(si->cps[i]);
	  end = selection_end(si->cps[i]);
	  len = end - beg + 1;
	  data = (mus_float_t *)malloc(len * sizeof(mus_float_t));
	  for (j = 0; j < len; j++)
	    data[j] = valf;
	  if (change_samples(beg, len, data, si->cps[i], "fill! selection", si->cps[i]->edit_ctr, fabs(valf)))
	    update_graph(si->cps[i]);
	  free(data);
	}
    }
  return(Xen_false);
}
#endif


static void init_xen_selection(void)
{
#if HAVE_SCHEME
  xen_selection_tag = s7_make_c_type(s7, "<selection>");
  s7_c_type_set_gc_free(s7, xen_selection_tag, s7_xen_selection_free);
  s7_c_type_set_is_equal(s7, xen_selection_tag, s7_xen_selection_is_equal);
  s7_c_type_set_length(s7, xen_selection_tag, s7_xen_selection_length);
  s7_c_type_set_copy(s7, xen_selection_tag, s7_xen_selection_copy);
  s7_c_type_set_fill(s7, xen_selection_tag, s7_xen_selection_fill);
  s7_c_type_set_to_string(s7, xen_selection_tag, g_xen_selection_to_string);
#else
#if HAVE_RUBY
  xen_selection_tag = Xen_make_object_type("XenSelection", sizeof(xen_selection));
#else
  xen_selection_tag = Xen_make_object_type("selection", sizeof(xen_selection));
#endif
#endif

#if HAVE_FORTH
  fth_set_object_inspect(xen_selection_tag,   print_xen_selection);
  fth_set_object_dump(xen_selection_tag,      g_xen_selection_to_string);
  fth_set_object_equal(xen_selection_tag,     equalp_xen_selection);
  fth_set_object_free(xen_selection_tag,      free_xen_selection);
#endif

#if HAVE_RUBY
  rb_define_method(xen_selection_tag, "to_s",     Xen_procedure_cast print_xen_selection, 0);
  rb_define_method(xen_selection_tag, "eql?",     Xen_procedure_cast equalp_xen_selection, 1);
  rb_define_method(xen_selection_tag, "==",       Xen_procedure_cast equalp_xen_selection, 1);
  rb_define_method(xen_selection_tag, "to_str",   Xen_procedure_cast g_xen_selection_to_string, 0);
#endif
}
/* -------------------------------------------------------------------------------- */



io_error_t save_selection(const char *ofile, int srate, mus_sample_t samp_type, mus_header_t head_type, const char *comment, int chan)
{
  /* type and format have already been checked */
  int ofd;
  io_error_t io_err = IO_NO_ERROR;
  mus_long_t oloc, alloc_len;
  sync_info *si;
  mus_long_t *ends;
  int i, j, k, chans;
  mus_long_t dur;
  snd_fd **sfs;
  snd_info *sp = NULL;
  mus_float_t **data;

  si = selection_sync();
  if ((si) && (si->cps) && (si->cps[0])) sp = si->cps[0]->sound;

  if (head_type == MUS_UNKNOWN_HEADER)
    {
      if ((sp) && (mus_header_writable(sp->hdr->type, MUS_IGNORE_SAMPLE)))
	head_type = sp->hdr->type;
      else head_type = MUS_NEXT;
    }
  if (samp_type == MUS_UNKNOWN_SAMPLE)
    {
      if ((sp) && (mus_header_writable(head_type, sp->hdr->sample_type)))
	samp_type = sp->hdr->sample_type;
      else samp_type = MUS_OUT_SAMPLE_TYPE;
    }
  if (!mus_header_writable(head_type, samp_type))
    {
      head_type = MUS_NEXT;
      samp_type = MUS_OUT_SAMPLE_TYPE;
    }
  if (srate == -1)
    srate = selection_srate();

  dur = selection_len();
  if (chan == SAVE_ALL_CHANS)
    chans = si->chans;
  else chans = 1;

  io_err = snd_write_header(ofile, head_type, srate, chans, chans * dur, samp_type, comment, NULL);
  if (io_err != IO_NO_ERROR)
    {
      free_sync_info(si);
      return(io_err);
    }

  oloc = mus_header_data_location();
  ofd = snd_reopen_write(ofile);

  if (sp)
    {
      int bps;
      mus_long_t num;
      disk_space_t no_space;
      bool copy_ok = false;

      bps = mus_bytes_per_sample(samp_type);
      num = dur * bps * chans;

      no_space = disk_has_space(num, ofile);
      if (no_space != DISK_SPACE_OK)
	{
	  snd_close(ofd, ofile);
	  free_sync_info(si);
	  return(IO_DISK_FULL);
	}

      copy_ok = ((samp_type == sp->hdr->sample_type) && 
		 (chans == (int)sp->nchans) &&
		 (chan == SAVE_ALL_CHANS));
      if (copy_ok)
	for (i = 0; i < chans; i++)
	  if ((sp->chans[i]->edit_ctr != 0) || 
	      (si->cps[i]->sound != sp) ||
	      (si->begs[i] != si->begs[0]))
	    {
	      copy_ok = false;
	      break;
	    }
      if (copy_ok)
	{
	  /* write next header with correct len
	   * seek loc in sp->filename
	   * copy len*data-size bytes
	   * get max from amp envs
	   */
	  int fdi;
	  lseek(ofd, oloc, SEEK_SET);
	  fdi = mus_file_open_read(sp->filename); /* this does not read the header */
	  if (fdi == -1)
	    {
	      snd_close(ofd, ofile);
	      free_sync_info(si);
	      return(IO_CANT_READ_SELECTION_FILE);
	    }
	  /* snd_error("can't read selection's original sound? %s: %s", sp->filename, snd_io_strerror()); */
	  else
	    {
	      mus_long_t iloc;
	      char *buffer;
	  
	      iloc = mus_sound_data_location(sp->filename);
	      lseek(fdi, iloc + chans * bps * si->begs[0], SEEK_SET);
	      buffer = (char *)malloc(MAX_BUFFER_SIZE * sizeof(char));
	      for (j = 0; j < num; j += MAX_BUFFER_SIZE)
		{
		  ssize_t n;
		  mus_long_t bytes;
		  bytes = num - j;
		  if (bytes > MAX_BUFFER_SIZE) bytes = MAX_BUFFER_SIZE;
		  n = read(fdi, buffer, bytes);
		  if (n != 0)
		    n = write(ofd, buffer, bytes);
		  if (n == 0)
		    fprintf(stderr, "IO error while saving selection");
		}
	      free(buffer);
	      snd_close(fdi, sp->filename);
	    }
	  snd_close(ofd, ofile);
	  free_sync_info(si);
#if USE_MOTIF
	  if (!(ss->file_monitor_ok))
	    alert_new_file();
#endif
	  return(IO_NO_ERROR);
	}
    }

  ends = (mus_long_t *)calloc(chans, sizeof(mus_long_t));
  sfs = (snd_fd **)calloc(chans, sizeof(snd_fd *));
  if (chan == SAVE_ALL_CHANS)
    {
      for (i = 0; i < chans; i++) 
	{
	  ends[i] = selection_end(si->cps[i]);
	  sfs[i] = init_sample_read(selection_beg(si->cps[i]), si->cps[i], READ_FORWARD);
	}
    }
  else
    {
      ends[0] = selection_end(si->cps[chan]);
      sfs[0] = init_sample_read(selection_beg(si->cps[chan]), si->cps[chan], READ_FORWARD);
    }

  snd_file_open_descriptors(ofd, ofile, samp_type, oloc, chans, head_type);
  mus_file_set_clipping(ofd, clipping(ss));
  lseek(ofd, oloc, SEEK_SET);
  data = (mus_float_t **)calloc(chans, sizeof(mus_float_t *));

  if (dur > REPORTING_SIZE)
    alloc_len = REPORTING_SIZE;
  else alloc_len = dur;

  for (i = 0; i < chans; i++) 
    data[i] = (mus_float_t *)calloc(alloc_len, sizeof(mus_float_t)); 

  if (alloc_len == dur)
    {
      for (k = 0; k < chans; k++)
	samples_to_vct_with_reader(dur, data[k], sfs[k]);
      mus_file_write(ofd, 0, dur - 1, chans, data);
    }
  else
    {
      mus_long_t ioff;
      ss->stopped_explicitly = false;
      for (k = 0; k < chans; k++)
	sampler_set_safe(sfs[k], ends[k]);

      for (ioff = 0; ioff < dur; ioff += alloc_len)
	{
	  mus_long_t kdur;

	  kdur = dur - ioff;
	  if (kdur > alloc_len) kdur = alloc_len;

	  for (j = 0; j < kdur; j++)
	    {
	      for (k = 0; k < chans; k++)
		{
		  if ((ioff + j) <= ends[k]) 
		    data[k][j] = read_sample(sfs[k]);
		  else data[k][j] = 0.0;
		}
	    }
	  io_err = sndlib_error_to_snd(mus_file_write(ofd, 0, j - 1, chans, data));
	  if (io_err != IO_NO_ERROR)
	    {
	      snd_warning("%s %s: %s",
			  io_error_name(io_err),
			  ofile,
			  snd_io_strerror());
	      break; 
	    }
	  if (ss->stopped_explicitly)
	    {
	      ss->stopped_explicitly = false;
	      snd_warning_without_format("save selection stopped");
	      io_err = IO_INTERRUPTED;
	      break;
	    }
	}
    }

  for (i = 0; i < chans; i++)
    {
      free_snd_fd(sfs[i]);
      free(data[i]);
    }
  free(sfs);
  free(data);
  free_sync_info(si);
  free(ends);

  if (mus_file_close(ofd) != 0)
    return(IO_CANT_CLOSE_FILE);
#if USE_MOTIF
  if (!(ss->file_monitor_ok))
    alert_new_file();
#endif

  return(io_err);
}


static Xen g_delete_selection(void)
{
  #define H_delete_selection "(" S_delete_selection "): delete the currently selected portion"
  if (selection_is_active())
    {
      delete_selection(UPDATE_DISPLAY);
      return(Xen_true);
    }
  return(snd_no_active_selection_error(S_delete_selection));
}


static Xen g_insert_selection(Xen beg, Xen snd, Xen chn)
{
  #define H_insert_selection "(" S_insert_selection " :optional (beg 0) snd chn): insert the currently selected portion starting at beg"
  if (selection_is_active())
    {
      chan_info *cp;
      mus_long_t samp;
      io_error_t io_err = IO_NO_ERROR;
      sync_info *si_out;

      Snd_assert_channel(S_insert_selection, snd, chn, 2);
      Xen_check_type(Xen_is_integer_or_unbound(beg), beg, 1, S_insert_selection, "an integer");

      cp = get_cp(snd, chn, S_insert_selection);
      if ((!cp) || (!(is_editable(cp)))) return(Xen_false);

      samp = beg_to_sample(beg, S_insert_selection);
      if (Xen_is_integer(chn))
	si_out = make_simple_sync(cp, samp); /* ignore sync */
      else si_out = sync_to_chan(cp);

      io_err = insert_selection(cp, si_out, samp);
      free_sync_info(si_out);

      if (is_serious_io_error(io_err))
	Xen_error(Xen_make_error_type("IO-error"),
		  Xen_list_2(C_string_to_Xen_string(S_insert_selection ": IO error ~A"),
			     C_string_to_Xen_string(io_error_name(io_err))));
      return(Xen_false);
    }
  return(snd_no_active_selection_error(S_insert_selection));
}


static Xen g_mix_selection(Xen beg, Xen snd, Xen chn, Xen sel_chan)
{
  #define H_mix_selection "(" S_mix_selection " :optional (beg 0) snd chn (selection-channel " PROC_TRUE ")): mix the currently selected portion starting at beg"
  if (selection_is_active())
    {
      chan_info *cp;
      mus_long_t obeg;
      io_error_t io_err = IO_NO_ERROR;
      int i, selection_chan = 0, id = -1, chans = 0;
      sync_info *si_out;
      Xen result = Xen_empty_list;

      Snd_assert_channel(S_mix_selection, snd, chn, 2);
      Xen_check_type(Xen_is_integer_or_unbound(beg), beg, 1, S_mix_selection, "an integer");
      Xen_check_type(Xen_is_integer_boolean_or_unbound(sel_chan), sel_chan, 4, S_mix_selection, "an integer or " PROC_TRUE);

      cp = get_cp(snd, chn, S_mix_selection);
      if ((!cp) || (!(is_editable(cp)))) return(Xen_false);

      obeg = beg_to_sample(beg, S_mix_selection);
      if (Xen_is_integer(sel_chan))
	selection_chan = Xen_integer_to_C_int(sel_chan);
      if (Xen_is_integer(chn))
	si_out = make_simple_sync(cp, obeg); /* ignore sync */
      else si_out = sync_to_chan(cp);

      id = mix_selection(cp, si_out, obeg, &io_err, selection_chan);
      chans = si_out->chans;                 /* save for loop below */
      free_sync_info(si_out);

      if (is_serious_io_error(io_err))
	Xen_error(Xen_make_error_type("IO-error"),
		  Xen_list_2(C_string_to_Xen_string(S_mix_selection ": IO error ~A"),
			     C_string_to_Xen_string(io_error_name(io_err))));

      if (id == -1) return(Xen_false);
      for (i = 0; i < chans; i++)
	result = Xen_cons(new_xen_mix(id + i), result);
      return(Xen_list_reverse(result));
    }
  return(snd_no_active_selection_error(S_mix_selection));
}


static Xen g_selection_to_mix(void)
{
  #define H_selection_to_mix "(" S_selection_to_mix "): turns the current selection into a mix"
  if (selection_is_active())
    {
      chan_info *cp;
      io_error_t io_err;
      int i, id = INVALID_MIX_ID, chans = 0, sync = GET_NEW_SYNC;
      sync_info *si_out;
      Xen result = Xen_empty_list;
      char *tempfile, *origin = NULL;

      si_out = selection_sync();
      cp = si_out->cps[0];

      tempfile = snd_tempnam();
      io_err = save_selection(tempfile, snd_srate(cp->sound), MUS_OUT_SAMPLE_TYPE, MUS_NEXT, NULL, SAVE_ALL_CHANS);
      if (is_serious_io_error(io_err))
	{
	  if (tempfile) free(tempfile);
	  free_sync_info(si_out);
	  Xen_error(Xen_make_error_type("IO-error"),
		    Xen_list_2(C_string_to_Xen_string(S_selection_to_mix ": IO error ~A"),
			       C_string_to_Xen_string(io_error_name(io_err))));
	}

      origin = mus_format("%s", S_selection_to_mix);
      if (si_out->chans > 1)
	remember_temp(tempfile, si_out->chans);

      g_scale_selection_by(C_double_to_Xen_real(0.0));

      id = mix_file(selection_beg(NULL), selection_len(), si_out->chans, si_out->cps, tempfile, 
		    (si_out->chans > 1) ? MULTICHANNEL_DELETION : DELETE_ME, 
		    origin, true,
		    0);

      deactivate_selection();
      free(origin);
      if (tempfile) free(tempfile);
      chans = si_out->chans;                 /* save for loop below */
      free_sync_info(si_out);

      if (id == -1) return(Xen_false);
      if (chans == 1)
	return(Xen_cons(new_xen_mix(id), Xen_empty_list)); /* no sync */

      for (i = 0; i < chans; i++)
	{
	  sync = mix_set_sync_from_id(id + i, sync);
	  result = Xen_cons(new_xen_mix(id + i), result);
	}

      if ((mix_dialog_mix() >= id) &&
	  (mix_dialog_mix() < (id + chans)))
	reflect_mix_change(id);
       /* this update is needed in a case like: file close (closing old mix), open new, mix -- this mix can now have old mix's id */
      return(Xen_list_reverse(result));
    }
  return(snd_no_active_selection_error(S_selection_to_mix));
}


static Xen g_is_selection(Xen sel)
{
  #define H_is_selection "(" S_is_selection " :optional obj): " PROC_TRUE " if selection is currently active, visible, etc. \
If 'obj' is passed, " S_is_selection " returns " PROC_TRUE " if obj is a selection object and there is a current selection."

  if ((Xen_is_bound(sel)) &&
      (!(xen_is_selection(sel))))
    return(Xen_false);

  return(C_bool_to_Xen_boolean(selection_is_active()));
}


static Xen g_selection_position(Xen snd, Xen chn)
{
  #define H_selection_position "(" S_selection_position " :optional snd chn): selection start samp"
  if (selection_is_active())
    {
      if (!Xen_is_bound(snd))
	return(C_llong_to_Xen_llong(selection_beg(NULL)));
      else
	{
	  chan_info *cp;
	  Snd_assert_channel(S_selection_position, snd, chn, 1);
	  cp = get_cp(snd, chn, S_selection_position);
	  if (!cp) return(Xen_false);
	  return(C_llong_to_Xen_llong(selection_beg(cp)));
	}
    }
  return(snd_no_active_selection_error(S_selection_position));
}


static Xen g_set_selection_position(Xen pos, Xen snd, Xen chn)
{
  chan_info *cp;
  mus_long_t beg;

  Snd_assert_channel(S_set S_selection_position, snd, chn, 2);
  Xen_check_type(Xen_is_integer(pos), pos, 1, S_selection_position, "an integer");

  beg = beg_to_sample(pos, S_set S_selection_position);
  if (!Xen_is_bound(snd))
    {
      sync_info *si = NULL;
      if (selection_is_active())
	si = selection_sync();
      else 
	{
	  cp = current_channel();
	  if (cp) si = sync_to_chan(cp);
	}
      if (si)
	{
	  int i;
	  for (i = 0; i < si->chans; i++) 
	    cp_set_selection_beg(si->cps[i], beg);
	  free_sync_info(si);
	}
    }
  else 
    {
      cp = get_cp(snd, chn, S_set S_selection_position);
      if (!cp) return(Xen_false);
      cp_set_selection_beg(cp, beg);
    }
  redraw_selection();
  return(pos);
}

with_three_setter_args(g_set_selection_position_reversed, g_set_selection_position)


Xen g_selection_framples(Xen snd, Xen chn)
{
  #define H_selection_framples "(" S_selection_framples " :optional snd chn): selection length"
  if (selection_is_active())
    {
      if (!Xen_is_bound(snd))
	return(C_llong_to_Xen_llong(selection_len()));
      else
	{
	  chan_info *cp;
	  Snd_assert_channel(S_selection_framples, snd, chn, 1);
	  cp = get_cp(snd, chn, S_selection_framples);
	  if (!cp) return(Xen_false);
	  return(C_llong_to_Xen_llong(cp_selection_len(cp, NULL)));
	}
    }
  return(snd_no_active_selection_error(S_selection_framples));
}


static Xen g_set_selection_framples(Xen samps, Xen snd, Xen chn)
{
  chan_info *cp;
  mus_long_t len;

  Xen_check_type(Xen_is_llong(samps), samps, 1, S_set S_selection_framples, "an integer");
  len = Xen_llong_to_C_llong(samps);
  if (len <= 0)
    Xen_wrong_type_arg_error(S_set S_selection_framples, 1, samps, "a positive integer");
  if (!Xen_is_bound(snd))
    {
      sync_info *si = NULL;
      if (selection_is_active())
	si = selection_sync();
      else 
	{
	  cp = current_channel();
	  if (cp) si = sync_to_chan(cp);
	}
      if (si)
	{
	  int i;
	  for (i = 0; i < si->chans; i++)
	    cp_set_selection_len(si->cps[i], len);
	  free_sync_info(si);
	}
    }
  else 
    {
      Snd_assert_channel(S_set S_selection_framples, snd, chn, 2);
      cp = get_cp(snd, chn, S_set S_selection_framples);
      if (!cp) return(Xen_false);
      cp_set_selection_len(cp, len);
    }
  redraw_selection();
  return(samps);
}

with_three_setter_args(g_set_selection_framples_reversed, g_set_selection_framples)


static Xen g_selection_member(Xen snd, Xen chn)
{
  #define H_selection_member "(" S_selection_member " :optional snd chn): " PROC_TRUE " if snd's channel chn is a member of the current selection"
  chan_info *cp;
  Snd_assert_channel(S_selection_member, snd, chn, 1);
  cp = get_cp(snd, chn, S_selection_member);
  if (!cp) return(Xen_false);
  return(C_bool_to_Xen_boolean(selection_is_active_in_channel(cp)));
}


static Xen g_set_selection_member(Xen on, Xen snd, Xen chn)
{
  Xen_check_type(Xen_is_boolean(on), on, 1, S_set S_selection_member, "a boolean");
  if ((Xen_is_true(snd)) && (Xen_is_false(on)))
    deactivate_selection();
  else
    {
      chan_info *cp;
      Snd_assert_channel(S_set S_selection_member, snd, chn, 2);
      cp = get_cp(snd, chn, S_set S_selection_member);
      if (!cp) return(Xen_false);
      if (Xen_is_true(on))
	{
	  if (selection_is_active())
	    cp_set_selection_beg(cp, selection_beg(NULL));
	  else cp_set_selection_beg(cp, 0);
	}
      else cp_deactivate_selection(cp);
      enved_reflect_selection(selection_is_active());
      reflect_selection_in_save_as_dialog(selection_is_active());

      if (Xen_hook_has_list(ss->effects_hook))
	run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);

      if (selection_is_active())
	redraw_selection();
    }
  return(on);
}

with_three_setter_args(g_set_selection_member_reversed, g_set_selection_member)


static Xen g_select_all(Xen snd_n, Xen chn_n)
{
  #define H_select_all "(" S_select_all " :optional snd chn): make a new selection containing all of snd's channel chn. \
If sync is set, all chans are included.  The new region id is returned (if " S_selection_creates_region " is " PROC_TRUE ")."
  chan_info *cp;
  int id;

  Snd_assert_channel(S_select_all, snd_n, chn_n, 1);
  cp = get_cp(snd_n, chn_n, S_select_all);
  if (!cp) return(Xen_false);

  id = select_all(cp);
  if (selection_creates_region(ss)) 
    return(C_int_to_Xen_region(id));
  else return(Xen_false);
}


#define H_save_selection "(" S_save_selection " file srate sample-type header-type comment channel): \
save the current selection in file using the indicated file attributes.  If channel is given, save only that channel."

#if HAVE_SCHEME
static s7_pointer g_save_selection(s7_scheme *sc, s7_pointer args)
{
  mus_header_t head_type;
  mus_sample_t samp_type;
  int sr, chn;
  io_error_t io_err;
  const char *com, *file;
  char *fname;
  s7_pointer p, fp, res;

  if (!(selection_is_active()))
    return(snd_no_active_selection_error(S_save_selection));

  fp = s7_car(args);
  if (fp == Xen_false)
    Xen_error(Xen_make_error_type("IO-error"), Xen_list_1(C_string_to_Xen_string(S_save_selection ": no output file?")));
  if (!s7_is_string(fp))
    return(s7_wrong_type_arg_error(sc, S_save_selection, 1, fp, "a string"));
  file = s7_string(fp);
  res = fp;

  fp = s7_cadr(args);
  if (fp == Xen_false)
    sr = selection_srate();
  else
    {
      if (!s7_is_integer(fp))
	return(s7_wrong_type_arg_error(sc, S_save_selection, 2, fp, "an integer"));
      sr = s7_integer(fp);
      if (sr <= 0)
	Xen_error(Xen_make_error_type("cannot-save"),
		  Xen_list_2(C_string_to_Xen_string(S_save_selection ": srate (~A) can't be <= 0"), fp));
    }

  p = s7_cddr(args);
  fp = s7_car(p);
  if (fp == Xen_false)
    samp_type = MUS_UNKNOWN_SAMPLE;
  else
    {
      if (!s7_is_integer(fp))
	return(s7_wrong_type_arg_error(sc, S_save_selection, 3, fp, "an integer"));
      samp_type = (mus_sample_t)s7_integer(fp);
    }

  fp = s7_cadr(p);
  if (fp == Xen_false)
    head_type = MUS_UNKNOWN_HEADER;
  else
    {
      if (!s7_is_integer(fp))
	return(s7_wrong_type_arg_error(sc, S_save_selection, 4, fp, "an integer"));
      head_type = (mus_header_t)s7_integer(fp);
    }
  if ((head_type != MUS_UNKNOWN_HEADER) && (!(mus_header_writable(head_type, MUS_IGNORE_SAMPLE))))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_2(C_string_to_Xen_string(S_save_selection ": can't write a ~A header"),
			 C_string_to_Xen_string(mus_header_type_name(head_type))));

  if ((head_type != MUS_UNKNOWN_HEADER) && (samp_type != MUS_UNKNOWN_SAMPLE) && (!(mus_header_writable(head_type, samp_type))))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_3(C_string_to_Xen_string(S_save_selection ": can't write ~A data to a ~A header"),
			 C_string_to_Xen_string(mus_sample_type_name(samp_type)),
			 C_string_to_Xen_string(mus_header_type_name(head_type))));

  p = s7_cddr(p);
  fp = s7_car(p);
  if (fp == Xen_false)
    com = NULL;
  else
    {
      if (!s7_is_string(fp))
	return(s7_wrong_type_arg_error(sc, S_save_selection, 5, fp, "a string"));
      com = s7_string(fp);
    }
  
  fp = s7_cadr(p);
  if (fp == Xen_false)
    chn = SAVE_ALL_CHANS;
  else
    {
      if (!s7_is_integer(fp))
	return(s7_wrong_type_arg_error(sc, S_save_selection, 6, fp, "an integer"));
      chn = s7_integer(fp);
    }

  fname = mus_expand_filename(file);
  io_err = save_selection(fname, sr, samp_type, head_type, com, chn);

  if (fname) free(fname);
  if ((io_err != IO_NO_ERROR) &&
      (io_err != IO_INTERRUPTED) &&
      (io_err != IO_SAVE_HOOK_CANCELLATION))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_3(C_string_to_Xen_string(S_save_selection ": can't save ~S, ~A"), res, C_string_to_Xen_string(snd_open_strerror())));
  return(res);
}
#else
static Xen kw_header_type, kw_comment, kw_file, kw_srate, kw_channel, kw_sample_type;

static void init_selection_keywords(void)
{
  kw_header_type = Xen_make_keyword("header-type");
  kw_sample_type = Xen_make_keyword("sample-type");
  kw_comment = Xen_make_keyword("comment");
  kw_file = Xen_make_keyword("file");
  kw_srate = Xen_make_keyword("srate");
  kw_channel = Xen_make_keyword("channel");
}

static Xen g_save_selection(Xen arglist)
{
  mus_header_t head_type = MUS_UNKNOWN_HEADER;
  mus_sample_t samp_type = MUS_UNKNOWN_SAMPLE;
  int sr = -1, chn = 0;
  io_error_t io_err = IO_NO_ERROR;
  const char *com = NULL, *file = NULL;
  char *fname = NULL;
  Xen args[12]; 
  Xen keys[6];
  int orig_arg[6] = {0, 0, 0, 0, 0, 0};
  int vals, i, arglist_len;
  if (!(selection_is_active()))
    return(snd_no_active_selection_error(S_save_selection));

  /* changed 7-Dec-08 to be more like save-sound-as in default values -- if just one
   *   sound involved, or all sounds same, use current choices rather than MUS_NEXT etc:
   *   hdr->type|srate|format
   */

  keys[0] = kw_file;
  keys[1] = kw_srate;
  keys[2] = kw_sample_type;
  keys[3] = kw_header_type;
  keys[4] = kw_comment;
  keys[5] = kw_channel;

  for (i = 0; i < 12; i++) args[i] = Xen_undefined;
  arglist_len = Xen_list_length(arglist);
  if (arglist_len > 12)
    Xen_out_of_range_error(S_save_selection, 0, arglist, "too many arguments");
  for (i = 0; i < arglist_len; i++) args[i] = Xen_list_ref(arglist, i);

  vals = mus_optkey_unscramble(S_save_selection, arglist_len, 6, keys, args, orig_arg);
  if (vals > 0)
    {
      file = mus_optkey_to_string(keys[0], S_save_selection, orig_arg[0], NULL);
      sr = mus_optkey_to_int(keys[1], S_save_selection, orig_arg[1], selection_srate());
      samp_type = (mus_sample_t)mus_optkey_to_int(keys[2], S_save_selection, orig_arg[2], (int)samp_type);
      head_type = (mus_header_t)mus_optkey_to_int(keys[3], S_save_selection, orig_arg[3], (int)head_type);
      com = mus_optkey_to_string(keys[4], S_save_selection, orig_arg[4], NULL);
      chn = mus_optkey_to_int(keys[5], S_save_selection, orig_arg[5], SAVE_ALL_CHANS);
    }

  if (!file) 
    Xen_error(Xen_make_error_type("IO-error"),
	      Xen_list_1(C_string_to_Xen_string(S_save_selection ": no output file?")));

  if ((head_type != MUS_UNKNOWN_HEADER) && (!(mus_header_writable(head_type, MUS_IGNORE_SAMPLE))))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_2(C_string_to_Xen_string(S_save_selection ": can't write a ~A header"),
			 C_string_to_Xen_string(mus_header_type_name(head_type))));

  if ((head_type != MUS_UNKNOWN_HEADER) && (samp_type != MUS_UNKNOWN_SAMPLE) && (!(mus_header_writable(head_type, samp_type))))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_3(C_string_to_Xen_string(S_save_selection ": can't write ~A data to a ~A header"),
			 C_string_to_Xen_string(mus_sample_type_name(samp_type)),
			 C_string_to_Xen_string(mus_header_type_name(head_type))));

  if ((sr != -1) && (sr <= 0))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_2(C_string_to_Xen_string(S_save_selection ": srate (~A) can't be <= 0"),
			 C_int_to_Xen_integer(sr)));

  fname = mus_expand_filename(file);
  io_err = save_selection(fname, sr, samp_type, head_type, com, chn);

  if (fname) free(fname);
  if ((io_err != IO_NO_ERROR) &&
      (io_err != IO_INTERRUPTED) &&
      (io_err != IO_SAVE_HOOK_CANCELLATION))
    Xen_error(Xen_make_error_type("cannot-save"),
	      Xen_list_3(C_string_to_Xen_string(S_save_selection ": can't save ~S, ~A"),
			 keys[0],
			 C_string_to_Xen_string(snd_open_strerror())));
  return(args[orig_arg[0] - 1]);
}
#endif

Xen g_selection_chans(void)
{
  #define H_selection_chans "(" S_selection_chans "): chans in active selection"
  return(C_int_to_Xen_integer(selection_chans()));
}


Xen g_selection_srate(void)
{
  #define H_selection_srate "(" S_selection_srate "): selection srate"
  return(C_int_to_Xen_integer(selection_srate()));
}


Xen g_selection_maxamp(Xen snd, Xen chn)
{
  #define H_selection_maxamp "(" S_selection_maxamp " :optional snd chn): selection maxamp in given channel, or overall maxamp if no args passed."
  if (Xen_is_bound(snd))
    {
      chan_info *cp;
      Snd_assert_channel(S_selection_maxamp, snd, chn, 1);
      cp = get_cp(snd, chn, S_selection_maxamp);
      if (!cp) return(Xen_false);
      return(C_double_to_Xen_real(selection_maxamp(cp)));
    }
  else
    {
      mus_float_t mx = 0.0;
      int i;
      sync_info *si;
      si = selection_sync();
      if (!si)
	return(C_double_to_Xen_real(0.0)); /* no selection -- error? */
      for (i = 0; i < si->chans; i++)
	{
	  mus_float_t cur_mx;
	  cur_mx = selection_maxamp(si->cps[i]);
	  if (cur_mx > mx)
	    mx = cur_mx;
	}
      free_sync_info(si);
      return(C_double_to_Xen_real(mx));
    }
}


static Xen g_selection_maxamp_position(Xen snd, Xen chn)
{
  #define H_selection_maxamp_position "(" S_selection_maxamp_position " :optional snd chn): location of selection maxamp (0 = start of selection)"
  chan_info *cp;
  Snd_assert_channel(S_selection_maxamp_position, snd, chn, 1);
  cp = get_cp(snd, chn, S_selection_maxamp_position);
  if (!cp) return(Xen_false);
  return(C_llong_to_Xen_llong(selection_maxamp_position(cp)));
}


static double sel_beg, sel_end;

static bool get_selection_bounds(chan_info *cp)
{
  if (selection_is_active_in_channel(cp))
    {
      mus_long_t samp;
      double x;
      samp = selection_beg(cp);
      x = (double)samp / snd_srate(cp->sound);
      if ((sel_beg < 0.0) || (x < sel_beg))
	sel_beg = x;
      samp = selection_end(cp);
      x = (double)samp / snd_srate(cp->sound);
      if ((sel_end < 0.0) || (x > sel_end))
	sel_end = x;
    }
  return(false);
}

static bool update_bounds(chan_info *cp)
{
  set_x_axis_x0x1(cp, sel_beg, sel_end);
  return(false);
}


void show_selection(void)
{
  sel_beg = -1.0;
  sel_end = -1.0;
  map_over_chans(get_selection_bounds);
  map_over_chans(update_bounds);
}


static Xen g_show_selection(void)
{
  #define H_show_selection "(" S_show_selection ") adjusts graph bounds to display the current selection in full"
  if (selection_is_active())
    show_selection();
  return(Xen_false);
}


static Xen g_unselect_all(void)
{
  #define H_unselect_all "(" S_unselect_all ") deactivates (unselects) the current selection."
  deactivate_selection();
  return(Xen_false);
}


Xen_wrap_2_optional_args(g_selection_position_w, g_selection_position)
Xen_wrap_2_optional_args(g_selection_framples_w, g_selection_framples)
Xen_wrap_2_optional_args(g_selection_member_w, g_selection_member)
Xen_wrap_no_args(g_selection_w, g_selection)
Xen_wrap_1_optional_arg(g_is_selection_w, g_is_selection)
Xen_wrap_no_args(g_selection_chans_w, g_selection_chans)
Xen_wrap_no_args(g_selection_srate_w, g_selection_srate)
Xen_wrap_2_optional_args(g_selection_maxamp_w, g_selection_maxamp)
Xen_wrap_2_optional_args(g_selection_maxamp_position_w, g_selection_maxamp_position)
Xen_wrap_no_args(g_delete_selection_w, g_delete_selection)
Xen_wrap_3_optional_args(g_insert_selection_w, g_insert_selection)
Xen_wrap_4_optional_args(g_mix_selection_w, g_mix_selection)
Xen_wrap_no_args(g_selection_to_mix_w, g_selection_to_mix)
Xen_wrap_2_optional_args(g_select_all_w, g_select_all)
Xen_wrap_no_args(g_show_selection_w, g_show_selection)
Xen_wrap_no_args(g_unselect_all_w, g_unselect_all)
#if HAVE_SCHEME
#define g_set_selection_position_w g_set_selection_position_reversed
#define g_set_selection_framples_w g_set_selection_framples_reversed
#define g_set_selection_member_w g_set_selection_member_reversed
#else
Xen_wrap_any_args(g_save_selection_w, g_save_selection)
Xen_wrap_3_optional_args(g_set_selection_position_w, g_set_selection_position)
Xen_wrap_3_optional_args(g_set_selection_framples_w, g_set_selection_framples)
Xen_wrap_3_optional_args(g_set_selection_member_w, g_set_selection_member)
#endif

void g_init_selection(void)
{
#if HAVE_SCHEME
  s7_pointer i, b, t, f, sel, p;
  i = s7_make_symbol(s7, "integer?");
  sel = s7_make_symbol(s7, "selection?");
  b = s7_make_symbol(s7, "boolean?");
  f = s7_make_symbol(s7, "float?");
  p = s7_make_symbol(s7, "pair?");
  t = s7_t(s7);
#endif

#if (!HAVE_SCHEME)
  init_selection_keywords();
#endif
  init_xen_selection();

  Xen_define_typed_dilambda(S_selection_position, g_selection_position_w, H_selection_position, S_set S_selection_position, g_set_selection_position_w, 0, 2, 1, 2,
			    s7_make_signature(s7, 3, i, t, t), s7_make_signature(s7, 4, i, t, t, i));
  Xen_define_typed_dilambda(S_selection_framples, g_selection_framples_w, H_selection_framples, S_set S_selection_framples, g_set_selection_framples_w, 0, 2, 1, 2,
			    s7_make_signature(s7, 3, i, t, t), s7_make_signature(s7, 4, i, t, t, i));
  Xen_define_typed_dilambda(S_selection_member, g_selection_member_w, H_selection_member, S_set S_selection_member, g_set_selection_member_w, 0, 2, 1, 2,
			    s7_make_signature(s7, 3, b, t, t), s7_make_signature(s7, 4, b, t, t, b));

  Xen_define_typed_procedure(S_selection,        g_selection_w,        0, 0, 0, H_selection,         s7_make_signature(s7, 1, s7_make_signature(s7, 2, sel, b)));
  Xen_define_typed_procedure(S_is_selection,     g_is_selection_w,     0, 1, 0, H_is_selection,      s7_make_signature(s7, 2, b, t));
  Xen_define_typed_procedure(S_selection_chans,  g_selection_chans_w,  0, 0, 0, H_selection_chans,   s7_make_signature(s7, 1, i));
  Xen_define_typed_procedure(S_selection_srate,  g_selection_srate_w,  0, 0, 0, H_selection_srate,   s7_make_signature(s7, 1, i));
  Xen_define_typed_procedure(S_selection_maxamp, g_selection_maxamp_w, 0, 2, 0, H_selection_maxamp,  s7_make_signature(s7, 3, f, t, t));
  Xen_define_typed_procedure(S_selection_maxamp_position, g_selection_maxamp_position_w, 0, 2, 0, H_selection_maxamp_position, s7_make_signature(s7, 3, i, t, t));
  Xen_define_typed_procedure(S_select_all,       g_select_all_w,       0, 2, 0, H_select_all,        s7_make_signature(s7, 3, b, t, t));
  Xen_define_typed_procedure(S_unselect_all,     g_unselect_all_w,     0, 0, 0, H_unselect_all,      s7_make_signature(s7, 1, b));
  Xen_define_typed_procedure(S_delete_selection, g_delete_selection_w, 0, 0, 0, H_delete_selection,  s7_make_signature(s7, 1, b));
  Xen_define_typed_procedure(S_insert_selection, g_insert_selection_w, 0, 3, 0, H_insert_selection,  s7_make_signature(s7, 4, b, i, t, t));
  Xen_define_typed_procedure(S_mix_selection,    g_mix_selection_w,    0, 4, 0, H_mix_selection,     s7_make_signature(s7, 5, t, i, t, t, i));
  Xen_define_typed_procedure(S_selection_to_mix, g_selection_to_mix_w, 0, 0, 0, H_selection_to_mix,  s7_make_signature(s7, 1, p));
  Xen_define_typed_procedure(S_show_selection,   g_show_selection_w,   0, 0, 0, H_show_selection,    s7_make_signature(s7, 1, b));
#if HAVE_SCHEME
  s7_define_safe_function_star(s7, S_save_selection, g_save_selection, "file srate sample-type header-type comment channel", H_save_selection);
#else
  Xen_define_typed_procedure(S_save_selection,   g_save_selection_w,   0, 0, 1, H_save_selection,    s7_make_circular_signature(s7, 0, 1, t));
#endif
}
