/* linkedlist-inhom.c -- Inhomogene einfach verkettete Liste
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* Definition eines Funktionstyps zum Iterieren */
typedef void (*generic_function)(void *node_data, void *function_data);

/* Strukturdefinitionen */

struct generic_node {
  void *node_data;
  struct generic_node *next;
};

struct generic_list {
  /* Listenkopf */
  struct generic_node *head;
  /* Funktion zum Loeschen eines Elements: */
  generic_function delete_function;
  void *delete_function_data;
};

/* Konstruktor */

struct generic_list *new_generic_list(generic_function delete_function,
				      void *delete_function_data)
{
  struct generic_list *list = malloc(sizeof(struct generic_list));
  list->head = NULL;
  list->delete_function = delete_function;
  list->delete_function_data = delete_function_data;
  return list;
}

/* Destruktor */

void free_generic_list(struct generic_list *list)
{
  struct generic_node *node = list->head;
  free(list);
  while (node != NULL) {
    struct generic_node *next = node->next;
    list->delete_function(node->node_data,
			  list->delete_function_data);
    free(node);
    node = next;
  }
}

/* Einfuegen eines Datenelements */

void generic_list_push(struct generic_list *list, void *node_data)
{
  struct generic_node *node = malloc(sizeof(struct generic_node));
  node->node_data = node_data;
  node->next = list->head;
  list->head = node;
}

/* Funktion zum Iterieren */

void generic_list_for_each(struct generic_list *list,
			   generic_function function,
			   void *function_data)
{
  struct generic_node *node = list->head;
  while (node != NULL) {
    (*function)(node->node_data, function_data);
    node = node->next;
  }
}

/* Inhomogene Datenelemente: Terme eines mathematischen Ausdrucks */

enum term_type {
  NUMERICAL_CONSTANT,
  VARIABLE
  /* ... und weitere */
};

struct numerical_constant_data {
  double value;
};

struct variable_data {
  char *name;
};

struct term {
  enum term_type type;
  union {
    struct numerical_constant_data numerical_constant_data;
    struct variable_data variable_data;
  } data;
};

/* Konstruktoren */

struct term *new_numerical_constant_term(double value)
{
  struct term *term = malloc(sizeof(struct term));
  term->type = NUMERICAL_CONSTANT;
  term->data.numerical_constant_data.value = value;
  return term;
}

struct term *new_variable_term(char *name)
{
  struct term *term = malloc(sizeof(struct term));
  term->type = VARIABLE;
  term->data.variable_data.name = strdup(name);
  return term;
}

/* Destruktor */

static void free_term(void *node_data, void *function_data)
{
  struct term *term = node_data;
  switch (term->type) {
  case NUMERICAL_CONSTANT:
    /* nichts freigeben */
    break;
  case VARIABLE:
    free(term->data.variable_data.name);
    break;
  }
  free(term);
}

/* Term-Liste (z.B. fuer Operanden einer assoziativen Verknuepfung) */

struct generic_list *new_term_list()
{
  return new_generic_list(free_term, NULL);
}

/* Ausgeben einer Term-Liste */

struct print_term_data {
  int is_first_term;
  char *operator;
  FILE *file;
};

static void print_term(void *node_data, void *function_data)
{
  struct term *term = node_data;
  struct print_term_data *print_term_data = function_data;
  if (!print_term_data->is_first_term) {
    fprintf(print_term_data->file, " %s ",
	    print_term_data->operator);
  }
  switch (term->type) {
  case NUMERICAL_CONSTANT:
    fprintf(print_term_data->file, "%g",
	    term->data.numerical_constant_data.value);
    break;
  case VARIABLE:
    fprintf(print_term_data->file, "%s",
	    term->data.variable_data.name);
    break;
  }
  print_term_data->is_first_term = 0;
}

void print_term_list(FILE *file,
		     struct generic_list *list,
		     char *operator)
{
  struct print_term_data print_term_data;
  print_term_data.is_first_term = 1;
  print_term_data.operator = operator;
  print_term_data.file = file;
  generic_list_for_each(list, print_term, &print_term_data);
}
			  
/* Hauptprozedur */

int main ()
{
  struct generic_list *list = new_term_list();
  generic_list_push(list, new_variable_term("x"));
  generic_list_push(list, new_variable_term("y"));
  generic_list_push(list, new_variable_term("z"));
  generic_list_push(list, new_numerical_constant_term(4711));
  print_term_list(stdout, list, "+");
  printf("\n");
  return 0;
}
