/* GSequencer - Advanced GTK Sequencer
 * Copyright (C) 2005-2015 Joël Krähemann
 *
 * This file is part of GSequencer.
 *
 * GSequencer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GSequencer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GSequencer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ags/audio/file/ags_ipatch.h>

#include <ags/libags.h>

#include <ags/audio/ags_playable.h>
#include <ags/audio/ags_audio_buffer_util.h>

#include <ags/audio/file/ags_ipatch_sf2_reader.h>

#include <ags/i18n.h>

void ags_ipatch_class_init(AgsIpatchClass *ipatch);
void ags_ipatch_connectable_interface_init(AgsConnectableInterface *connectable);
void ags_ipatch_playable_interface_init(AgsPlayableInterface *playable);
void ags_ipatch_init(AgsIpatch *ipatch);
void ags_ipatch_set_property(GObject *gobject,
			     guint prop_id,
			     const GValue *value,
			     GParamSpec *param_spec);
void ags_ipatch_get_property(GObject *gobject,
			     guint prop_id,
			     GValue *value,
			     GParamSpec *param_spec);
void ags_ipatch_finalize(GObject *gobject);

void ags_ipatch_connect(AgsConnectable *connectable);
void ags_ipatch_disconnect(AgsConnectable *connectable);

gboolean ags_ipatch_open(AgsPlayable *playable, gchar *name);
guint ags_ipatch_level_count(AgsPlayable *playable);
guint ags_ipatch_nth_level(AgsPlayable *playable);
gchar** ags_ipatch_sublevel_names(AgsPlayable *playable);
void ags_ipatch_level_select(AgsPlayable *playable,
			     guint nth_level, gchar *sublevel_name,
			     GError **error);
void ags_ipatch_iter_start(AgsPlayable *playable);
gboolean ags_ipatch_iter_next(AgsPlayable *playable);
void ags_ipatch_info(AgsPlayable *playable,
		     guint *channels, guint *frames,
		     guint *loop_start, guint *loop_end,
		     GError **error);
guint ags_ipatch_get_samplerate(AgsPlayable *playable);
guint ags_ipatch_get_format(AgsPlayable *playable);
double* ags_ipatch_read(AgsPlayable *playable, guint channel,
			GError **error);
int* ags_ipatch_read_int(AgsPlayable *playable, guint channel,
			 GError **error);
void ags_ipatch_close(AgsPlayable *playable);

/**
 * SECTION:ags_ipatch
 * @short_description: Libinstpatch wrapper
 * @title: AgsIpatch
 * @section_id:
 * @include: ags/audio/file/ags_ipatch.h
 *
 * #AgsIpatch is the base object to ineract with libinstpatch.
 */

static gpointer ags_ipatch_parent_class = NULL;
static AgsConnectableInterface *ags_ipatch_parent_connectable_interface;
static AgsPlayableInterface *ags_ipatch_parent_playable_interface;

enum{
  PROP_0,
  PROP_SOUNDCARD,
  PROP_FILENAME,
  PROP_MODE,
};

GType
ags_ipatch_get_type()
{
  static GType ags_type_ipatch = 0;

  if(!ags_type_ipatch){
    static const GTypeInfo ags_ipatch_info = {
      sizeof (AgsIpatchClass),
      NULL, /* base_init */
      NULL, /* base_finalize */
      (GClassInitFunc) ags_ipatch_class_init,
      NULL, /* class_finalize */
      NULL, /* class_data */
      sizeof (AgsIpatch),
      0,    /* n_preallocs */
      (GInstanceInitFunc) ags_ipatch_init,
    };

    static const GInterfaceInfo ags_connectable_interface_info = {
      (GInterfaceInitFunc) ags_ipatch_connectable_interface_init,
      NULL, /* interface_finalize */
      NULL, /* interface_data */
    };

    static const GInterfaceInfo ags_playable_interface_info = {
      (GInterfaceInitFunc) ags_ipatch_playable_interface_init,
      NULL, /* interface_finalize */
      NULL, /* interface_data */
    };

    ags_type_ipatch = g_type_register_static(G_TYPE_OBJECT,
					     "AgsIpatch",
					     &ags_ipatch_info,
					     0);

    g_type_add_interface_static(ags_type_ipatch,
				AGS_TYPE_CONNECTABLE,
				&ags_connectable_interface_info);

    g_type_add_interface_static(ags_type_ipatch,
				AGS_TYPE_PLAYABLE,
				&ags_playable_interface_info);
  }
  
  return (ags_type_ipatch);
}

