#include <dk.h>

#include <stdio.h>
#if DK_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if DK_HAVE_STRING_H
#include <string.h>
#endif

#include <dkmem.h>
#include <dktypes.h>
#include <dksto.h>
#include <dksf.h>
#include <dkstr.h>

/**	Description of a person.
*/
typedef struct {
  char *famname;	/**< Family name */
  char *surname;	/**< Surname */
  unsigned long age;	/**< Age in years */
} Person;

/** Storage, persons sorted by family name. */
static dk_storage_t *s1;

/** Storage, persons sorted by surname. */
static dk_storage_t *s2;

/** Storage, persons sorted by age. */
static dk_storage_t *s3;

/** Storage, persons unsorted. */
static dk_storage_t *s4;

/** Storage iterator, sorted by family name. */
static dk_storage_iterator_t *i1;

/** Storage iterator, sorted by surname. */
static dk_storage_iterator_t *i2;

/** Storage iterator, sorted by age. */
static dk_storage_iterator_t *i3;

/** Storage iterator to traverse unsorted container. */
static dk_storage_iterator_t *i4;



/** Flag, input processing can be continued. */
static int can_continue;

/** Flag, stdin is connected to terminal. */
static int stdin_is_tty = 1;

static void read_file(FILE *);



/**	Retrieve age from pointer.
	The function retrieves an age evaluation of a pointer.
	The pointer target is either a struct Person or an
	unsigned long.
	@param	p	Pointer to person or unsigned long.
	@param	cr	Pointer object type:
	- 1: \a p points to a struct Person,
	- anything else: \a p points to an unsigned long.
	@return	The age.
*/
unsigned long age_fct(void *p, int cr)
{
  unsigned long back;
  switch(cr) {
    case 1: {
      back = *((unsigned long *)p);
    } break;
    default: {
      back = ((Person *)p)->age;
    } break;
  }
  return back;
}



/**	Compare object against other object or text.
	@param	p1	Left pointer (from storage).
	@param	p2	Right pointer (from dksto_it_find_like()).
	@param	cr	Comparison criteria:
	- 0 indicates: p1 and p2 are pointers to struct Person,
	  compare by family name.
	- 1 indicates: p1 is a pointer to a struct Person, p2 is a
	  pointer to a string. Compare the family name of p1
	  against the string.
	- 2 indicates: p1 and p2 are pointers to struct Person,
	  compare by surname.
	- 3 indicates: p1 is a pointer to a struct person, p2 is a
	  pointer to a string. Compare the surname of p1 against
	  the string.
	@return	Comparison result:
	- Positive value, if left pointer is larger (or behind),
	- Negative value, if left pointer is smaller (or before),
	- 0 if both pointers evaluate equal.
*/
int compare_fct(void *p1, void *p2, int cr)
{
  int back = 0;
  switch(cr) {
    case 0: {
      back = strcmp(((Person *)p1)->famname, ((Person *)p2)->famname);
    } break;
    case 1: {
      back = strcmp(((Person *)p1)->famname, (char *)p2);
    } break;
    case 2: {
      back = strcmp(((Person *)p1)->surname, ((Person *)p2)->surname);
    } break;
    case 3: {
      back = strcmp(((Person *)p1)->surname, (char *)p2);
    } break;
  }
  return back;
}



/**	Print a prompt.
*/
static void print_prompt(void)
{
  if(stdin_is_tty) {
    printf("\nstotest: ");
  }
}



/**	Print one persons record.
*/
static void print_person(Person *pers, FILE *fout)
{
  fprintf(
    fout,
    "%s %s %lu\n",
    ((pers->surname) ? pers->surname : "-"),
    ((pers->famname) ? pers->famname : "-"),
    pers->age
  );
}



/**	Print persons record from an iterators current
	position until the end of the container.
*/
static void print_it(dk_storage_iterator_t *i, FILE *fout)
{
  void *p;
  dksto_it_reset(i);
  while((p = dksto_it_next(i)) != NULL) {
    print_person((Person *)p, fout);
    fprintf(fout, "\n");
  }
}



/**	Print storage tree node including subtree.
	This function is used to show the tree structure.
	In your applications you should not access the internal
	members of a dk_storage_t or dk_storage_node_t directly.
*/
void print_node(dk_storage_node_t *n, int num, FILE *f, char c)
{
  int i;
  Person *pers;
  if(n) {
    for(i = 0; i < num; i++) {
      fprintf(f, "  ");
    }
    fprintf(f, "%c %5d (%d)", c, num, n->b);
    pers = (Person *)(n->o);
    if(pers) {
      if(pers->surname) fprintf(f, " %s ", pers->surname);
      else fprintf(f, " - ");
      if(pers->famname) fprintf(f, " %s ", pers->famname);
      else fprintf(f, " - ");
      fprintf(f, " %lu", pers->age);
    }
    fprintf(f, " \tparent:");
    if(n->p) {
      if((n->p)->o) {
	pers = (Person *)((n->p)->o);
	if(pers->surname) fprintf(f, " %s ", pers->surname);
	else fprintf(f, " - ");
	if(pers->famname) fprintf(f, "%s ", pers->famname);
	else fprintf(f, "- ");
	fprintf(f, " %lu", pers->age);
      }
    }
    fprintf(f, "\n");
    print_node(n->l, (num+1), f, 'l');
    print_node(n->r, (num+1), f, 'r');
  }
}



