/* FluidSynth - A Software Synthesizer
 *
 * Copyright (C) 2003  Peter Hanappe and others.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */

#include "fluidsynth_priv.h"
#include "fluid_sys.h"
#include "fluid_hash.h"
#include "fluid_synth.h"
#include "fluid_cmd.h"
#include "fluid_adriver.h"
#include "fluid_mdriver.h"
#include "fluid_settings.h"
#include "fluid_midi.h"

/* Defined in fluid_filerenderer.c */
extern void fluid_file_renderer_settings (fluid_settings_t* settings);

/* maximum allowed components of a settings variable (separated by '.') */
#define MAX_SETTINGS_TOKENS 8	/* currently only a max of 3 are used */
#define MAX_SETTINGS_LABEL 256	/* max length of a settings variable label */

static void fluid_settings_init(fluid_settings_t* settings);
static void fluid_settings_key_destroy_func(void* value);
static void fluid_settings_value_destroy_func(void* value);
static int fluid_settings_tokenize(const char *s, char *buf, char **ptr);

/* Common structure to all settings nodes */
typedef struct {
  int type;             /**< fluid_types_enum */
} fluid_setting_node_t; 

typedef struct {
  fluid_setting_node_t node;
  char* value;
  char* def;
  int hints;
  fluid_list_t* options;
  fluid_str_update_t update;
  void* data;
} fluid_str_setting_t;

typedef struct {
  fluid_setting_node_t node;
  double value;
  double def;
  double min;
  double max;
  int hints;
  fluid_num_update_t update;
  void* data;
} fluid_num_setting_t;

typedef struct {
  fluid_setting_node_t node;
  int value;
  int def;
  int min;
  int max;
  int hints;
  fluid_int_update_t update;
  void* data;
} fluid_int_setting_t;

typedef struct {
  fluid_setting_node_t node;
  fluid_hashtable_t *hashtable;
} fluid_set_setting_t;


static fluid_str_setting_t*
new_fluid_str_setting(const char* value, const char* def, int hints, fluid_str_update_t fun, void* data)
{
  fluid_str_setting_t* str;

  str = FLUID_NEW(fluid_str_setting_t);

  if (!str)
  {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }

  str->node.type = FLUID_STR_TYPE;
  str->value = value? FLUID_STRDUP(value) : NULL;
  str->def = def? FLUID_STRDUP(def) : NULL;
  str->hints = hints;
  str->options = NULL;
  str->update = fun;
  str->data = data;
  return str;
}

static void
delete_fluid_str_setting(fluid_str_setting_t* str)
{
  if (!str) return;

  if (str->value) FLUID_FREE(str->value);
  if (str->def) FLUID_FREE(str->def);

  if (str->options) {
    fluid_list_t* list = str->options;

    while (list) {
      FLUID_FREE (list->data);
      list = fluid_list_next(list);
    }

    delete_fluid_list(str->options);
  }

  FLUID_FREE(str);
}


static fluid_num_setting_t*
new_fluid_num_setting(double min, double max, double def,
		     int hints, fluid_num_update_t fun, void* data)
{
  fluid_num_setting_t* setting;

  setting = FLUID_NEW(fluid_num_setting_t);

  if (!setting)
  {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }

  setting->node.type = FLUID_NUM_TYPE;
  setting->value = def;
  setting->def = def;
  setting->min = min;
  setting->max = max;
  setting->hints = hints;
  setting->update = fun;
  setting->data = data;
  return setting;
}

static void
delete_fluid_num_setting(fluid_num_setting_t* setting)
{
  if (setting) FLUID_FREE(setting);
}

static fluid_int_setting_t*
new_fluid_int_setting(int min, int max, int def,
		     int hints, fluid_int_update_t fun, void* data)
{
  fluid_int_setting_t* setting;

  setting = FLUID_NEW(fluid_int_setting_t);

  if (!setting)
  {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }

  setting->node.type = FLUID_INT_TYPE;
  setting->value = def;
  setting->def = def;
  setting->min = min;
  setting->max = max;
  setting->hints = hints;
  setting->update = fun;
  setting->data = data;
  return setting;
}

static void
delete_fluid_int_setting(fluid_int_setting_t* setting)
{
  if (setting) FLUID_FREE(setting);
}

