#include <stdio.h>
#include <dictionary.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <ndbm.h>
#include <dicterrors.h>
#include <dict_errs.h>
#include <errno.h>
#include <strings.h>
#include <dictvariables.h>

static dictReadFileNames();
static dictFreeFileNames();

dictClearDatabase()
{
     int len;
     char buf[MAXPATHLEN];
	  
     if (! _dict_paths_initialized) {
	  dict_set_error(DICT_PATHS_UNINITIALIZED);
	  return(dict_error_code);
     }

     if (_dict_database) {
	  dict_set_error(DICT_DATABASE_OPEN);
	  return(dict_error_code);
     }
     
     if (unlink(_dict_index_path) && (errno != ENOENT)) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     len = strlen(_dict_index_path);
     if (len + 5 > MAXPATHLEN) {
	  dict_set_error(ENAMETOOLONG);
	  return(dict_error_code);
     }

     (void) sprintf(buf, "%s.dir", _dict_index_path);
     if (unlink(buf) && (errno != ENOENT)) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     (void) sprintf(buf, "%s.pag", _dict_index_path);
     if (unlink(buf) && (errno != ENOENT)) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }
	  
     return(0);
}

dictOpenNewDatabase()
{
     int retval;

     retval = dictClearDatabase();
     if (retval)
	  return(retval);

     _dict_database = dbm_open(_dict_index_path, O_RDWR | O_CREAT | O_EXCL,
			       0666);
     if (! _dict_database) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     _dict_index_file = fopen(_dict_index_path, "w+");
     if (! _dict_index_file) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }
     _dict_index_size = 0;
     
     retval = dictReadFileNames();
     if (retval) {
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  fclose(_dict_index_file);
	  return(retval);
     }
     
     return(0);
}

dictOpenDatabaseRead()
{
     int retval;
     struct stat statbuf;
     
     if (! _dict_paths_initialized) {
	  dict_set_error(DICT_PATHS_UNINITIALIZED);
	  return(dict_error_code);
     }

     if (_dict_database) {
	  dict_set_error(DICT_DATABASE_OPEN);
	  return(dict_error_code);
     }

     _dict_database = dbm_open(_dict_index_path, O_RDONLY, 0);
     if (! _dict_database) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     _dict_index_file = fopen(_dict_index_path, "r");
     if (! _dict_index_file) {
	  dict_set_error(errno ? errno : -1);
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  return(dict_error_code);
     }

     if (fstat(fileno(_dict_index_file), &statbuf) == -1) {
	  dict_set_error(errno ? errno : -1);
	  fclose(_dict_index_file);
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  return(dict_error_code);
     }
     _dict_index_size = statbuf.st_size;
     
     retval = dictReadFileNames();
     if (retval) {
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  fclose(_dict_index_file);
	  return(retval);
     }
     
     return(0);
}

dictOpenDatabaseWrite()
{
     int retval;
     struct stat statbuf;
     
     if (! _dict_paths_initialized) {
	  dict_set_error(DICT_PATHS_UNINITIALIZED);
	  return(dict_error_code);
     }

     if (_dict_database) {
	  dict_set_error(DICT_DATABASE_OPEN);
	  return(dict_error_code);
     }

     _dict_database = dbm_open(_dict_index_path, O_RDWR | O_CREAT, 0666);
     if (! _dict_database) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     _dict_index_file = fopen(_dict_index_path, "a+");
     if (! _dict_index_file) {
	  dict_set_error(errno ? errno : -1);
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  return(dict_error_code);
     }

     if (fstat(fileno(_dict_index_file), &statbuf) == -1) {
	  dict_set_error(errno ? errno : -1);
	  fclose(_dict_index_file);
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  return(dict_error_code);
     }
     _dict_index_size = statbuf.st_size;
	  
     retval = dictReadFileNames();
     if (retval) {
	  dbm_close(_dict_database);
	  _dict_database = NULL;
	  fclose(_dict_index_file);
	  return(retval);
     }
     
     return(0);
}

