/*
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "option.h"

static struct chain * G_options = NULL;

option * option_new(const char *name, option_type type, const void *value) {
/* -----------------------------------------------------------------------------
 * Create an option
 * if option_type OPTION_CHAIN is passed, value will not be copied
 * so it must pointed to a dynamically allocated memory and it must not be freed
 * just after !!
 *
 * @param  const char *, name
 * @param  option_type
 * @param  const void *, the value
 * @return option *
 * -------------------------------------------------------------------------- */
  
  option *new_option = NULL;
  char *new_name = NULL;
  void *new_value = NULL;
  
  new_option = (option *)malloc(sizeof(option));
  if (new_option == NULL)
    return NULL;
  new_name = (char *)strdup(name);
  if (new_name == NULL) {
    free(new_option);
    return NULL;
  }
  switch(type) {
  case OPTION_STRING:
    new_value = (char *)strdup(value);
    if (new_value == NULL) {
      free(new_name);
      free(new_option);
      return NULL;
    }
    break;
  case OPTION_INT:
    new_value = (int *)malloc(sizeof(value));
    if (new_value == NULL) {
      free(new_name);
      free(new_option);
      return NULL;
    }
    memcpy(new_value, value, sizeof(value));
    break;
  case OPTION_CHAIN:
    new_value = (struct chain *)value;
    break;
  }
  
  new_option->name = new_name;
  new_option->type = type;
  new_option->value = new_value;
  
  return new_option;
}

void option_destroy(option *option_to_destroy) {
/* -----------------------------------------------------------------------------
 * Destroy an option
 *
 * -------------------------------------------------------------------------- */
  struct chain* chain_option = NULL;
  option *option_tmp = NULL;
  switch((option_to_destroy)->type) {
  case OPTION_CHAIN:
    for (chain_option = (struct chain *)option_to_destroy->value;
         chain_option != NULL; chain_option = chain_option->next) {
      option_tmp = (option *)chain_option->value;
      option_destroy(option_tmp);
    }
    free(option_to_destroy->name);
    drop_all_chain((struct chain *)option_to_destroy->value);
    free(option_to_destroy);
    break;
  case OPTION_INT:
  case OPTION_STRING:
    free(option_to_destroy->name);
    free(option_to_destroy->value);
    free(option_to_destroy);
    break;
  }
}

option * option_copy(const option *option_to_copy) {
/* -----------------------------------------------------------------------------
 * Copy an option
 *
 * @param  option *, the option to copy
 * @return option *
 * -------------------------------------------------------------------------- */
  return option_new(option_to_copy->name, option_to_copy->type, option_to_copy->value);
}

void drop_all_option() {
/* -----------------------------------------------------------------------------
 * Destroy *ALL* option
 *
 * -------------------------------------------------------------------------- */
  struct chain *chain_option = NULL;
  option *option_tmp = NULL;
  for (chain_option = G_options; chain_option != NULL; chain_option = chain_option->next) {
    option_tmp = chain_option->value;
    option_destroy(option_tmp);
  }
  drop_all_chain(G_options);
  G_options = NULL;
}

struct chain * get_root_option() {
/* -----------------------------------------------------------------------------
 * Get pointer to the first element
 *
 * @return struct chain *
 * -------------------------------------------------------------------------- */ 
  return G_options;
}
void set_root_option(void *mem) {
/* -----------------------------------------------------------------------------
 * Set a new pointer to the first element
 *
 * @param  void *
 * -------------------------------------------------------------------------- */ 
  G_options = mem;
}
  
