/*
 * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
 */

#include <sys/types.h>
#ifndef _WIN32
#include <sys/mman.h>
#endif

#include "Eet.h"
#include "Eet_private.h"

#ifdef HAVE_REALPATH
#undef HAVE_REALPATH
#endif

#ifdef _WIN32

#ifndef F_SETFD
#define F_SETFD 2
#endif

#ifndef PROT_READ
#define PROT_READ 1
#endif

#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif

#endif

#define EET_MAGIC_FILE                  0x1ee7ff00
#define EET_MAGIC_FILE_HEADER           0x1ee7ff01

typedef struct _Eet_File_Header         Eet_File_Header;
typedef struct _Eet_File_Node           Eet_File_Node;
typedef struct _Eet_File_Directory      Eet_File_Directory;

struct _Eet_File
{
   int              magic;
   int              references;

   char            *path;

   FILE            *fp;
   Eet_File_Mode    mode;

   unsigned char    writes_pending : 1;
   unsigned char    delete_me_now : 1;

   Eet_File_Header *header;

   uint8_t	   *data;
   int		    data_size;
   time_t	    mtime;
};

struct _Eet_File_Header
{
   int                 magic;
   Eet_File_Directory *directory;
};

struct _Eet_File_Directory
{
   int             size;
   Eet_File_Node **nodes;
};

struct _Eet_File_Node
{
   unsigned char  free_name : 1;
   unsigned char  compression : 1;

   int            offset;
   int            size;
   int            data_size;

   char          *name;
   void          *data;
   Eet_File_Node *next; /* FIXME: make buckets linked lists */
};

#if 0
/* NB: all int's are stored in network byte order on disk */
/* file format: */
int magic; /* magic number ie 0x1ee7ff00 */
int num_directory_entries; /* number of directory entries to follow */
int bytes_directory_entries; /* bytes of directory entries to follow */
struct
{
   int offset; /* bytes offset into file for data chunk */
   int flags; /* flags - for now 0 = uncompressed, 1 = compressed */
   int size; /* size of the data chunk */
   int data_size; /* size of the (uncompressed) data chunk */
   int name_size; /* length in bytes of the name field */
   char name[name_size]; /* name string (variable length) */
} directory[num_directory_entries];
/* and now startes the data stream... */
#endif

/* prototypes of internal calls */
static Eet_File		*eet_cache_find(const char *path, Eet_File **cache, int cache_num);
static void		eet_cache_add(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc);
static void		eet_cache_del(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc);
static int		eet_string_match(const char *s1, const char *s2);
static Eet_Error	eet_flush(Eet_File *ef);
static Eet_File_Node	*find_node_by_name(Eet_File *ef, const char *name);
static int		read_data_from_disk(Eet_File *ef, Eet_File_Node *efn, void *buf, int len);

/* cache. i don't expect this to ever be large, so arrays will do */
static int        eet_writers_num     = 0;
static int        eet_writers_alloc   = 0;
static Eet_File **eet_writers         = NULL;
static int        eet_readers_num     = 0;
static int        eet_readers_alloc   = 0;
static Eet_File **eet_readers         = NULL;
static int        eet_initcount       = 0;

/* Check to see its' an eet file pointer */
static inline int
eet_check_pointer(const Eet_File *ef)
{
  if ((!ef) || (ef->magic != EET_MAGIC_FILE))
    return 1;
  return 0;
}

static inline int
eet_check_header(const Eet_File *ef)
{
   if (!ef->header)
     return 1;
   if (!ef->header->directory)
     return 1;
   return 0;
}

static inline int
eet_test_close(int test, Eet_File *ef)
{
   if (test)
     {
	ef->delete_me_now = 1;
	eet_close(ef);
     }
   return test;
}

/* find an eet file in the currently in use cache */
static Eet_File *
eet_cache_find(const char *path, Eet_File **cache, int cache_num)
{
   int i;

   /* walk list */
   for (i = 0; i < cache_num; i++)
     {
	/* if matches real path - return it */
	if (eet_string_match(cache[i]->path, path))
	  {
	     if (!cache[i]->delete_me_now)
	       return cache[i];
	  }
     }

   /* not found */
   return NULL;
}