void
ags_ipatch_class_init(AgsIpatchClass *ipatch)
{
  GObjectClass *gobject;
  GParamSpec *param_spec;

  ags_ipatch_parent_class = g_type_class_peek_parent(ipatch);

  /* GObjectClass */
  gobject = (GObjectClass *) ipatch;

  gobject->set_property = ags_ipatch_set_property;
  gobject->get_property = ags_ipatch_get_property;

  gobject->finalize = ags_ipatch_finalize;

  /* properties */
  /**
   * AgsIpatch:soundcard:
   *
   * The assigned soundcard.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("soundcard",
				   i18n_pspec("soundcard of ipatch"),
				   i18n_pspec("The soundcard what ipatch has it's presets"),
				   G_TYPE_OBJECT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_SOUNDCARD,
				  param_spec);

  /**
   * AgsIpatch:filename:
   *
   * The assigned filename.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_pointer("filename",
				    i18n_pspec("the filename"),
				    i18n_pspec("The filename to open"),
				    G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_FILENAME,
				  param_spec);

  /**
   * AgsIpatch:mode:
   *
   * The assigned mode.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_pointer("mode",
				    i18n_pspec("the mode"),
				    i18n_pspec("The mode to open the file"),
				    G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_MODE,
				  param_spec);
}

void
ags_ipatch_connectable_interface_init(AgsConnectableInterface *connectable)
{
  ags_ipatch_parent_connectable_interface = g_type_interface_peek_parent(connectable);

  connectable->connect = ags_ipatch_connect;
  connectable->disconnect = ags_ipatch_disconnect;
}

void
ags_ipatch_playable_interface_init(AgsPlayableInterface *playable)
{
  ags_ipatch_parent_playable_interface = g_type_interface_peek_parent(playable);

  playable->open = ags_ipatch_open;

  playable->nth_level = ags_ipatch_nth_level;
  playable->level_count = ags_ipatch_level_count;
  playable->sublevel_names = ags_ipatch_sublevel_names;
  playable->level_select = ags_ipatch_level_select;

  playable->iter_start = ags_ipatch_iter_start;
  playable->iter_next = ags_ipatch_iter_next;

  playable->info = ags_ipatch_info;

  playable->get_samplerate = ags_ipatch_get_samplerate;
  playable->get_format = ags_ipatch_get_format;

  playable->read = ags_ipatch_read;
  playable->read_int = ags_ipatch_read_int;

  playable->close = ags_ipatch_close;
}

void
ags_ipatch_init(AgsIpatch *ipatch)
{
  ipatch->flags = 0;

  ipatch->soundcard = NULL;
  ipatch->audio_signal= NULL;

  ipatch->file = NULL;

  ipatch->filename = NULL;
  ipatch->mode = AGS_IPATCH_READ;

  ipatch->handle = NULL;
  ipatch->error = NULL;

  ipatch->base = NULL;
  ipatch->reader = NULL;

  ipatch->samples = NULL;
  ipatch->iter = NULL;

  ipatch->nth_level = 0;
}

void
ags_ipatch_set_property(GObject *gobject,
			guint prop_id,
			const GValue *value,
			GParamSpec *param_spec)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(gobject);

  switch(prop_id){
  case PROP_SOUNDCARD:
    {
      GObject *soundcard;
      
      soundcard = (GObject *) g_value_get_object(value);

      if(soundcard == ((GObject *) ipatch->soundcard)){
	return;
      }

      if(ipatch->soundcard != NULL){
	g_object_unref(ipatch->soundcard);
      }
      
      if(soundcard != NULL){
	g_object_ref(G_OBJECT(soundcard));
      }
      
      ipatch->soundcard = (GObject *) soundcard;
    }
    break;
  case PROP_FILENAME:
    {
      gchar *filename;

      filename = (gchar *) g_value_get_pointer(value);

      ags_playable_open(AGS_PLAYABLE(ipatch), g_strdup(filename));
    }
    break;
  case PROP_MODE:
    {
      gchar *mode;
      
      mode = (gchar *) g_value_get_pointer(value);
      
      ipatch->mode = mode;

#ifdef AGS_WITH_LIBINSTPATCH      
      if(ipatch->handle != NULL){
	GError *error;

	error = NULL;

	ipatch_file_default_open_method(ipatch->handle,
					mode,
					&error);

	if(error != NULL){
	  g_warning("%s", error->message);
	}
      }
#endif
    }
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
    break;
  }
}

void
ags_ipatch_get_property(GObject *gobject,
			guint prop_id,
			GValue *value,
			GParamSpec *param_spec)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(gobject);

  switch(prop_id){
  case PROP_SOUNDCARD:
    {
      g_value_set_object(value, ipatch->soundcard);
    }
    break;
  case PROP_FILENAME:
    {
      g_value_set_pointer(value, ipatch->filename);
    }
    break;
  case PROP_MODE:
    {
      g_value_set_pointer(value, ipatch->mode);
    }
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
    break;
  }
}

void
ags_ipatch_connect(AgsConnectable *connectable)
{
  ags_ipatch_parent_connectable_interface->connect(connectable);

  /* empty */
}