static fluid_set_setting_t*
new_fluid_set_setting(void)
{
  fluid_set_setting_t* setting;

  setting = FLUID_NEW(fluid_set_setting_t);

  if (!setting)
  {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }

  setting->node.type = FLUID_SET_TYPE;
  setting->hashtable = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal,
                                                fluid_settings_key_destroy_func,
                                                fluid_settings_value_destroy_func);
  if (!setting->hashtable)
  {
    FLUID_FREE (setting);
    return NULL;
  }

  return setting;
}

static void
delete_fluid_set_setting(fluid_set_setting_t* setting)
{
  if (setting)
  {
    delete_fluid_hashtable(setting->hashtable);
    FLUID_FREE(setting);
  }
}

/**
 * Create a new settings object
 * @return the pointer to the settings object
 */
fluid_settings_t *
new_fluid_settings(void)
{
  fluid_settings_t* settings;

  settings = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal,
                                      fluid_settings_key_destroy_func,
                                      fluid_settings_value_destroy_func);
  if (settings == NULL) return NULL;

  fluid_rec_mutex_init (settings->mutex);
  fluid_settings_init(settings);
  return settings;
}

/**
 * Delete the provided settings object
 * @param settings a settings object
 */
void
delete_fluid_settings(fluid_settings_t* settings)
{
  fluid_return_if_fail (settings != NULL);

  fluid_rec_mutex_destroy (settings->mutex);
  delete_fluid_hashtable(settings);
}

/* Settings hash key destroy function */
static void
fluid_settings_key_destroy_func(void* value)
{
  FLUID_FREE (value);   /* Free the string key value */
}

/* Settings hash value destroy function */
static void
fluid_settings_value_destroy_func(void* value)
{
  fluid_setting_node_t *node = value;

  switch (node->type) {
  case FLUID_NUM_TYPE:
    delete_fluid_num_setting((fluid_num_setting_t*) value);
    break;
  case FLUID_INT_TYPE:
    delete_fluid_int_setting((fluid_int_setting_t*) value);
    break;
  case FLUID_STR_TYPE:
    delete_fluid_str_setting((fluid_str_setting_t*) value);
    break;
  case FLUID_SET_TYPE:
    delete_fluid_set_setting((fluid_set_setting_t*) value);
    break;
  }
}

void
fluid_settings_init(fluid_settings_t* settings)
{
  fluid_return_if_fail (settings != NULL);

  fluid_synth_settings(settings);
  fluid_shell_settings(settings);
  fluid_player_settings(settings);
  fluid_file_renderer_settings(settings);
  fluid_audio_driver_settings(settings);
  fluid_midi_driver_settings(settings);
}

static int
fluid_settings_tokenize(const char *s, char *buf, char **ptr)
{
  char *tokstr, *tok;
  int n = 0;

  if (strlen (s) > MAX_SETTINGS_LABEL)
  {
    FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max length of %d chars",
	      MAX_SETTINGS_LABEL);
    return 0;
  }

  FLUID_STRCPY(buf, s);	/* copy string to buffer, since it gets modified */
  tokstr = buf;

  while ((tok = fluid_strtok (&tokstr, ".")))
  {
    if (n >= MAX_SETTINGS_TOKENS)
    {
      FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max token count of %d",
		MAX_SETTINGS_TOKENS);
      return 0;
    } else
        ptr[n++] = tok;
  }

  return n;
}

/**
 * Get a setting name, value and type
 *
 * @param settings a settings object
 * @param name Settings name
 * @param value Location to store setting node if found
 * @return 1 if the node exists, 0 otherwise
 */
static int
fluid_settings_get(fluid_settings_t* settings, const char *name,
                   fluid_setting_node_t **value)
{
  fluid_hashtable_t* table = settings;
  fluid_setting_node_t *node = NULL;
  char* tokens[MAX_SETTINGS_TOKENS];
  char buf[MAX_SETTINGS_LABEL+1];
  int ntokens;
  int n;

  ntokens = fluid_settings_tokenize (name, buf, tokens);

  if (table == NULL || ntokens <= 0) return 0;

  for (n = 0; n < ntokens; n++) {

    node = fluid_hashtable_lookup(table, tokens[n]);
    if (!node) return 0;

    table = (node->type == FLUID_SET_TYPE) ? ((fluid_set_setting_t *)node)->hashtable : NULL;
  }

  if (value) *value = node;

  return 1;
}

/**
 * Set a setting name, value and type, replacing it if already exists
 *
 * @param settings a settings object
 * @param name Settings name
 * @param value Node instance to assign (used directly)
 * @return 1 if the value has been set, zero otherwise
 */