void *get_option(const char *name, option_type *type) {
/* -----------------------------------------------------------------------------
 * Get the value from an option by name
 * 
 * @param  const char *, name
 * @param  option_type *, option type retrieved
 * @return void *
 *    if option_type is OPTION_STRING, value is a char *
 *    if option_type is OPTION_INT, value is an int *
 *    if option_type is OPTION_CHAIN, value is a struct chain *
 * -------------------------------------------------------------------------- */
  struct chain *chain_option = NULL;
  option *option_tmp = NULL;
  
	for (chain_option = G_options; chain_option != NULL; chain_option = chain_option->next) {
    option_tmp = chain_option->value;
    if (strcmp(option_tmp->name, name) == 0) {
    /* We have found the option */
      if (type != NULL)
        *type = option_tmp->type;
      return option_tmp->value;
    }
  }
  return NULL;
}
int set_option(const char *name, option_type type, const void *value) {
/* -----------------------------------------------------------------------------
 * Set the value from an option by name
 * 
 * @param  const char *, name
 * @param  option_type, option type
 * @param  const void *, new value
 * @return int
 * -------------------------------------------------------------------------- */

  option *option_tmp = NULL;
  struct chain *chain_option = NULL;
  
  if (G_options == NULL) {
  /* It is the first option */
    option_tmp = option_new(name, type, value);
    if (option_tmp == NULL)
      return 0;
    G_options = new_chain(option_tmp, NO_FREE);
    return 1;
  }
  
  /* Verify if this option exists */
  chain_option = find_option_byname(name);
    
  if (chain_option == NULL) {
  /* We add a new option */
    option_tmp = option_new(name, type, value);
    if (option_tmp == NULL)
      return 0;
    append_chain(new_chain(option_tmp, NO_FREE), G_options);
  }
  else {
  /* We replace an existing option */
    option_tmp = (option *)chain_option->value;
    
    option_destroy(option_tmp);
    option_tmp = option_new(name, type, value);
    if (option_tmp == NULL)
      return 0;
    chain_option->value = option_tmp;
  }
  return 1;
}
int add_option(const char *name, option_type type, const void *value) {
/* -----------------------------------------------------------------------------
 * Add an option by name
 * 
 * @param  const char *, name
 * @param  option_type, option type
 * @param  const void *, new value
 * @return int
 * -------------------------------------------------------------------------- */

  option *option_tmp = NULL, *option_chain = NULL;
  struct chain *chain_option = NULL, *chain_option_chain = NULL;
  
  if (G_options == NULL) {
  /* It is the first option */
    option_tmp = option_new(name, type, value);
    if (option_tmp == NULL)
      return 0;
    chain_option = new_chain(option_tmp, NO_FREE);
    if (chain_option == NULL)
      return 0;
    option_chain = option_new(name, OPTION_CHAIN, chain_option);
    if (option_chain == NULL)
      return 0;
    G_options = new_chain(option_chain, NO_FREE);
    return 1;
  }
  
  /* Verify it this option exists */
  chain_option = find_option_byname(name);
  
  if (chain_option == NULL) {
  /* We add a new option */
    option_tmp = option_new(name, type, value);
    if (option_tmp == NULL)
      return 0;
    chain_option = new_chain(option_tmp, NO_FREE);
    if (chain_option == NULL)
      return 0;
    option_chain = option_new(name, OPTION_CHAIN, chain_option);
    if (option_chain == NULL)
      return 0;
    append_chain(new_chain(option_chain, NO_FREE), G_options);
  }
  else {
  /* We add a new value to an existing option */
    option_tmp = (option *)chain_option->value;

    switch(option_tmp->type) {
    case OPTION_CHAIN:
    /* We add a new option to the chain */
      chain_option_chain = (struct chain *)option_tmp->value;
        
      option_tmp = option_new(name, type, value);
      if (option_tmp == NULL)
        return 0;
      append_chain(new_chain(option_tmp, NO_FREE), chain_option_chain);
      break;
    case OPTION_INT:
    case OPTION_STRING:
      /* We have an option with simple values
         so we add it */
      chain_option_chain = new_chain(option_tmp, NO_FREE);
      if (chain_option_chain == NULL)
        return 0;
      /* We create a new option */
      option_tmp = option_new(name, type, value);
      if (option_tmp == NULL)
        return 0;
      append_chain(new_chain(option_tmp, NO_FREE), chain_option_chain);
      
      option_chain = option_new(name, OPTION_CHAIN, chain_option_chain);
      if (option_chain == NULL)
        return 0;
      chain_option->value = option_chain;
      break;
    }
  }
  return 1;
}

struct chain *find_option_byname(const char *name) {
/* -----------------------------------------------------------------------------
 * Get the chain pointer to an option found by name
 * 
 * @param  const char *, name
 * @return struct chain * or NULL if option is not found
 * -------------------------------------------------------------------------- */	
  struct chain *chain_option = NULL;
  option *option_tmp = NULL;
  
  for (chain_option = G_options; chain_option != NULL; chain_option = chain_option->next) {
  /* Verify if this option exists */
    option_tmp = (option *)chain_option->value;
    if (strcmp(option_tmp->name, name) == 0)
    /* An option already exists */
      break;
  }
  if (chain_option == NULL)
    return NULL;
  return chain_option;
}

void dump_option(int fd, option *option_dump) {
/* -----------------------------------------------------------------------------
 * Print an option information to a file descriptor
 * 
 * @param  int, the file descriptor to write on
 * @param  option *, the option to dump
 * -------------------------------------------------------------------------- */
  struct chain *chain_option_chain = NULL;
  option *option_tmp = option_dump;
  FILE * stream = NULL;
  stream = fdopen(fd, "w");
  if (stream == NULL)
    return;
  
  switch(option_tmp->type) {
  case OPTION_INT:
    fprintf(stream, "%s = %d\n", option_tmp->name, *(int *)option_tmp->value);
    fflush(stream);
    break;
  case OPTION_STRING:
    fprintf(stream, "%s = %s\n", option_tmp->name, (char *)option_tmp->value);
    fflush(stream);
    break;
  case OPTION_CHAIN:
    fprintf(stream, "%s = ", option_tmp->name);
    for (chain_option_chain = (struct chain *)option_tmp->value;
         chain_option_chain != NULL; chain_option_chain = chain_option_chain->next) {
      option_tmp = (option *)chain_option_chain->value;
      switch(option_tmp->type) {
      case OPTION_INT:
        fprintf(stream, "%d", *(int *)option_tmp->value);
        break;
      case OPTION_STRING:
        fprintf(stream, "%s", (char *)option_tmp->value);
        break;
      default:
        break;
      }
      if (chain_option_chain->next != NULL)
        fprintf(stream, ", ");
    }
    fprintf(stream, "\n");
    fflush(stream);
    break;
  }
}

void dump_all_option(int fd) {
/* -----------------------------------------------------------------------------
 * Print *ALL* option informations to a file descriptor
 * 
 * @param  int, the file descriptor to write on
 * -------------------------------------------------------------------------- */
  struct chain *chain_option = NULL;
  option *option_tmp = NULL;
    
  for (chain_option = G_options; chain_option != NULL; chain_option = chain_option->next) {
    option_tmp = (option *)chain_option->value;
    dump_option(fd, option_tmp);
  }  
}


syntax highlighted by Code2HTML, v. 0.9.1