void
ags_ipatch_disconnect(AgsConnectable *connectable)
{
  ags_ipatch_parent_connectable_interface->disconnect(connectable);

  /* empty */
}

gboolean
ags_ipatch_check_suffix(gchar *filename)
{
  if(g_str_has_suffix(filename, ".sf2") ||
     g_str_has_suffix(filename, ".dls") ||
     g_str_has_suffix(filename, ".gig")){
    return(TRUE);
  }

  return(FALSE);
}

gboolean
ags_ipatch_open(AgsPlayable *playable, gchar *filename)
{
  AgsIpatch *ipatch;
#ifdef AGS_WITH_LIBINSTPATCH
  IpatchFileIOFuncs *io_funcs;
#endif
  GError *error;
  
#ifdef AGS_DEBUG
  g_message("open %s with libinstpatch.", filename);
#endif

  ipatch = AGS_IPATCH(playable);

  ipatch->filename = filename;
  error = NULL;

#ifdef AGS_WITH_LIBINSTPATCH
  ipatch->handle = ipatch_file_identify_open(ipatch->filename,
					     &error);

  if(error != NULL){
    g_warning("%s", error->message);
  }

  if(ipatch->handle == NULL){
    return(FALSE);
  }

  if(IPATCH_IS_DLS_FILE(ipatch->handle->file)){
    ipatch->flags |= AGS_IPATCH_DLS2;

    //TODO:JK: implement me
  }else if(g_str_has_suffix(filename, ".sf2")){
    /*  */
    ipatch->flags |= AGS_IPATCH_SF2;

    /*  */
    ipatch->reader = (GObject *) ags_ipatch_sf2_reader_new();
    AGS_IPATCH_SF2_READER(ipatch->reader)->ipatch = ipatch;

    AGS_IPATCH_SF2_READER(ipatch->reader)->reader = ipatch_sf2_reader_new(ipatch->handle);

    error = NULL;
    ipatch->base = (IpatchBase *) ipatch_sf2_reader_load(AGS_IPATCH_SF2_READER(ipatch->reader)->reader,
							 &error);

    error = NULL;
    AGS_IPATCH_SF2_READER(ipatch->reader)->sf2 = (IpatchSF2 *) ipatch_convert_object_to_type((GObject *) ipatch->handle->file,
											     IPATCH_TYPE_SF2,
											     &error);

    if(error != NULL){
      g_warning("%s", error->message);
    }

    /* load samples */
    ipatch->samples = (IpatchList *) ipatch_container_get_children(IPATCH_CONTAINER(ipatch->base),
								   IPATCH_TYPE_SF2_SAMPLE);
    
    while(g_static_rec_mutex_unlock_full(((IpatchItem *) (ipatch->base))->mutex) != 0);
  }else if(IPATCH_IS_GIG_FILE(ipatch->handle->file)){
    ipatch->flags |= AGS_IPATCH_GIG;

    //TODO:JK: implement me
  }
#endif

  if(error == NULL){
    return(TRUE);
  }else{
    return(FALSE);
  }
}