/* add to end of cache */
static void
eet_cache_add(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc)
{
   Eet_File	**new_cache;
   int		new_cache_num;
   int		new_cache_alloc;

   new_cache_num = *cache_num;
   if (new_cache_num >= 64) /* avoid fd overruns - limit to 128 (most recent) in the cache */
     {
	Eet_File	*del_ef = NULL;
	int		i;
	
	new_cache = *cache;
	for (i = 0; i < new_cache_num; i++)
	  {
	     if (new_cache[i]->references == 0)
	       {
		  del_ef = new_cache[i];
		  break;
	       }
	  }
	
	if (del_ef)
	  {
	     del_ef->delete_me_now = 1;
	     eet_close(del_ef);
	  }
     }

   new_cache = *cache;
   new_cache_num = *cache_num;
   new_cache_alloc = *cache_alloc;
   new_cache_num++;
   if (new_cache_num > new_cache_alloc)
     {
	new_cache_alloc += 16;
	new_cache = realloc(new_cache, new_cache_alloc * sizeof(Eet_File *));
	if (!new_cache)
	  {
	     fprintf(stderr, "BAD ERROR! Eet realloc of cache list failed. Abort\n");
	     abort();
	  }
     }
   new_cache[new_cache_num - 1] = ef;
   *cache = new_cache;
   *cache_num = new_cache_num;
   *cache_alloc = new_cache_alloc;
}

/* delete from cache */
static void
eet_cache_del(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc)
{
   Eet_File **new_cache;
   int new_cache_num, new_cache_alloc;
   int i, j;

   new_cache = *cache;
   new_cache_num = *cache_num;
   new_cache_alloc = *cache_alloc;
   if (new_cache_num <= 0)
     return;
   
   for (i = 0; i < new_cache_num; i++)
     {
	if (new_cache[i] == ef)
	  break;
     }

   if (i >= new_cache_num)
     return;

   new_cache_num--;
   for (j = i; j < new_cache_num; j++)
     new_cache[j] = new_cache[j + 1];

   if (new_cache_num < (new_cache_alloc - 16))
     {
	new_cache_alloc -= 16;
	if (new_cache_num > 0)
	  {
	     new_cache = realloc(new_cache, new_cache_alloc * sizeof(Eet_File *));
	     if (!new_cache)
	       {
		  fprintf(stderr, "BAD ERROR! Eet realloc of cache list failed. Abort\n");
		  abort();
	       }
	  }
	else
	  {
	     free(new_cache);
	     new_cache = NULL;
	  }
     }
   *cache = new_cache;
   *cache_num = new_cache_num;
   *cache_alloc = new_cache_alloc;
}

/* internal string match. bails out at first mismatch - not comparing all */
/* bytes in strings */
static int
eet_string_match(const char *s1, const char *s2)
{
   /* both null- no match */
   if ((!s1) || (!s2)) return 0;
   return (!strcmp(s1, s2));
}

/* flush out writes to an eet file */
static Eet_Error
eet_flush(Eet_File *ef)
{
   Eet_File_Node	*efn;
   int			head[3];
   int			count = 0;
   int			size = 0;
   int			offset = 0;
   int			i;
   int			num;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return EET_ERROR_BAD_OBJECT;
   if (eet_check_header(ef))
     return EET_ERROR_EMPTY;
   if ((ef->mode != EET_FILE_MODE_WRITE) && (ef->mode != EET_FILE_MODE_READ_WRITE))
     return EET_ERROR_NOT_WRITABLE;
   if (!ef->writes_pending)
     return EET_ERROR_NONE;

   /* calculate total size in bytes of directory block */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  {
	     if (efn->compression >= 0)
	       {
		  size += 20 + strlen(efn->name) + 1;
		  count++;
	       }
	  }
     }

   /* calculate offsets per entry */
   offset = 0;
   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  {
	     if (efn->compression >= 0)
	       {
		  efn->offset = 12 + size + offset;
		  offset += efn->size;
	       }
	  }
     }

   /* go thru and write the header */
   head[0] = (int) htonl ((uint32_t) EET_MAGIC_FILE);
   head[1] = (int) htonl ((uint32_t) count);
   head[2] = (int) htonl ((uint32_t) size);

   fseek(ef->fp, 0, SEEK_SET);
   if (fwrite(head, 12, 1, ef->fp) != 1)
     goto write_error;

   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  {
	     if (efn->compression >= 0)
	       {
		  uint32_t	ibuf[5];
		  int		name_size;
		  
		  name_size = strlen(efn->name) + 1;
		  
		  ibuf[0] = (int) htonl ((uint32_t) efn->offset);
		  ibuf[1] = (int) htonl ((uint32_t) efn->compression);
		  ibuf[2] = (int) htonl ((uint32_t) efn->size);
		  ibuf[3] = (int) htonl ((uint32_t) efn->data_size);
		  ibuf[4] = (int) htonl ((uint32_t) name_size);
		  
		  
		  if (fwrite(ibuf, sizeof(ibuf), 1, ef->fp) != 1)
		    goto write_error;
		  if (fwrite(efn->name, name_size, 1, ef->fp) != 1)
		    goto write_error;
	       }
	  }
     }
   
   /* write data */
   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  {
	     if (efn->compression >= 0)
	       {
		  if (fwrite(efn->data, efn->size, 1, ef->fp) != 1)
		    goto write_error;
	       }
	  }
     }

   /* no more writes pending */
   ef->writes_pending = 0;
   
   return EET_ERROR_NONE;
   