/**	Print the tree structure.
	In your applications you should not access the internal
	members of a dk_storage_t or dk_storage_node_t directly.
*/
void print_tree(dk_storage_t *s, FILE *f)
{
  if((s->h) && (s->t)) {
    print_node(s->r, 0, f, '+');
  }
}



/**	Free a persons record (release memory.)
	@param	pers	Pointer to record to release.
*/
static void free_person(Person *pers)
{
  char *cptr;
  if(pers) {
    cptr = pers->famname;
    if(cptr) { dkmem_free(cptr); }
    cptr = pers->surname;
    if(cptr) { dkmem_free(cptr); }
    pers->famname = pers->surname = NULL;
    dkmem_free(pers);
  }
}



/**	Handle command without arguments.
	@param	str	Command (first text in input line).
*/
static
void
handle_cmd_1(char *str)
{
  if(strcasecmp(str, "quit") == 0) {
    can_continue = 0;
  }
  if(strcasecmp(str, "print") == 0) {
    fprintf(stdout, "Sorted by family name:\n");
    print_it(i1, stdout);
    fprintf(stdout, "Sorted by surname:\n");
    print_it(i2, stdout);
    fprintf(stdout, "Sorted by age:\n");
    print_it(i3, stdout);
    fprintf(stdout, "Unsorted:\n");
    print_it(i4, stdout);
  }
  if(strcasecmp(str, "tree") == 0) {
    fprintf(stdout, "Sorted by family name:\n");
    print_tree(s1, stdout);
    fprintf(stdout, "Sorted by surname:\n");
    print_tree(s2, stdout);
    fprintf(stdout, "Sorted by age:\n");
    print_tree(s3, stdout);
    fprintf(stdout, "Unsorted:\n");
    print_tree(s4, stdout);
  }
}



/**	Handle command with one argument.
	@param	cmd	Command to handle.
	@param	arg1	Command argument.
*/
static void handle_cmd_2(char *cmd, char *arg1)
{
  if(strcasecmp(cmd, "read") == 0) {
    FILE *in;
    in = dksf_fopen(arg1, "r");
    if(in) {
      read_file(in);
      fclose(in);
    }
  }
}



/**	Handle command with 2 arguments.
	@param	cmd	Command to handle.
	@param	arg1	Command argument 1.
	@param	arg2	Command argument 2.
*/
static void handle_cmd_3(char *cmd, char *arg1, char *arg2)
{
  void *p;
  if(strcasecmp(cmd, "find") == 0) {
    if(strcasecmp(arg1, "f") == 0) {
      p = dksto_it_find_like(i1, arg2, 1);
      if(p) {
	print_person(p,stdout); printf("\n");
      }
      printf("Followers:\n");
      while((p = (Person *)dksto_it_next(i1)) != NULL) {
	print_person(p,stdout); printf("\n");
      }
    }
    if(strcasecmp(arg1, "s") == 0) {
      p = dksto_it_find_like(i2, arg2, 3);
      if(p) {
	print_person(p,stdout); printf("\n");
      }
      printf("Followers:\n");
      while((p = (Person *)dksto_it_next(i2)) != NULL) {
	print_person(p,stdout); printf("\n");
      }
    }
    if(strcasecmp(arg1, "a") == 0) {
      unsigned long age;
      if(sscanf(arg2, "%lu", &age) == 1) {
	p = dksto_it_find_like(i3, (void *)(&age), 1);
	if(p) {
	  print_person(p,stdout); printf("\n");
	}
	printf("Followers:\n");
	while((p = (Person *)dksto_it_next(i3)) != NULL) {
	  print_person(p,stdout); printf("\n");
	}
      }
    }
  }
  if(strcasecmp(cmd, "del") == 0) {
    if(strcasecmp(arg1, "f") == 0) {
      while((p = dksto_it_find_like(i1, arg2, 1)) != NULL) {
	dksto_remove(s1,p);
	dksto_remove(s2,p);
	dksto_remove(s3,p);
	dksto_remove(s4,p);
	free_person(p);
      }
    }
    if(strcasecmp(arg1, "s") == 0) {
      while((p = dksto_it_find_like(i2, arg2, 3)) != NULL) {
	dksto_remove(s1,p);
	dksto_remove(s2,p);
	dksto_remove(s3,p);
	dksto_remove(s4,p);
	free_person(p);
      }
    }
    if(strcasecmp(arg1, "a") == 0) {
      unsigned long age;
      if(sscanf(arg2, "%lu", &age) == 1) {
	while((p = dksto_it_find_like(i3, (void *)(&age), 1)) != NULL) {
	  dksto_remove(s1,p);
	  dksto_remove(s2,p);
	  dksto_remove(s3,p);
	  dksto_remove(s4,p);
	  free_person(p);
	}
      }
    }
  }
}