guint
ags_ipatch_nth_level(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(playable);

  return(ipatch->nth_level);
}

guint
ags_ipatch_level_count(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(playable);

  if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
    return(4);
  }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
    return(4);
  }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
    return(4);
  }

  return(0);
}

gchar**
ags_ipatch_sublevel_names(AgsPlayable *playable)
{
  AgsIpatch *ipatch;
  AgsIpatchSF2Reader *ipatch_sf2_reader;
#ifdef AGS_WITH_LIBINSTPATCH
  IpatchItem *ipatch_item;
  IpatchList *ipatch_list;
#endif
  GList *list;
  gchar **names;
  gchar *name;
  guint sublevel;
  guint i;

  ipatch = AGS_IPATCH(playable);
  list = NULL;

  names = (gchar **) malloc(1 * sizeof(gchar*));
  names[0] = NULL;

#ifdef AGS_WITH_LIBINSTPATCH
  if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
    ipatch_sf2_reader = AGS_IPATCH_SF2_READER(ipatch->reader);
    
    sublevel = ipatch->nth_level;

    switch(sublevel){
    case AGS_SF2_FILENAME:
      {
	names = realloc(names, 2 * sizeof(char*));

	names[0] = ipatch_sf2_reader->ipatch->filename;
	names[1] = NULL;

	return(names);
      }
    case AGS_SF2_PHDR:
      {
	ipatch_list = ipatch_container_get_children(IPATCH_CONTAINER(ipatch_sf2_reader->sf2),
						    IPATCH_TYPE_SF2_PRESET);

	while(g_static_rec_mutex_unlock_full(((IpatchItem *) (ipatch_sf2_reader->sf2))->mutex) != 0);
	
	if(ipatch_list != NULL){
	  list = ipatch_list->items;
	}else{
	  return(names);
	}
      }
      break;
    case AGS_SF2_IHDR:
      {
	GList *tmp;

	ipatch_list = ipatch_sf2_preset_get_zones(ipatch_sf2_reader->preset);
	tmp = ipatch_list->items;

	while(tmp != NULL){
	  list = g_list_prepend(list, ipatch_sf2_zone_get_link_item(IPATCH_SF2_ZONE(tmp->data)));

	  tmp = tmp->next;
	}

	if(list != NULL){
	  list = g_list_reverse(list);
	}else{
	  return(names);
	}
      }
      break;
    case AGS_SF2_SHDR:
      {
	GList *tmp;

	ipatch_list = ipatch_sf2_preset_get_zones(ipatch_sf2_reader->instrument);
	tmp = ipatch_list->items;
	
	while(tmp != NULL){
	  list = g_list_prepend(list, ipatch_sf2_zone_get_link_item(IPATCH_SF2_ZONE(tmp->data)));

	  tmp = tmp->next;
	}

	if(list != NULL){
	  list = g_list_reverse(list);
	}else{
	  return(names);
	}
      }
      break;
    };
  }

  for(i = 0; list != NULL; i++){
    switch(sublevel){
    case AGS_SF2_PHDR:
      {
	if(IPATCH_IS_SF2_PRESET(list->data)){
	  names = (gchar **) realloc(names, (i + 2) * sizeof(char*));
	  names[i] = ipatch_sf2_preset_get_name(IPATCH_SF2_PRESET(list->data));
	}
      }
      break;
    case AGS_SF2_IHDR:
      {
	if(IPATCH_IS_SF2_INST(list->data)){
	  names = (gchar **) realloc(names, (i + 2) * sizeof(char*));
	  names[i] = ipatch_sf2_inst_get_name(IPATCH_SF2_INST(list->data));
	}
      }
      break;
    case AGS_SF2_SHDR:
      {
	if(IPATCH_IS_SF2_SAMPLE(list->data)){
	  names = (gchar **) realloc(names, (i + 2) * sizeof(char*));
	  names[i] = ipatch_sf2_sample_get_name(IPATCH_SF2_SAMPLE(list->data));
	}
      }
      break;
    };
    
    list = list->next;
  }