dictCloseDatabase()
{
     int retval;
     
     if (! _dict_paths_initialized) {
	  dict_set_error(DICT_PATHS_UNINITIALIZED);
	  return(dict_error_code);
     }

     if (! _dict_database) {
	  dict_set_error(DICT_DATABASE_CLOSED);
	  return(dict_error_code);
     }

     dbm_close(_dict_database);
     _dict_database = NULL;
     
     retval = fclose(_dict_index_file);
     if (retval == EOF) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     if (_dict_file_names)
	  dictFreeFileNames();
     
     return(0);
}

static dictReadFileNames()
{
     FILE *list_file;
     char name[MAXNAMLEN], *bigbuf = NULL, *ptr;
     int total_len = 0, len, total_len2 = 0;
     int dirlen;
     int result = 0;
     int i;
     extern char *malloc();
     
     /*
      * N.B.: This function makes two passes through the filelist,
      * simply scanning lengths the first time, and actually saving
      * the names the second time.
      *
      * I consider this reasonable because the file name list is read
      * very infrequently, and because it's relatively small, and
      * because any other method would require (a) allocating much
      * more memory than necessary and wasting a lot of it, or (b)
      * calling malloc() and realloc() many times, which would
      * probably be slower than two passes through the file, and would
      * result in less efficient memory allocation.
      */

     dirlen = strlen(_dict_directory) + 1;
     list_file = fopen(_dict_list_path, "r");
     if (! list_file) {
	  dict_set_error(errno ? errno : -1);
	  return(dict_error_code);
     }

     _dict_num_files = 0;
     
     while (fgets(name, MAXNAMLEN, list_file) != NULL) {
	  len = strlen(name);
	  if ((name[len - 1] != '\n') || (dirlen + len + 2 > MAXPATHLEN)) {
	       dict_set_error(ENAMETOOLONG);
	       result = dict_error_code;
	       goto done;
	  }
	  _dict_num_files++;
	  total_len += len;
     }

     if (! feof(list_file)) {
	  dict_set_error(errno ? errno : -1);
	  result = dict_error_code;
	  goto done;
     }

     _dict_file_names = (char **) malloc((unsigned)
					 (sizeof(char *) * _dict_num_files));
     bigbuf = malloc((unsigned) total_len);
     if (! (_dict_file_names && bigbuf)) {
	  dict_set_error(errno ? errno : -1);
	  result = dict_error_code;
	  goto done;
     }
     
     if (fseek(list_file, 0L, 0) < 0) {
	  dict_set_error(errno ? errno : -1);
	  result = dict_error_code;
	  goto done;
     }

     ptr = bigbuf;
     for (i = 0; i < _dict_num_files; i++) {
	  if (fgets(name, MAXNAMLEN, list_file) == NULL) {
	       dict_set_error(errno ? errno : -1);
	       result = dict_error_code;
	       goto done;
	  }
	  len = strlen(name);
	  total_len2 += len;
	  if ((name[len - 1] != '\n') || (total_len2 > total_len) ||
	      (dirlen + len > MAXPATHLEN)) {
	       dict_set_error(DICT_FILES_CHANGED);
	       result = dict_error_code;
	       goto done;
	  }
	  name[len - 1] = '\0';
	  strcpy(ptr, name);
	  _dict_file_names[i] = ptr;
	  ptr += len;
     }

     if (! (fgets(name, MAXNAMLEN, list_file) == NULL) && (feof(list_file))) {
	  dict_set_error(DICT_FILES_CHANGED);
	  result = dict_error_code;
	  goto done;
     }

done:
     if (fclose(list_file) == EOF) {
	  dict_set_error(errno ? errno : -1);
	  result = dict_error_code;
     }
     if (result && bigbuf) {
	  free(_dict_file_names);
	  free(bigbuf);
     }
     
     return(result);
}

static dictFreeFileNames()
{
     free(_dict_file_names[0]);
     free(_dict_file_names);

     return(0);
}