static int
fluid_settings_set(fluid_settings_t* settings, const char *name, void* value)
{
  fluid_hashtable_t* table = settings;
  fluid_setting_node_t *node;
  char* tokens[MAX_SETTINGS_TOKENS];
  char buf[MAX_SETTINGS_LABEL+1];
  int n, num;
  char *dupname;

  num = fluid_settings_tokenize (name, buf, tokens) - 1;
  if (num == 0)
    return 0;

  for (n = 0; n < num; n++) {

    node = fluid_hashtable_lookup(table, tokens[n]);

    if (node) {

      if (node->type == FLUID_SET_TYPE) {
	table = ((fluid_set_setting_t *)node)->hashtable;
      } else {
	/* path ends prematurely */
	FLUID_LOG(FLUID_WARN, "'%s' is not a node", name[n]);
	return 0;
      }

    } else {
      /* create a new node */
      fluid_set_setting_t* setnode;

      dupname = FLUID_STRDUP (tokens[n]);
      setnode = new_fluid_set_setting ();

      if (!dupname || !setnode)
      {
        if (dupname) FLUID_FREE (dupname);
        else FLUID_LOG(FLUID_ERR, "Out of memory");

        if (setnode) delete_fluid_set_setting (setnode);

        return 0;
      }

      fluid_hashtable_insert(table, dupname, setnode);
      table = setnode->hashtable;
    }
  }

  dupname = FLUID_STRDUP (tokens[num]);

  if (!dupname)
  {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return 0;
  }

  fluid_hashtable_insert(table, dupname, value);

  return 1;
}

/** returns 1 if the value has been registered correctly, 0
    otherwise */