#endif
  
  if(i > 0){
    names[i] = NULL;
  }

  return(names);
}

void
ags_ipatch_level_select(AgsPlayable *playable,
			guint nth_level, gchar *sublevel_name,
			GError **error)
{
  AgsIpatch *ipatch;
  AgsIpatchSF2Reader *ipatch_sf2_reader;
  gboolean success;

  GError *this_error;

  ipatch = AGS_IPATCH(playable);

#ifdef AGS_WITH_LIBINSTPATCH
  if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
    ipatch_sf2_reader = AGS_IPATCH_SF2_READER(ipatch->reader);

    //TODO:JK: apply mods and gens

    if(sublevel_name == NULL){
      ipatch->nth_level = 0;
      ipatch_sf2_reader->selected[0] = NULL;
    }else{
      IpatchList *ipatch_list;
      IpatchItem *ipatch_item;
      GList *list;

      if(nth_level == 0 && !g_strcmp0(ipatch_sf2_reader->ipatch->filename, sublevel_name)){
	ipatch->nth_level = 0;

	if(ipatch_sf2_reader->selected[0] != NULL){
	  g_free(ipatch_sf2_reader->selected[0]);
	}
	
	ipatch_sf2_reader->selected[0] = g_strdup(sublevel_name);
	return;
      }

      if(nth_level == 1){
	ipatch->nth_level = 1;

	if(ipatch_sf2_reader->selected[1] != NULL){
	  g_free(ipatch_sf2_reader->selected[1]);
	}
	
	ipatch_sf2_reader->selected[1] = g_strdup(sublevel_name);

	/* preset */
	ipatch_list = ipatch_container_get_children(IPATCH_CONTAINER(ipatch_sf2_reader->sf2),
						    IPATCH_TYPE_SF2_PRESET);

	while(g_static_rec_mutex_unlock_full(((IpatchItem *) (ipatch_sf2_reader->sf2))->mutex) != 0);

	if(ipatch_list == NULL){
	  ipatch->iter = NULL;
	    
	  return;
	}
	  
	list = ipatch_list->items;
	
	while(list != NULL){
	  if(!g_strcmp0(ipatch_sf2_preset_get_name(IPATCH_SF2_PRESET(list->data)), sublevel_name)){
	    /* some extra code for bank and program */
	    //	    ipatch_sf2_preset_get_midi_locale(IPATCH_SF2_PRESET(list->data),
	    //				      &(ipatch_sf2_reader->bank),
	    //				      &(ipatch_sf2_reader->program));

	    //	    g_message("bank %d program %d\n", ipatch_sf2_reader->bank, ipatch_sf2_reader->program);

	    this_error = NULL;
	    ipatch_sf2_reader->preset = (IpatchContainer *) IPATCH_SF2_PRESET(list->data);

	    break;
	  }

	  list = list->next;
	}

	ipatch->iter = list;
      }else{
	if(nth_level == 2){
	  GList *tmp;

	  ipatch->nth_level = 2;

	  if(ipatch_sf2_reader->selected[2] != NULL){
	    g_free(ipatch_sf2_reader->selected[2]);
	  }
	  
	  ipatch_sf2_reader->selected[2] = g_strdup(sublevel_name);

	  /* instrument */
	  ipatch_list = ipatch_sf2_preset_get_zones(ipatch_sf2_reader->preset);

	  if(ipatch_list == NULL){
	    ipatch->iter = NULL;

	    return;
	  }
	  
	  list = NULL;
	  tmp = ipatch_list->items;

	  while(tmp != NULL){
	    list = g_list_prepend(list, ipatch_sf2_zone_get_link_item(IPATCH_SF2_ZONE(tmp->data)));

	    if(!g_strcmp0(IPATCH_SF2_INST(list->data)->name, sublevel_name)){
	      ipatch_sf2_reader->instrument = (IpatchContainer *) IPATCH_SF2_INST(list->data);
	    }

	    tmp = tmp->next;
	  }

	  ipatch->iter = g_list_reverse(list);
	}else if(ipatch->nth_level == 3){
	  GList *tmp;

	  ipatch->nth_level = 3;

	  if(ipatch_sf2_reader->selected[3] != NULL){
	    g_free(ipatch_sf2_reader->selected[3]);
	  }
	  
	  ipatch_sf2_reader->selected[3] = g_strdup(sublevel_name);

	  /* sample */
	  ipatch_list = ipatch_sf2_preset_get_zones(ipatch_sf2_reader->instrument);

	  if(ipatch_list == NULL){
	    ipatch->iter = NULL;

	    return;
	  }
	  
	  list = NULL;
	  tmp = ipatch_list->items;
	
	  while(tmp != NULL){
	    list = g_list_prepend(list, ipatch_sf2_zone_get_link_item(IPATCH_SF2_ZONE(tmp->data)));
	    
	    if(!g_ascii_strcasecmp(IPATCH_SF2_SAMPLE(list->data)->name,
				   sublevel_name)){
	      ipatch_sf2_reader->sample = (IpatchContainer *) IPATCH_SF2_SAMPLE(list->data);
	    }

	    tmp = tmp->next;
	  }

	  ipatch->iter = g_list_reverse(list);
	}else{
	  g_set_error(error,
		      AGS_PLAYABLE_ERROR,
		      AGS_PLAYABLE_ERROR_NO_SUCH_LEVEL,
		      "no level called %s in soundfont2 file: %s",
		      sublevel_name, ipatch_sf2_reader->ipatch->filename);
	}
      }
    }
  }