write_error:
   switch (ferror(ef->fp))
     {
      case EFBIG:
	fclose(ef->fp);
	ef->fp = NULL;
	return EET_ERROR_WRITE_ERROR_FILE_TOO_BIG;
      case EIO:
	fclose(ef->fp);
	ef->fp = NULL;
	return EET_ERROR_WRITE_ERROR_IO_ERROR;
      case ENOSPC:
	fclose(ef->fp);
	ef->fp = NULL;
	return EET_ERROR_WRITE_ERROR_OUT_OF_SPACE;
      case EPIPE:
	fclose(ef->fp);
	ef->fp = NULL;
	return EET_ERROR_WRITE_ERROR_FILE_CLOSED;
      default:
	fclose(ef->fp);
	ef->fp = NULL;
	return EET_ERROR_WRITE_ERROR;
     }
   fclose(ef->fp);
   ef->fp = NULL;
   return EET_ERROR_WRITE_ERROR;
}

EAPI int
eet_init(void)
{
   return ++eet_initcount;
}

EAPI int
eet_shutdown(void)
{
   if (--eet_initcount == 0)
     {
	eet_clearcache();
	_eet_memfile_shutdown();
     }

   return eet_initcount;
}

EAPI void
eet_clearcache(void)
{
   int	num = 0;
   int	i;
   
   /*
     We need to compute the list of eet file to close separately from the cache,
     due to eet_close removing them from the cache after each call.
   */
   for (i = 0; i < eet_writers_num; i++)
     {
	if (eet_writers[i]->references <= 0)
	  num++;
     }
   
   for (i = 0; i < eet_readers_num; i++)
     {
	if (eet_readers[i]->references <= 0)
	  num++;
     }

   if (num > 0)
     {
	Eet_File **closelist = NULL;
	
	closelist = alloca(num * sizeof(Eet_File *));
	num = 0;
	for (i = 0; i < eet_writers_num; i++)
	  {
	     if (eet_writers[i]->references <= 0)
	       {
		  closelist[num] = eet_writers[i];
		  eet_writers[i]->delete_me_now = 1;
		  num++;
	       }
	  }
	
	for (i = 0; i < eet_readers_num; i++)
	  {
	     if (eet_readers[i]->references <= 0)
	       {
		  closelist[num] = eet_readers[i];
		  eet_readers[i]->delete_me_now = 1;
		  num++;
	       }
	  }
	
	for (i = 0; i < num; i++)
	  eet_close(closelist[i]);
     }
}