int
fluid_settings_register_str(fluid_settings_t* settings, const char* name, const char* def, int hints,
			    fluid_str_update_t fun, void* data)
{
  fluid_setting_node_t *node;
  fluid_str_setting_t* setting;
  int retval;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (!fluid_settings_get(settings, name, &node)) {
    setting = new_fluid_str_setting(def, def, hints, fun, data);
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_str_setting (setting);
  } else {
    /* if variable already exists, don't change its value. */
    if (node->type == FLUID_STR_TYPE) {
      setting = (fluid_str_setting_t*) node;
      setting->update = fun;
      setting->data = data;
      setting->def = def? FLUID_STRDUP(def) : NULL;
      setting->hints = hints;
      retval = 1;
    } else {
      FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name);
      retval = 0;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/** returns 1 if the value has been register correctly, zero
    otherwise */
int
fluid_settings_register_num(fluid_settings_t* settings, const char* name, double def,
			    double min, double max, int hints,
			    fluid_num_update_t fun, void* data)
{
  fluid_setting_node_t *node;
  int retval;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  /* For now, all floating point settings are bounded below and above */
  hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE;

  fluid_rec_mutex_lock (settings->mutex);

  if (!fluid_settings_get(settings, name, &node)) {
    /* insert a new setting */
    fluid_num_setting_t* setting;
    setting = new_fluid_num_setting(min, max, def, hints, fun, data);
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_num_setting (setting);
  } else {
    if (node->type == FLUID_NUM_TYPE) {
      /* update the existing setting but don't change its value */
      fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
      setting->update = fun;
      setting->data = data;
      setting->min = min;
      setting->max = max;
      setting->def = def;
      setting->hints = hints;
      retval = 1;
    } else {
      /* type mismatch */
      FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name);
      retval = 0;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/** returns 1 if the value has been register correctly, zero
    otherwise. */
int
fluid_settings_register_int(fluid_settings_t* settings, const char* name, int def,
			    int min, int max, int hints,
			    fluid_int_update_t fun, void* data)
{
  fluid_setting_node_t *node;
  int retval;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  /* For now, all integer settings are bounded below and above */
  hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE;

  fluid_rec_mutex_lock (settings->mutex);

  if (!fluid_settings_get(settings, name, &node)) {
    /* insert a new setting */
    fluid_int_setting_t* setting;
    setting = new_fluid_int_setting(min, max, def, hints, fun, data);
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_int_setting (setting);
  } else {
    if (node->type == FLUID_INT_TYPE) {
      /* update the existing setting but don't change its value */
      fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
      setting->update = fun;
      setting->data = data;
      setting->min = min;
      setting->max = max;
      setting->def = def;
      setting->hints = hints;
      retval = 1;
    } else {
      /* type mismatch */
      FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name);
      retval = 0;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the type of the setting with the given name
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return the type for the named setting, or #FLUID_NO_TYPE when it does not exist
 */
int
fluid_settings_get_type(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  int type;

  fluid_return_val_if_fail (settings != NULL, FLUID_NO_TYPE);
  fluid_return_val_if_fail (name != NULL, FLUID_NO_TYPE);
  fluid_return_val_if_fail (name[0] != '\0', FLUID_NO_TYPE);

  fluid_rec_mutex_lock (settings->mutex);
  type = fluid_settings_get (settings, name, &node) ? node->type : FLUID_NO_TYPE;
  fluid_rec_mutex_unlock (settings->mutex);

  return (type);
}

/**
 * Get the hints for the named setting as an integer bitmap
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return the hints associated to the named setting if it exists, zero otherwise
 */
int
fluid_settings_get_hints(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  int hints = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)) {
    if (node->type == FLUID_NUM_TYPE) {
      fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
      hints = setting->hints;
    } else if (node->type == FLUID_STR_TYPE) {
      fluid_str_setting_t* setting = (fluid_str_setting_t*) node;
      hints = setting->hints;
    } else if (node->type == FLUID_INT_TYPE) {
      fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
      hints = setting->hints;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return hints;
}

/**
 * Ask whether the setting is changeable in real-time.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return non zero if the setting is changeable in real-time
 */
int
fluid_settings_is_realtime(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  int isrealtime = FALSE;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)) {
    if (node->type == FLUID_NUM_TYPE) {
      fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
      isrealtime = setting->update != NULL;
    } else if (node->type == FLUID_STR_TYPE) {
      fluid_str_setting_t* setting = (fluid_str_setting_t*) node;
      isrealtime = setting->update != NULL;
    } else if (node->type == FLUID_INT_TYPE) {
      fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
      isrealtime = setting->update != NULL;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return isrealtime;
}

/**
 * Set a string value for a named setting
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param str new string value
 * @return 1 if the value has been set, 0 otherwise
 */
int
fluid_settings_setstr(fluid_settings_t* settings, const char *name, const char *str)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get (settings, name, &node)) {
    if (node->type == FLUID_STR_TYPE) {
      fluid_str_setting_t *setting = (fluid_str_setting_t *)node;

      if (setting->value) FLUID_FREE (setting->value);
      setting->value = str ? FLUID_STRDUP (str) : NULL;

      /* Call under lock to keep update() synchronized with the current value */
      if (setting->update) (*setting->update)(setting->data, name, str);
      retval = 1;
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle yes/no for boolean values for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
      {
        if (FLUID_STRCMP (str, "yes") == 0)
        {
          setting->value = TRUE;
          if (setting->update) (*setting->update)(setting->data, name, TRUE);
        }
        else if (FLUID_STRCMP (str, "no") == 0)
        {
          setting->value = FALSE;
          if (setting->update) (*setting->update)(setting->data, name, FALSE);
        }
      }
    }
  } else {
    /* insert a new setting */
    fluid_str_setting_t* setting;
    setting = new_fluid_str_setting(str, NULL, 0, NULL, NULL);
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_str_setting (setting);
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Copy the value of a string setting
 * @param settings a settings object
 * @param name a setting's name
 * @param str Caller supplied buffer to copy string value to
 * @param len Size of 'str' buffer (no more than len bytes will be written, which
 *   will always include a zero terminator)
 * @return 1 if the value exists, 0 otherwise
 * @since 1.1.0
 *
 * Like fluid_settings_getstr() but is thread safe.  A size of 256 should be
 * more than sufficient for the string buffer.
 */
int
fluid_settings_copystr(fluid_settings_t* settings, const char *name,
                       char *str, int len)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (str != NULL, 0);
  fluid_return_val_if_fail (len > 0, 0);

  str[0] = 0;

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get (settings, name, &node))
  {
    if (node->type == FLUID_STR_TYPE)
    {
      fluid_str_setting_t *setting = (fluid_str_setting_t *)node;

      if (setting->value)
      {
        FLUID_STRNCPY (str, setting->value, len);
        str[len - 1] = 0;   /* Force terminate, in case of truncation */
      }

      retval = 1;
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle boolean integers for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
      {
        FLUID_STRNCPY (str, setting->value ? "yes" : "no", len);
        str[len - 1] = 0;   /* Force terminate, in case of truncation */

        retval = 1;
      }
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Duplicate the value of a string setting
 * @param settings a settings object
 * @param name a setting's name
 * @param str Location to store pointer to allocated duplicate string
 * @return 1 if the value exists and was successfully duplicated, 0 otherwise
 * @since 1.1.0
 *
 * Like fluid_settings_copystr() but allocates a new copy of the string.  Caller
 * owns the string and should free it with free() when done using it.
 */
int
fluid_settings_dupstr(fluid_settings_t* settings, const char *name, char** str)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (str != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node))
  {
    if (node->type == FLUID_STR_TYPE)
    {
      fluid_str_setting_t *setting = (fluid_str_setting_t *)node;

      if (setting->value)
      {
        *str = FLUID_STRDUP (setting->value);
        if (!*str) FLUID_LOG (FLUID_ERR, "Out of memory");
      }

      if (!setting->value || *str) retval = 1;    /* Don't set to 1 if out of memory */
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle boolean integers for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
      {
        *str = FLUID_STRDUP (setting->value ? "yes" : "no");
        if (!*str) FLUID_LOG (FLUID_ERR, "Out of memory");

        if (!setting->value || *str) retval = 1;    /* Don't set to 1 if out of memory */
      }
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the value of a string setting
 * @param settings a settings object
 * @param name a setting's name
 * @param str Location to store pointer to the settings string value
 * @return 1 if the value exists, 0 otherwise
 * @deprecated
 *
 * If the value does not exists, 'str' is set to NULL. Otherwise, 'str' will
 * point to the value. The application does not own the returned value and it
 * is valid only until a new value is assigned to the setting of the given name.
 *
 * NOTE: In a multi-threaded environment, caller must ensure that the setting
 * being read by fluid_settings_getstr() is not assigned during the
 * duration of callers use of the setting's value.  Use fluid_settings_copystr()
 * or fluid_settings_dupstr() which does not have this restriction.
 */
int
fluid_settings_getstr(fluid_settings_t* settings, const char *name, char** str)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (str != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node))
  {
    if (node->type == FLUID_STR_TYPE)
    {
      fluid_str_setting_t *setting = (fluid_str_setting_t *)node;
      *str = setting->value;
      retval = 1;
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle boolean integers for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
      {
        *str = setting->value ? "yes" : "no";
        retval = 1;
      }
    }
  }
  else *str = NULL;

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Test a string setting for some value.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param s a string to be tested
 * @return 1 if the value exists and is equal to 's', 0 otherwise
 */
int
fluid_settings_str_equal (fluid_settings_t* settings, const char *name, const char *s)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (s != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get (settings, name, &node))
  {
    if (node->type == FLUID_STR_TYPE)
    {
      fluid_str_setting_t *setting = (fluid_str_setting_t *)node;
      if (setting->value) retval = FLUID_STRCMP (setting->value, s) == 0;
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle boolean integers for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
        retval = FLUID_STRCMP (setting->value ? "yes" : "no", s) == 0;
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the default value of a string setting.  Note that the returned string is
 * not owned by the caller and should not be modified or freed.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return the default string value of the setting if it exists, NULL otherwise
 */
char*
fluid_settings_getstr_default(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  char *retval = NULL;

  fluid_return_val_if_fail (settings != NULL, NULL);
  fluid_return_val_if_fail (name != NULL, NULL);
  fluid_return_val_if_fail (name[0] != '\0', NULL);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get (settings, name, &node))
  {
    if (node->type == FLUID_STR_TYPE)
    {
      fluid_str_setting_t* setting = (fluid_str_setting_t*) node;
      retval = setting->def;
    }
    else if (node->type == FLUID_INT_TYPE)      /* Handle boolean integers for backwards compatibility */
    {
      fluid_int_setting_t *setting = (fluid_int_setting_t *)node;

      if (setting->hints & FLUID_HINT_TOGGLED)
        retval = setting->def ? "yes" : "no";
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Add an option to a string setting (like an enumeration value).
 * @param settings a settings object
 * @param name a setting's name
 * @param s option string to add
 * @return 1 if the setting exists and option was added, 0 otherwise
 *
 * Causes the setting's #FLUID_HINT_OPTIONLIST hint to be set.
 */
int
fluid_settings_add_option(fluid_settings_t* settings, const char *name, const char *s)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (s != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_STR_TYPE)) {
    fluid_str_setting_t* setting = (fluid_str_setting_t*) node;
    char* copy = FLUID_STRDUP(s);
    setting->options = fluid_list_append(setting->options, copy);
    setting->hints |= FLUID_HINT_OPTIONLIST;
    retval = 1;
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Remove an option previously assigned by fluid_settings_add_option().
 * @param settings a settings object
 * @param name a setting's name
 * @param s option string to remove
 * @return 1 if the setting exists and option was removed, 0 otherwise
 */
int
fluid_settings_remove_option(fluid_settings_t* settings, const char *name, const char* s)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (s != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_STR_TYPE)) {

    fluid_str_setting_t* setting = (fluid_str_setting_t*) node;
    fluid_list_t* list = setting->options;

    while (list) {
      char* option = (char*) fluid_list_get(list);
      if (FLUID_STRCMP(s, option) == 0) {
	FLUID_FREE (option);
	setting->options = fluid_list_remove_link(setting->options, list);
	retval = 1;
        break;
      }
      list = fluid_list_next(list);
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Set a numeric value for a named setting.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param val new setting's value
 * @return 1 if the value has been set, 0 otherwise
 */
int
fluid_settings_setnum(fluid_settings_t* settings, const char *name, double val)
{
  fluid_setting_node_t *node;
  fluid_num_setting_t* setting;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)) {
    if (node->type == FLUID_NUM_TYPE) {
      setting = (fluid_num_setting_t*) node;

      if (val < setting->min) val = setting->min;
      else if (val > setting->max) val = setting->max;

      setting->value = val;

      /* Call under lock to keep update() synchronized with the current value */
      if (setting->update) (*setting->update)(setting->data, name, val);
      retval = 1;
    }
  } else {
    /* insert a new setting */
    fluid_num_setting_t* setting;
    setting = new_fluid_num_setting(-1e10, 1e10, 0.0f, 0, NULL, NULL);
    setting->value = val;
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_num_setting (setting);
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the numeric value of a named setting
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param val variable pointer to receive the setting's numeric value
 * @return 1 if the value exists, 0 otherwise
 */
int
fluid_settings_getnum(fluid_settings_t* settings, const char *name, double* val)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (val != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_NUM_TYPE)) {
    fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
    *val = setting->value;
    retval = 1;
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the range of values of a numeric setting
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param min setting's range lower limit
 * @param max setting's range upper limit
 */
void
fluid_settings_getnum_range(fluid_settings_t* settings, const char *name,
                            double* min, double* max)
{
  fluid_setting_node_t *node;

  fluid_return_if_fail (settings != NULL);
  fluid_return_if_fail (name != NULL);
  fluid_return_if_fail (name[0] != '\0');
  fluid_return_if_fail (min != NULL);
  fluid_return_if_fail (max != NULL);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_NUM_TYPE)) {
    fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
    *min = setting->min;
    *max = setting->max;
  }

  fluid_rec_mutex_unlock (settings->mutex);
}

/**
 * Get the default value of a named numeric (double) setting
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return the default value if the named setting exists, 0.0f otherwise
 */
double
fluid_settings_getnum_default(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  double retval = 0.0;

  fluid_return_val_if_fail (settings != NULL, 0.0);
  fluid_return_val_if_fail (name != NULL, 0.0);
  fluid_return_val_if_fail (name[0] != '\0', 0.0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_NUM_TYPE)) {
    fluid_num_setting_t* setting = (fluid_num_setting_t*) node;
    retval = setting->def;
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Set an integer value for a setting
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param val new setting's integer value
 * @return 1 if the value has been set, 0 otherwise
 */
int
fluid_settings_setint(fluid_settings_t* settings, const char *name, int val)
{
  fluid_setting_node_t *node;
  fluid_int_setting_t* setting;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)) {
    if (node->type == FLUID_INT_TYPE) {
      setting = (fluid_int_setting_t*) node;

      if (val < setting->min) val = setting->min;
      else if (val > setting->max) val = setting->max;

      setting->value = val;

      /* Call under lock to keep update() synchronized with the current value */
      if (setting->update) (*setting->update)(setting->data, name, val);
      retval = 1;
    }
  } else {
    /* insert a new setting */
    fluid_int_setting_t* setting;
    setting = new_fluid_int_setting(INT_MIN, INT_MAX, 0, 0, NULL, NULL);
    setting->value = val;
    retval = fluid_settings_set(settings, name, setting);
    if (retval != 1) delete_fluid_int_setting (setting);
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get an integer value setting.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param val pointer to a variable to receive the setting's integer value
 * @return 1 if the value exists, 0 otherwise
 */
int
fluid_settings_getint(fluid_settings_t* settings, const char *name, int* val)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);
  fluid_return_val_if_fail (val != NULL, 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_INT_TYPE)) {
    fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
    *val = setting->value;
    retval = 1;
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Get the range of values of an integer setting
 * @param settings a settings object
 * @param name a setting's name
 * @param min setting's range lower limit
 * @param max setting's range upper limit
 */
void
fluid_settings_getint_range(fluid_settings_t* settings, const char *name,
                            int* min, int* max)
{
  fluid_setting_node_t *node;

  fluid_return_if_fail (settings != NULL);
  fluid_return_if_fail (name != NULL);
  fluid_return_if_fail (name[0] != '\0');
  fluid_return_if_fail (min != NULL);
  fluid_return_if_fail (max != NULL);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_INT_TYPE)) {
    fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
    *min = setting->min;
    *max = setting->max;
  }

  fluid_rec_mutex_unlock (settings->mutex);
}

/**
 * Get the default value of an integer setting.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @return the setting's default integer value it it exists, zero otherwise
 */
int
fluid_settings_getint_default(fluid_settings_t* settings, const char *name)
{
  fluid_setting_node_t *node;
  int retval = 0;

  fluid_return_val_if_fail (settings != NULL, 0);
  fluid_return_val_if_fail (name != NULL, 0);
  fluid_return_val_if_fail (name[0] != '\0', 0);

  fluid_rec_mutex_lock (settings->mutex);

  if (fluid_settings_get(settings, name, &node)
      && (node->type == FLUID_INT_TYPE)) {
    fluid_int_setting_t* setting = (fluid_int_setting_t*) node;
    retval = setting->def;
  }

  fluid_rec_mutex_unlock (settings->mutex);

  return retval;
}

/**
 * Iterate the available options for a named string setting, calling the provided
 * callback function for each existing option.
 *
 * @param settings a settings object
 * @param name a setting's name
 * @param data any user provided pointer
 * @param func callback function to be called on each iteration
 *
 * NOTE: Starting with FluidSynth 1.1.0 the \a func callback is called for each
 * option in alphabetical order.  Sort order was undefined in previous versions.
 */
void
fluid_settings_foreach_option (fluid_settings_t* settings, const char *name,
                               void* data, fluid_settings_foreach_option_t func)
{
  fluid_setting_node_t *node;
  fluid_str_setting_t *setting;
  fluid_list_t *p, *newlist = NULL;

  fluid_return_if_fail (settings != NULL);
  fluid_return_if_fail (name != NULL);
  fluid_return_if_fail (name[0] != '\0');
  fluid_return_if_fail (func != NULL);

  fluid_rec_mutex_lock (settings->mutex);       /* ++ lock */

  if (!fluid_settings_get (settings, name, &node) || node->type != FLUID_STR_TYPE)
  {
    fluid_rec_mutex_unlock (settings->mutex);   /* -- unlock */
    return;
  }

  setting = (fluid_str_setting_t*)node;

  /* Duplicate option list */
  for (p = setting->options; p; p = p->next)
    newlist = fluid_list_append (newlist, fluid_list_get (p));

  /* Sort by name */
  newlist = fluid_list_sort (newlist, fluid_list_str_compare_func);

  for (p = newlist; p; p = p->next)
    (*func)(data, (char *)name, (char *)fluid_list_get (p));

  fluid_rec_mutex_unlock (settings->mutex);   /* -- unlock */

  delete_fluid_list (newlist);
}

/**
 * Count option string values for a string setting.
 * @param settings a settings object
 * @param name Name of setting
 * @return Count of options for this string setting (0 if none, -1 if not found
 *   or not a string setting)
 * @since 1.1.0
 */
int
fluid_settings_option_count (fluid_settings_t *settings, const char *name)
{
  fluid_setting_node_t *node;
  int count = -1;

  fluid_return_val_if_fail (settings != NULL, -1);
  fluid_return_val_if_fail (name != NULL, -1);
  fluid_return_val_if_fail (name[0] != '\0', -1);

  fluid_rec_mutex_lock (settings->mutex);
  if (fluid_settings_get(settings, name, &node) && node->type == FLUID_STR_TYPE)
    count = fluid_list_size (((fluid_str_setting_t *)node)->options);
  fluid_rec_mutex_unlock (settings->mutex);

  return (count);
}

/**
 * Concatenate options for a string setting together with a separator between.
 * @param settings Settings object
 * @param name Settings name
 * @param separator String to use between options (NULL to use ", ")
 * @return Newly allocated string or NULL on error (out of memory, not a valid
 *   setting \a name or not a string setting).  Free the string when finished with it.
 * @since 1.1.0
 */
char *
fluid_settings_option_concat (fluid_settings_t *settings, const char *name,
                              const char *separator)
{
  fluid_setting_node_t *node;
  fluid_str_setting_t *setting;
  fluid_list_t *p, *newlist = NULL;
  int count, len;
  char *str, *option;

  fluid_return_val_if_fail (settings != NULL, NULL);
  fluid_return_val_if_fail (name != NULL, NULL);
  fluid_return_val_if_fail (name[0] != '\0', NULL);

  if (!separator) separator = ", ";

  fluid_rec_mutex_lock (settings->mutex);       /* ++ lock */

  if (!fluid_settings_get (settings, name, &node) || node->type != FLUID_STR_TYPE)
  {
    fluid_rec_mutex_unlock (settings->mutex);   /* -- unlock */
    return (NULL);
  }

  setting = (fluid_str_setting_t*)node;

  /* Duplicate option list, count options and get total string length */
  for (p = setting->options, count = 0, len = 0; p; p = p->next, count++)
  {
    option = fluid_list_get (p);

    if (option)
    {
      newlist = fluid_list_append (newlist, option);
      len += strlen (option);
    }
  }

  if (count > 1) len += (count - 1) * strlen (separator);
  len++;        /* For terminator */

  /* Sort by name */
  newlist = fluid_list_sort (newlist, fluid_list_str_compare_func);

  str = FLUID_MALLOC (len);

  if (str)
  {
    str[0] = 0;
    for (p = newlist; p; p = p->next)
    {
      option = fluid_list_get (p);
      strcat (str, option);
      if (p->next) strcat (str, separator);
    }
  }

  fluid_rec_mutex_unlock (settings->mutex);   /* -- unlock */

  delete_fluid_list (newlist);

  if (!str) FLUID_LOG (FLUID_ERR, "Out of memory");

  return (str);
}

/* Structure passed to fluid_settings_foreach_iter recursive function */
typedef struct
{
  char path[MAX_SETTINGS_LABEL+1];      /* Maximum settings label length */
  fluid_list_t *names;                  /* For fluid_settings_foreach() */
} fluid_settings_foreach_bag_t;

static int
fluid_settings_foreach_iter (void* key, void* value, void* data)
{
  fluid_settings_foreach_bag_t *bag = data;
  char *keystr = key;
  fluid_setting_node_t *node = value;
  int pathlen;
  char *s;

  pathlen = strlen (bag->path);

  if (pathlen > 0)
  {
    bag->path[pathlen] = '.';
    bag->path[pathlen + 1] = 0;
  }

  strcat (bag->path, keystr);

  switch (node->type) {
  case FLUID_NUM_TYPE:
  case FLUID_INT_TYPE:
  case FLUID_STR_TYPE:
    s = FLUID_STRDUP (bag->path);
    if (s) bag->names = fluid_list_append (bag->names, s);
    break;
  case FLUID_SET_TYPE:
    fluid_hashtable_foreach(((fluid_set_setting_t *)value)->hashtable,
                            fluid_settings_foreach_iter, bag);
    break;
  }

  bag->path[pathlen] = 0;

  return 0;
}

/**
 * Iterate the existing settings defined in a settings object, calling the
 * provided callback function for each setting.
 *
 * @param settings a settings object
 * @param data any user provided pointer
 * @param func callback function to be called on each iteration
 *
 * NOTE: Starting with FluidSynth 1.1.0 the \a func callback is called for each
 * setting in alphabetical order.  Sort order was undefined in previous versions.
 */
void
fluid_settings_foreach (fluid_settings_t* settings, void* data,
                        fluid_settings_foreach_t func)
{
  fluid_settings_foreach_bag_t bag;
  fluid_setting_node_t *node;
  fluid_list_t *p;
  int r;

  fluid_return_if_fail (settings != NULL);
  fluid_return_if_fail (func != NULL);

  bag.path[0] = 0;
  bag.names = NULL;

  fluid_rec_mutex_lock (settings->mutex);

  /* Add all node names to the bag.names list */
  fluid_hashtable_foreach (settings, fluid_settings_foreach_iter, &bag);

  /* Sort names */
  bag.names = fluid_list_sort (bag.names, fluid_list_str_compare_func);

  /* Loop over names and call the callback */
  for (p = bag.names; p; p = p->next)
  {
    r = fluid_settings_get (settings, (char *)(p->data), &node);
    if (r && node) (*func) (data, (char *)(p->data), node->type);
    FLUID_FREE (p->data);       /* -- Free name */
  }

  fluid_rec_mutex_unlock (settings->mutex);

  delete_fluid_list (bag.names);        /* -- Free names list */
}