#endif
}

void
ags_ipatch_level_up(AgsPlayable *playable, guint levels, GError **error)
{
  AgsIpatch *ipatch;
  guint i;

  if(levels == 0)
    return;

  ipatch = AGS_IPATCH(playable);

  if(ipatch->nth_level >= levels){
    ipatch->nth_level -= levels;
  }else{
    g_set_error(error,
		AGS_PLAYABLE_ERROR,
		AGS_PLAYABLE_ERROR_NO_SUCH_LEVEL,
		"Not able to go %u steps higher in soundfont2 file: %s",
		levels, ipatch->filename);
  }
}

void
ags_ipatch_iter_start(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(playable);

  if(ipatch->nth_level == 3){
    if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }
  }
}

gboolean
ags_ipatch_iter_next(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

  ipatch = AGS_IPATCH(playable);

  if(ipatch->iter != NULL){
    ipatch->iter = ipatch->iter->next;

    return(TRUE);
  }else{
    return(FALSE);
  }
}

void
ags_ipatch_info(AgsPlayable *playable,
		guint *channels, guint *frames,
		guint *loop_start, guint *loop_end,
		GError **error)
{
  AgsIpatch *ipatch;

#ifdef AGS_WITH_LIBINSTPATCH
  IpatchSample *sample;
#endif
  
  ipatch = AGS_IPATCH(playable);

  if(ipatch->iter == NULL){
    if(channels != NULL){
      *channels = 0;
    }

    if(frames != NULL){
      *frames = 0;
    }
  
    if(loop_start != NULL){
      *loop_start = 0;
    }
  
    if(loop_end != NULL){
      *loop_end = 0;
    }

    if(error != NULL){
      g_set_error(error,
		  AGS_PLAYABLE_ERROR,
		  AGS_PLAYABLE_ERROR_NO_SAMPLE,
		  "no sample selected for file: %s",
		  ipatch->filename);
    }

    return;
  }

#ifdef AGS_WITH_LIBINSTPATCH
  sample = IPATCH_SAMPLE(ipatch->iter->data);
  g_object_get(G_OBJECT(sample),
	       "sample-size", frames,
	       "loop-start", loop_start,
	       "loop-end", loop_end,
	       NULL);
#endif
  
  //TODO:JK: verify me
  if(channels != NULL){
    *channels = AGS_IPATCH_DEFAULT_CHANNELS;
  }
}