/**	Handle command with 3 arguments.
	@param	cmd	Command to handle.
	@param	arg1	Command argument 1.
	@param	arg2	Command argument 2.
	@param	arg3	Command argument 3.
*/
static void handle_cmd_4(char *cmd, char *arg1, char *arg2, char *arg3)
{
  if(strcasecmp(cmd, "add") == 0) {
    unsigned long age;
    Person *pers;
    if(sscanf(arg3, "%lu", &age) == 1) {
      pers = dk_new(Person,1);
      if(pers) {
	pers->famname = dkstr_dup(arg2);
	pers->surname = dkstr_dup(arg1);
	pers->age = age;
	if(!((pers->famname) && (pers->surname))) {
	  free_person(pers);
	} else {
	  if(!dksto_add(s4, (void *)pers)) {
	    free_person(pers);
	  } else {
	    dksto_add(s1, (void *)pers);
	    dksto_add(s2, (void *)pers);
	    dksto_add(s3, (void *)pers);
	  }
	}
      }
    }
  }
}



/**	Read input file.
	@param	in	File to read from.
*/
void read_file(FILE *in)
{

  char line[1024];
  char *lbegin;
  int i;
  char str1[sizeof(line)];
  char str2[sizeof(line)];
  char str3[sizeof(line)];
  char str4[sizeof(line)];
  int old_can_continue;
  old_can_continue = can_continue;
  can_continue = 1;
  while(can_continue) {
    if(in == stdin) print_prompt();
    if(fgets(line, sizeof(line), in)) {
      lbegin = dkstr_start(line, NULL);
      if(lbegin) {
        dkstr_chomp(lbegin, NULL);
	printf("# Processing \"%s\"\n", lbegin);
        i = sscanf(lbegin, "%s %s %s %s", str1, str2, str3, str4);
        switch(i) {
          case 1: {
            handle_cmd_1(str1);
          } break;
          case 2: {
            handle_cmd_2(str1, str2);
          } break;
          case 3: {
            handle_cmd_3(str1, str2, str3);
          } break;
          case 4: {
            handle_cmd_4(str1, str2, str3, str4);
          } break;
        }
      }
    } else {
	  can_continue = 0;
    }
  }
  can_continue = old_can_continue;
}



/**	Main program.
*/
int main(int argc, char *argv[])
{
  void *p; Person *pers; char *cptr;
  /* Check whether or not we are connected to a terminal */
  stdin_is_tty = dksf_echo_test_tty();
  /* Create storage containers */
  s1 = dksto_open(0);
  s2 = dksto_open(0);
  s3 = dksto_open(0);
  s4 = dksto_open(0);
  /* Check whether all containers were created successfully */
  if(s1 && s2 && s3 && s4) {
    /* Set comparison and evaluation functions */
    dksto_set_comp(s1, compare_fct, 0);
    dksto_set_comp(s2, compare_fct, 2);
    dksto_set_eval_ul(s3, age_fct, 0);
    /* Create storage container iterators */
    i1 = dksto_it_open(s1);
    i2 = dksto_it_open(s2);
    i3 = dksto_it_open(s3);
    i4 = dksto_it_open(s4);
    /* Check whether all iterators were created successfully */
    if(i1 && i2 && i3 && i4) {
      /* Process input */
      read_file(stdin);
      /* Release all the records */
      dksto_it_reset(i4);
      while((p = dksto_it_next(i4)) != NULL) {
	free_person((Person *)p);
      }
    }
  }
  /* Release the iterators */
  if(i1) dksto_it_close(i1);
  if(i2) dksto_it_close(i2);
  if(i3) dksto_it_close(i3);
  if(i4) dksto_it_close(i4);
  /* Release the storage containers */
  if(s1) dksto_close(s1);
  if(s2) dksto_close(s2);
  if(s3) dksto_close(s3);
  if(s4) dksto_close(s4);
  exit(0); return 0;
}




/**	@page	stotest	stotest - Test program for storage containers.

The stotest module is a test and demonstration program for
storage containers.

The program stores information about persons:
- surname,
- family name and
- age.

The program reads input line by line, each input line is a command.
The following commands can be used:
- add _surname_ _family-name_ _age_
- del f _family-name_
- del s _surname_
- del a _age_
- read _filename_
- print
- tree
- find f _family-name_
- find s _surname_
- find a _age_
- quit

The add command can be used to add persons.

The del command deletes one or multiple records matching the specified
family name, surname or age.

The read command reads an input file typically containing add commands.

The find command searches for the record for a given family name,
surname or age.

The print command prints all records beginning at the current record
(the first record in the container or the element found in the last
find command) until the end of the container.

The tree command command prints the tree structures of a container.

The quit command can be used to exit the application.

Input to the program might look like
@code
add	Erwin	Unsinn		30
add	David	Zeppelin	40
add	Johann	Unfug		50
add	Alter	Sack		99
add	Bla	Blubb		33
print
find	a	40
find	a	45
del	s	Johann
print
tree
quit
@endcode

@see stotest.c

@example stotest.c
*/