EAPI Eet_File *
eet_open(const char *file, Eet_File_Mode mode)
{
   Eet_File	*ef;
   struct stat	file_stat;
#ifdef _WIN32
   HANDLE       h;
#endif
   
   if (!file)
     return NULL;

   /* find the current file handle in cache*/
   ef = NULL;
   if (mode == EET_FILE_MODE_READ)
     {
	ef = eet_cache_find((char *)file, eet_writers, eet_writers_num);
	if (ef)
	  {
	     eet_flush(ef);
	     ef->delete_me_now = 1;
	     eet_close(ef);
	  }
	ef = eet_cache_find((char *)file, eet_readers, eet_readers_num);
     }
   else if ((mode == EET_FILE_MODE_WRITE) || (mode == EET_FILE_MODE_READ_WRITE))
     {
	ef = eet_cache_find((char *)file, eet_readers, eet_readers_num);
	if (ef)
	  {
	     ef->delete_me_now = 1;
	     eet_close(ef);
	  }
	ef = eet_cache_find((char *)file, eet_writers, eet_writers_num);
     }

   if (stat(file, &file_stat))
     {
	if (mode == EET_FILE_MODE_WRITE)
	  memset(&file_stat, 0, sizeof(file_stat));
	else
	  return NULL;
     }

   /* We found one */
   if (ef && (file_stat.st_mtime != ef->mtime))
     {
	ef->delete_me_now = 1;
	eet_close(ef);
	ef = NULL;
     }
   
   if (ef)
     {
	/* reference it up and return it */
	ef->references++;
	return ef;
     }

   /* Allocate struct for eet file and have it zero'd out */
   ef = malloc(sizeof(Eet_File) + strlen(file) + 1);
   if (!ef)
     return NULL;

   /* fill some of the members */
   ef->path = ((char *)ef) + sizeof(Eet_File);
   strcpy(ef->path, file);
   ef->magic = EET_MAGIC_FILE;
   ef->references = 1;
   ef->mode = mode;
   ef->header = NULL;
   ef->mtime = file_stat.st_mtime;
   ef->delete_me_now = 0;
   ef->data = NULL;
   ef->data_size = 0;

   /* try open the file based on mode */
   if ((ef->mode == EET_FILE_MODE_READ) || (ef->mode == EET_FILE_MODE_READ_WRITE))
     ef->fp = fopen(ef->path, "rb");
   else
     {
	if (eet_test_close(ef->mode != EET_FILE_MODE_WRITE, ef))
	  return NULL;
	else
	  {
	     /* opening for write - delete old copy of file right away */
	     unlink(ef->path);
	     ef->fp = fopen(ef->path, "wb");
	  }
     }

   /* if we can't open - bail out */
   if (eet_test_close(!ef->fp, ef))
     return NULL;

#ifndef _WIN32
   fcntl(fileno(ef->fp), F_SETFD, FD_CLOEXEC);
#else
   /* FIXME: check if that code is needed / correct */
   h = (HANDLE) _get_osfhandle (fileno(ef->fp));
   if (h == (HANDLE) -1)
     return NULL;
   SetHandleInformation (h, HANDLE_FLAG_INHERIT, 0);
#endif
   /* if we opened for read or read-write */
   if ((mode == EET_FILE_MODE_READ) || (mode == EET_FILE_MODE_READ_WRITE))
     {
	unsigned char		*dyn_buf = NULL;
	unsigned char		*p = NULL;
	int			index = 0;
	int			num_entries;
	int			byte_entries;
	int			i;
#ifdef _WIN32
	HANDLE                  fm;
#endif


	ef->data_size = file_stat.st_size;
#ifndef _WIN32
	ef->data = mmap(NULL, ef->data_size, PROT_READ,
			MAP_SHARED, fileno(ef->fp), 0);
#else
	fm = CreateFileMapping((HANDLE) _get_osfhandle (fileno(ef->fp)),
			       NULL,
			       PAGE_READONLY,
			       0,
			       0,
			       NULL);
	ef->data = MapViewOfFile(fm,
				 FILE_MAP_READ,
				 0,
				 0,
				 ef->data_size);
	CloseHandle(fm);
#endif

	if (eet_test_close((ef->data == (void *)-1) || (ef->data == NULL), ef))
	  return NULL;
	
	/* build header table if read mode */
	/* geat header */
	index += sizeof(int);
	if (eet_test_close((int)ntohl(*((int *)ef->data)) != EET_MAGIC_FILE, ef))
	  return NULL;

#define EXTRACT_INT(Value, Pointer, Index) \
        { \
	   int tmp; \
	   memcpy(&tmp, Pointer + Index, sizeof(int)); \
	   Value = ntohl(tmp); \
	   Index += sizeof(int); \
        }

	/* get entries count and byte count */
	EXTRACT_INT(num_entries, ef->data, index);
	EXTRACT_INT(byte_entries, ef->data, index);

	/* we cant have <= 0 values here - invalid */
	if (eet_test_close((num_entries <= 0) || (byte_entries <= 0), ef))
	  return NULL;

	/* we can't have more entires than minimum bytes for those! invalid! */
	if (eet_test_close((num_entries * 20) > byte_entries, ef))
	  return NULL;

	/* allocate header */
	ef->header = calloc(1, sizeof(Eet_File_Header));
	if (eet_test_close(!ef->header, ef))
	  return NULL;

	ef->header->magic = EET_MAGIC_FILE_HEADER;

	/* allocate directory block in ram */
	ef->header->directory = calloc(1, sizeof(Eet_File_Directory));
	if (eet_test_close(!ef->header->directory, ef))
	  return NULL;

	/* 8 bit hash table (256 buckets) */
	ef->header->directory->size = 8;
	/* allocate base hash table */
	ef->header->directory->nodes = calloc(1, sizeof(Eet_File_Node *) * (1 << ef->header->directory->size));
	if (eet_test_close(!ef->header->directory->nodes, ef))
	  return NULL;

	/* actually read the directory block - all of it, into ram */
	dyn_buf = ef->data + index;

	/* parse directory block */
	p = dyn_buf;

	for (i = 0; i < num_entries; i++)
	  {
	     Eet_File_Node	*efn;
	     void		*data = NULL;
	     int		indexn = 0;
	     int		name_size;
	     int		hash;
	     int		k;

#define HEADER_SIZE (sizeof(int) * 5)

	     /* out directory block is inconsistent - we have oveerun our */
	     /* dynamic block buffer before we finished scanning dir entries */
	     if (eet_test_close(p + HEADER_SIZE >= (dyn_buf + byte_entries), ef))
	       return NULL;

	     /* allocate all the ram needed for this stored node accounting */
	     efn = malloc (sizeof(Eet_File_Node));
	     if (eet_test_close(!efn, ef))
	       return NULL;

	     /* get entrie header */
	     EXTRACT_INT(efn->offset, p, indexn);
	     EXTRACT_INT(efn->compression, p, indexn);
	     EXTRACT_INT(efn->size, p, indexn);
	     EXTRACT_INT(efn->data_size, p, indexn);
	     EXTRACT_INT(name_size, p, indexn);

	     /* invalid size */
	     if (eet_test_close(efn->size <= 0, ef))
	       {
		  free (efn);
		  return NULL;
	       }

	     /* invalid name_size */
	     if (eet_test_close(name_size <= 0, ef))
	       {
		  free (efn);
		  return NULL;
	       }
	     
	     /* reading name would mean falling off end of dyn_buf - invalid */
	     if (eet_test_close((p + 16 + name_size) > (dyn_buf + byte_entries), ef))
	       {
		  free (efn);
		  return NULL;
	       }

	     /* This code is useless if we dont want backward compatibility */
	     for (k = name_size; k > 0 && ((uint8_t) * (p + HEADER_SIZE + k)) != 0; --k)
	       ;

	     efn->free_name = ((uint8_t) * (p + HEADER_SIZE + k)) != 0;

	     if (efn->free_name)
	       {
		  efn->name = malloc(sizeof(char) * name_size + 1);
		  if (eet_test_close(efn->name == NULL, ef))
		    {
		       free (efn);
		       return NULL;
		    }

		  strncpy(efn->name, (char *)p + HEADER_SIZE, name_size);
		  efn->name[name_size] = 0;

		  printf("File: %s is not up to date for key \"%s\" - needs rebuilding sometime\n", ef->path, efn->name);
	       }
	     else
	       /* The only really usefull peace of code for efn->name (no backward compatibility) */
	       efn->name = (char*)((uint8_t*)(p + HEADER_SIZE));

	     /* get hash bucket it should go in */
	     hash = _eet_hash_gen(efn->name, ef->header->directory->size);
	     efn->next = ef->header->directory->nodes[hash];
	     ef->header->directory->nodes[hash] = efn;

	     /* read-only mode, so currently we have no data loaded */
	     if (mode == EET_FILE_MODE_READ)
	       efn->data = NULL;
	     /* read-write mode - read everything into ram */
	     else
	       {
		  data = malloc(efn->size);
		  if (data)
		    memcpy(data, ef->data + efn->offset, efn->size);
                  efn->data = data;
	       }
	     /* advance */
	     p += HEADER_SIZE + name_size;
	  }
     }

   /* we need to delete the original file in read-write mode and re-open for writing */
   if (ef->mode == EET_FILE_MODE_READ_WRITE)
     {
	int i;
	
	for (i = 0; i < ef->header->directory->size; i++)
	  {
	     Eet_File_Node      *efn;
	     
	     for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	       {
		  if (!efn->free_name)
		    {
		       efn->free_name = 1;
		       efn->name = strdup(efn->name);
		    }
	       }
	  }
	fclose(ef->fp);
	unlink(ef->path);
	ef->fp = fopen(ef->path, "wb");
     }

   /* add to cache */
   if (ef->references == 1)
     {
	if (ef->mode == EET_FILE_MODE_READ)
	  eet_cache_add(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
	else
	  if ((ef->mode == EET_FILE_MODE_WRITE) || (ef->mode == EET_FILE_MODE_READ_WRITE))
	    eet_cache_add(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);
     }

   return ef;
}

EAPI Eet_File_Mode
eet_mode_get(Eet_File *ef)
{
   /* check to see its' an eet file pointer */
   if ((!ef) || (ef->magic != EET_MAGIC_FILE))
     return EET_FILE_MODE_INVALID;
   else
     return ef->mode;
}

EAPI Eet_Error
eet_close(Eet_File *ef)
{
   Eet_Error err;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return EET_ERROR_BAD_OBJECT;
   /* deref */
   ef->references--;
   /* if its still referenced - dont go any further */
   if (ef->references > 0) return EET_ERROR_NONE;
   /* flush any writes */
   err = eet_flush(ef);

   /* if not urgent to delete it - dont free it - leave it in cache */
   if ((!ef->delete_me_now) && (ef->mode == EET_FILE_MODE_READ))
     return EET_ERROR_NONE;
   
   /* remove from cache */
   if (ef->mode == EET_FILE_MODE_READ)
     eet_cache_del(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
   else if ((ef->mode == EET_FILE_MODE_WRITE) || (ef->mode == EET_FILE_MODE_READ_WRITE))
     eet_cache_del(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);

   /* free up data */
   if (ef->header)
     {
	if (ef->header->directory)
	  {
	     if (ef->header->directory->nodes)
	       {
		  int i, num;
	       
		  num = (1 << ef->header->directory->size);
		  for (i = 0; i < num; i++)
		    {
		       Eet_File_Node *efn;
		       
		       while ((efn = ef->header->directory->nodes[i]))
			 {
			    if (efn->data)
			      free(efn->data);

			    ef->header->directory->nodes[i] = efn->next;

			    if (efn->free_name)
			      free(efn->name);

			    free(efn);
			 }
		    }
		  free(ef->header->directory->nodes);
	       }
	     free(ef->header->directory);
	  }
	free(ef->header);
     }
#ifndef _WIN32
   if (ef->data) munmap(ef->data, ef->data_size);
#else
   if (ef->data) UnmapViewOfFile (ef->data);
#endif
   if (ef->fp) fclose(ef->fp);

   /* zero out ram for struct - caution tactic against stale memory use */
   memset(ef, 0, sizeof(Eet_File));
   
   /* free it */
   free(ef);
   return err;
}

EAPI void *
eet_read(Eet_File *ef, const char *name, int *size_ret)
{
   void			*data = NULL;
   int			size = 0;
   Eet_File_Node	*efn;

   if (size_ret)
     *size_ret = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return NULL;
   if (!name)
     return NULL;
   if ((ef->mode != EET_FILE_MODE_READ) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return NULL;

   /* no header, return NULL */
   if (eet_check_header(ef))
     return NULL;

   /* hunt hash bucket */
   efn = find_node_by_name(ef, name);
   if (!efn)
     return NULL;

   /* get size (uncompressed, if compressed at all) */
   size = efn->data_size;

   /* allocate data */
   data = malloc(size);
   if (!data)
     return NULL;

   /* uncompressed data */
   if (efn->compression == 0)
     {
	/* if we alreayd have the data in ram... copy that */
	if (efn->data)
	  memcpy(data, efn->data, efn->size);
	else
	  if (!read_data_from_disk(ef, efn, data, size))
	    {
	       free(data);
	       return NULL;
	    }
     }
   /* compressed data */
   else
     {
	void	*tmp_data;
	int	free_tmp = 0;
	int	compr_size = efn->size;
	uLongf	dlen;

	/* if we already have the data in ram... copy that */
	if (efn->data)
	  tmp_data = efn->data;
	else
	  {
	     tmp_data = malloc(compr_size);
	     if (!tmp_data)
	       {
		  free(data);
		  return NULL;
	       }

	     free_tmp = 1;

	     if (!read_data_from_disk(ef, efn, tmp_data, compr_size))
	       {
		  free(tmp_data);
		  free(data);
		  return NULL;
	       }
	  }

	/* decompress it */
	dlen = size;
	if (uncompress((Bytef *)data, &dlen,
		 tmp_data, (uLongf)compr_size))
	  {
	     free(data);
	     return NULL;
	  }

	if (free_tmp)
	  free(tmp_data);
     }

   /* fill in return values */
   if (size_ret)
     *size_ret = size;

   return data;
}

EAPI void *
eet_read_direct(Eet_File *ef, const char *name, int *size_ret)
{
   void *data = NULL;
   int size = 0;
   Eet_File_Node *efn;
   
   if (size_ret)
     *size_ret = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return NULL;
   if (!name)
     return NULL;
   if ((ef->mode != EET_FILE_MODE_READ) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return NULL;

   /* no header, return NULL */
   if (eet_check_header(ef))
     return NULL;

   /* hunt hash bucket */
   efn = find_node_by_name(ef, name);
   if (!efn)
     return NULL;

   /* get size (uncompressed, if compressed at all) */
   size = efn->data_size;

   /* uncompressed data */
   if (efn->compression == 0)
     data = efn->data ? efn->data : ef->data + efn->offset;
   /* compressed data */
   else
     data = NULL;

   /* fill in return values */
   if (size_ret)
     *size_ret = size;

   return data;
}

EAPI int
eet_write(Eet_File *ef, const char *name, const void *data, int size, int compress)
{
   Eet_File_Node	*efn;
   void			*data2;
   int			exists_already = 0;
   int			data_size;
   int			hash;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return 0;
   if ((!name) || (!data) || (size <= 0))
     return 0;
   if ((ef->mode != EET_FILE_MODE_WRITE) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return 0;

   if (!ef->header)
     {
	/* allocate header */
	ef->header = calloc(1, sizeof(Eet_File_Header));
	if (!ef->header)
	  return 0;

	ef->header->magic = EET_MAGIC_FILE_HEADER;
	/* allocate directory block in ram */
	ef->header->directory = calloc(1, sizeof(Eet_File_Directory));
	if (!ef->header->directory)
	  return 0;

	/* 8 bit hash table (256 buckets) */
	ef->header->directory->size = 8;
	/* allocate base hash table */
	ef->header->directory->nodes = calloc(1, sizeof(Eet_File_Node *) * (1 << ef->header->directory->size));
	if (!ef->header->directory->nodes)
	  return 0;
     }

   /* figure hash bucket */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   data_size = compress ? 12 + ((size * 101) / 100) : size;

   data2 = malloc(data_size);
   if (!data2)
     return 0;

   /* if we want to compress */
   if (compress)
     {
	uLongf buflen;

	/* compress the data with max compression */
	buflen = (uLongf)data_size;
	if (compress2((Bytef *)data2, &buflen, (Bytef *)data,
			   (uLong)size, Z_BEST_COMPRESSION) != Z_OK)
	  {
	     free(data2);
	     return 0;
	  }
	/* record compressed chunk size */
	data_size = (int)buflen;
	if (data_size < 0 || data_size >= size)
	  {
	     compress = 0;
	     data_size = size;
	  }
	else
	  {
	     void *data3;

	     data3 = realloc(data2, data_size);
	     if (data3)
	       data2 = data3;
	  }
     }
   if (!compress)
     memcpy(data2, data, size);

   /* Does this node already exist? */
   if (ef->mode == EET_FILE_MODE_READ_WRITE)
     {
	for (efn = ef->header->directory->nodes[hash]; efn; efn = efn->next)
	  {
	     /* if it matches */
	     if ((efn->name) && (eet_string_match(efn->name, name)))
	       {
		  free(efn->data);
		  efn->compression = !!compress;
		  efn->size = data_size;
		  efn->data_size = size;
		  efn->data = data2;
		  exists_already = 1;
		  break;
	       }
	  }
     }
   if (!exists_already)
     {
	efn = malloc(sizeof(Eet_File_Node));
	if (!efn)
	  {
	     free(data2);
	     return 0;
	  }
	efn->name = strdup(name);
	efn->free_name = 1;
	efn->next = ef->header->directory->nodes[hash];
	ef->header->directory->nodes[hash] = efn;
	efn->offset = 0;
	efn->compression = !!compress;
	efn->size = data_size;
	efn->data_size = size;
	efn->data = data2;
     }

   /* flags that writes are pending */
   ef->writes_pending = 1;
   return data_size;
}

EAPI int
eet_delete(Eet_File *ef, const char *name)
{
   Eet_File_Node	*efn;
   Eet_File_Node	*pefn;
   int			hash;
   int			exists_already = 0;
   
   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return 0;
   if (!name)
     return 0;

   /* deleting keys is only possible in RW or WRITE mode */
   if (ef->mode == EET_FILE_MODE_READ)
     return 0;

   if (eet_check_header(ef))
     return 0;

   /* figure hash bucket */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   /* Does this node already exist? */
   for (pefn = NULL, efn = ef->header->directory->nodes[hash];
	efn;
	pefn = efn, efn = efn->next)
     {
	/* if it matches */
	if (eet_string_match(efn->name, name))
	  {
	     if (efn->data)
	       free(efn->data);

	     if (efn == ef->header->directory->nodes[hash])
	       ef->header->directory->nodes[hash] = efn->next;
	     else
	       pefn->next = efn->next;
	     free(efn);
	     exists_already = 1;
	     break;
	  }
     }
   /* flags that writes are pending */
   if (exists_already)
     ef->writes_pending = 1;

   /* update access time */
   return exists_already;
}

EAPI char **
eet_list(Eet_File *ef, const char *glob, int *count_ret)
{
   Eet_File_Node	*efn;
   char			**list_ret = NULL;
   int			list_count = 0;
   int			list_count_alloc = 0;
   int			i, num;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef) || eet_check_header(ef) ||
       (!glob) ||
       ((ef->mode != EET_FILE_MODE_READ) &&
        (ef->mode != EET_FILE_MODE_READ_WRITE)))
     {
	if (count_ret)
	  *count_ret = 0;

	return NULL;
     }

   /* loop through all entries */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  {
	     /* if the entry matches the input glob
	      * check for * explicitly, because on some systems, * isn't well
	      * supported
	      */
	     if ((!strcmp (glob, "*")) || !fnmatch(glob, efn->name, 0))
	       {
		  /* add it to our list */
		  list_count++;
		  
		  /* only realloc in 32 entry chunks */
		  if (list_count > list_count_alloc)
		    {
		       char	**new_list = NULL;
		       
		       list_count_alloc += 64;
		       new_list = realloc(list_ret, list_count_alloc * (sizeof(char *)));
		       if (!new_list)
			 {
			    free(list_ret);
			    
			    if (count_ret)
			      *count_ret = 0;
			    
			    return NULL;
			 }
		       list_ret = new_list;
		    }
		  
		  /* put pointer of name string in */
		  list_ret[list_count - 1] = efn->name;
	       }
	  }
     }

   /* return count and list */
   if (count_ret)
     *count_ret = list_count;

   return list_ret;
}

EAPI int
eet_num_entries(Eet_File *ef)
{
   int i, num, ret = 0;
   Eet_File_Node *efn;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef) || eet_check_header(ef) ||
       ((ef->mode != EET_FILE_MODE_READ) &&
        (ef->mode != EET_FILE_MODE_READ_WRITE)))
     return -1;

   /* loop through all entries */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; i++)
     {
	for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
	  ret++;
     }

   return ret;
}

static Eet_File_Node *
find_node_by_name(Eet_File *ef, const char *name)
{
   Eet_File_Node *efn;
   int hash;

   /* get hash bucket this should be in */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   for (efn = ef->header->directory->nodes[hash]; efn; efn = efn->next)
     {
	if (eet_string_match(efn->name, name))
	  return efn;
     }

   return NULL;
}

static int
read_data_from_disk(Eet_File *ef, Eet_File_Node *efn, void *buf, int len)
{
   if (ef->data)
     {
	if ((efn->offset + len) > ef->data_size) return 0;
	memcpy(buf, ef->data + efn->offset, len);
     }
   else
     {
	/* seek to data location */
	if (fseek(ef->fp, efn->offset, SEEK_SET) < 0)
	  return 0;
	
	/* read it */
	len = fread(buf, len, 1, ef->fp);
     }
   return len;
}