guint
ags_ipatch_get_samplerate(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

#ifdef AGS_WITH_LIBINSTPATCH
  IpatchSample *sample;
#endif
  
  guint samplerate;
  
  ipatch = AGS_IPATCH(playable);

  samplerate = 0;
  
#ifdef AGS_WITH_LIBINSTPATCH
  if(ipatch->nth_level == 3){
    if(ipatch->iter != NULL){
      sample = IPATCH_SAMPLE(ipatch->iter->data);
    }else{
      sample = NULL;
    }
  }else{
    sample = NULL;

    if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
      AgsIpatchSF2Reader *reader;

      reader = AGS_IPATCH_SF2_READER(ipatch->reader);
      sample = (IpatchSample *) ipatch_sf2_find_sample(reader->sf2,
						       reader->selected[3],
						       NULL);
    }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }
  }

  g_object_get(sample,
	       "sample-rate", &samplerate,
	       NULL);
#endif
  
  return(samplerate);
}

guint
ags_ipatch_get_format(AgsPlayable *playable)
{
  AgsIpatch *ipatch;

#ifdef AGS_WITH_LIBINSTPATCH
  IpatchSample *sample;
#endif
  
  guint format;
  
  ipatch = AGS_IPATCH(playable);

#ifdef AGS_WITH_LIBINSTPATCH
  sample = NULL;

  if(ipatch->nth_level == 3){
    if(ipatch->iter != NULL){
      sample = IPATCH_SAMPLE(ipatch->iter->data);
    }
  }else{
    if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
      AgsIpatchSF2Reader *reader;

      reader = AGS_IPATCH_SF2_READER(ipatch->reader);
      sample = (IpatchSample *) ipatch_sf2_find_sample(reader->sf2,
						       reader->selected[3],
						       NULL);
    }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }
  }

  format = 0;
  
  if(sample != NULL){
    g_object_get(sample,
		 "sample-format", &format,
		 NULL);
  }

  switch(format){
  case IPATCH_SAMPLE_8BIT:
    return(AGS_AUDIO_BUFFER_UTIL_S8);
  case IPATCH_SAMPLE_16BIT:
    return(AGS_AUDIO_BUFFER_UTIL_S16);
  case IPATCH_SAMPLE_24BIT:
    return(AGS_AUDIO_BUFFER_UTIL_S24);
  case IPATCH_SAMPLE_32BIT:
    return(AGS_AUDIO_BUFFER_UTIL_S32);
  case IPATCH_SAMPLE_FLOAT:
    return(AGS_AUDIO_BUFFER_UTIL_FLOAT);
  case IPATCH_SAMPLE_DOUBLE:
    return(AGS_AUDIO_BUFFER_UTIL_DOUBLE);
  default:
    g_warning("ags_ipatch_get_format() - unknown format");
    return(0);
  }
#else
  return(0);
#endif
}

double*
ags_ipatch_read(AgsPlayable *playable, guint channel,
		GError **error)
{
  AgsIpatch *ipatch;

#ifdef AGS_WITH_LIBINSTPATCH
  IpatchSample *sample;
#endif
  
  double *buffer, *source;
  guint channels, frames;
  guint loop_start, loop_end;
  guint i;

  GError *this_error;

  ipatch = AGS_IPATCH(playable);

  this_error = NULL;
  ags_playable_info(playable,
		    &channels, &frames,
		    &loop_start, &loop_end,
		    &this_error);

  if(this_error != NULL){
    g_warning("%s", this_error->message);
  }

  if(channels == 0 ||
     frames == 0){
    buffer = NULL;
  }else{
    buffer = (double *) malloc(channels * frames * sizeof(double));
  }
  
#ifdef AGS_WITH_LIBINSTPATCH
  if(ipatch->nth_level == 3){
    if(AGS_IPATCH_SF2_READER(ipatch->reader)->sample != NULL){
      sample = AGS_IPATCH_SF2_READER(ipatch->reader)->sample;
    }else{
      if(ipatch->iter != NULL){
	sample = IPATCH_SAMPLE(ipatch->iter->data);
      }else{
	sample = NULL;
      }
    }
  }else{
    sample = NULL;

    if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
      AgsIpatchSF2Reader *reader;

      reader = AGS_IPATCH_SF2_READER(ipatch->reader);

      this_error = NULL;
      sample = (IpatchSample *) ipatch_sf2_find_sample(reader->sf2,
						       reader->selected[3],
						       NULL);
    }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }
  }

  this_error = NULL;
  ipatch_sample_read_transform(sample,
			       0,
			       frames,
			       buffer,
			       IPATCH_SAMPLE_DOUBLE | IPATCH_SAMPLE_MONO,
			       IPATCH_SAMPLE_UNITY_CHANNEL_MAP,
			       &this_error);
      
  if(this_error != NULL){
    g_warning("%s", this_error->message);
  }
#endif
  
  return(buffer);
}

int*
ags_ipatch_read_int(AgsPlayable *playable, guint channel,
		    GError **error)
{
  AgsIpatch *ipatch;

#ifdef AGS_WITH_LIBINSTPATCH
  IpatchSample *sample;
#endif
  
  int *buffer;
  gint32 *source;
  guint channels, frames;
  guint loop_start, loop_end;
  guint i;

  GError *this_error;

  ipatch = AGS_IPATCH(playable);

  this_error = NULL;
  ags_playable_info(playable,
		    &channels, &frames,
		    &loop_start, &loop_end,
		    &this_error);

  if(this_error != NULL){
    g_warning("%s", this_error->message);
  }

  if(channels == 0 ||
     frames == 0){
    source = NULL;
  }else{
    source = (gint32 *) malloc(channels * frames * sizeof(gint32));
  }
  
#ifdef AGS_WITH_LIBINSTPATCH
  if(ipatch->nth_level == 3){
    if(AGS_IPATCH_SF2_READER(ipatch->reader)->sample != NULL){
      sample = AGS_IPATCH_SF2_READER(ipatch->reader)->sample;
    }else{
      if(ipatch->iter != NULL){
	sample = IPATCH_SAMPLE(ipatch->iter->data);
      }else{
	sample = NULL;
      }
    }
  }else{
    sample = NULL;

    if((AGS_IPATCH_DLS2 & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }else if((AGS_IPATCH_SF2 & (ipatch->flags)) != 0){
      AgsIpatchSF2Reader *reader;

      reader = AGS_IPATCH_SF2_READER(ipatch->reader);

      this_error = NULL;
      sample = (IpatchSample *) ipatch_sf2_find_sample(reader->sf2,
						       reader->selected[3],
						       NULL);
    }else if((AGS_IPATCH_GIG & (ipatch->flags)) != 0){
      //TODO:JK: implement me
    }
  }

  this_error = NULL;
  ipatch_sample_read_transform(sample,
			       0,
			       frames,
			       source,
			       IPATCH_SAMPLE_32BIT | IPATCH_SAMPLE_MONO,
			       IPATCH_SAMPLE_UNITY_CHANNEL_MAP,
			       &this_error);
      
  if(this_error != NULL){
    g_warning("%s", this_error->message);
  }
#endif
  
  if(sizeof(int) == 8){
    if(channels == 0 ||
      frames == 0){
    buffer = NULL;
  }else{
    buffer = (int *) malloc(channels * frames * sizeof(int));
    ags_audio_buffer_util_clear_buffer(buffer, 1,
      frames, AGS_AUDIO_BUFFER_UTIL_S64);
    ags_audio_buffer_util_copy_s32_to_s64(buffer, 1,
      source, 1,
      frames);

    free(source);
  }
  }else{
  buffer = source;
  }
  
  return(buffer);
}

void
ags_ipatch_close(AgsPlayable *playable)
{
  /* empty */
}

void
ags_ipatch_finalize(GObject *gobject)
{
  G_OBJECT_CLASS(ags_ipatch_parent_class)->finalize(gobject);

  /* empty */
}

/**
 * ags_ipatch_new:
 *
 * Creates an #AgsIpatch.
 *
 * Returns: an empty #AgsIpatch.
 *
 * Since: 1.0.0
 */
AgsIpatch*
ags_ipatch_new()
{
  AgsIpatch *ipatch;

  ipatch = (AgsIpatch *) g_object_new(AGS_TYPE_IPATCH,
				      NULL);

  return(ipatch);
}
