#define DISK_C

/* NOTE: you'll have to convert if your OS isn't using the same char rep.
 * usually ASCII */

#include "main.h"

/* forward reference */
static int check_entry_tag(const char *, file_io *);


static void setup_for_entry(file_io *fs)
{ 
 fs->disk_tags.used = 0;
}

static void file_setup_file_io(file_io *fs)
{ 
 fs->io = NULL;
 fs->read_only = TRUE;
 fs->used_popen = FALSE;
 fs->prefix_tags.used = 0;
 FILE_PREV_NULL(fs);
   
 setup_for_entry(fs);
}

void file_section_beg(const char *tag, file_io *fs)
{
 assert(fs->prefix_tags.used < FILE_IO_MAX_PREFIX_TAGS);
 assert(!strchr(tag, '.') && !strchr(tag, ':'));
 assert(strlen(tag) < FILE_TAG_MAX_SIZE);
 assert(strcmp(tag, fs->prev_tags[fs->prefix_tags.used]) > 0);

 FILE_PREV_COPY(fs, tag);

 if (fs->prefix_tags.used < FILE_IO_MAX_PREFIX_TAGS)
 {
  fs->prefix_tags.tag[fs->prefix_tags.used++] = tag;
  FILE_PREV_NULL(fs);
 }
}

void file_section_end(file_io *fs)
{
 if (fs->prefix_tags.used > 0)
   --fs->prefix_tags.used;
 else
 {
  assert(FALSE);
 }
}



/* input stuff .... */






static void finnish_get_entry(file_io *fs)
{
 if (!feof(fs->io))
 {
  int tmp = getc(fs->io);
  
  assert(tmp == '\n');
 }
 
 setup_for_entry(fs);
}

static size_t get_entry_length(file_io *fs)
{
 if (!feof(fs->io))
 {    
  int tmp = getc(fs->io);
  size_t length = 0;
  
  while ((tmp != EOF) && isdigit(tmp))
  {
   length = (length * 10) + TONUMB(tmp);
   tmp = getc(fs->io);
  }
  assert(tmp == ':');
  
  return (length);
 }

 return (0);
}

static void skip_file_length(int length, file_io *fs)
{
#if 0
 /* ? use fseek, will popen work with it ? */
  char buffer[512];
  
  while ((length > 512) && !feof(fs->io))
    length -= fread(buffer, sizeof(char), 512, fs->io);
  
  fread(buffer, sizeof(char), length, fs->io);
#else
  /* massively quicker, funnily enough */
  fseek(fs->io, length, SEEK_CUR);
#endif
}

static void skip_entry(file_io *fs)
{
 if (!feof(fs->io))
 {
  int length = get_entry_length(fs);
  skip_file_length(length, fs);
 }
 
 finnish_get_entry(fs);
}

static void skip_rest_tag(file_io *fs)
{
 int tmp = getc(fs->io);
 
 while ((tmp != EOF) && (tmp != ':'))
   tmp = getc(fs->io);
 assert(tmp == ':');
}

#ifndef NDEBUG
static void seperate_disk_tag_type(file_io *fs)
{
 int disk_tag = fs->disk_tags.used;
 int length = fs->disk_tags.len[disk_tag];
 char *tmp = NULL;
 
 assert(length < FILE_TAG_MAX_SIZE);

 if (length > 1)
 {
 tmp = fs->disk_tags.tag[disk_tag];
 tmp[length + 1] = 0;
 tmp[length] = tmp[length - 1];
 tmp[length - 1] = tmp[length - 2];
 tmp[length - 2] = 0;

 fs->disk_tags.type[disk_tag] = (tmp + length - 1);
 }
 else
 {
  assert(feof(fs->io));
 }
}
#else
/* remove the type from the disk tag */
# define seperate_disk_tag_type(fs) \
 if ((fs)->disk_tags.len[(fs)->disk_tags.used] > 1) \
  (fs)->disk_tags.tag[(fs)->disk_tags.used] \
                     [(fs)->disk_tags.len[(fs)->disk_tags.used] - 2] = 0
#endif

static int get_entry_next_tag(char *disk_tags, int *disk_tags_len,
                              int *current_char, file_io *fs)
{
 while ((*current_char != EOF) &&
        (*current_char != '.') && (*current_char != ':'))
 {
  if (*disk_tags_len < (FILE_TAG_MAX_SIZE + 2))
    disk_tags[(*disk_tags_len)++] = *current_char;
  else
  {
   assert(FALSE);
   fprintf(stderr, " ERROR: %s\n", " Your files are corrupted I'd guess.");
   return (FALSE);
  }
  
  *current_char = getc(fs->io);
 }
 
 disk_tags[*disk_tags_len] = 0;
 return (TRUE);
}

static int get_entry_tag(const char *tag, const char *tag_type, file_io *fs)
{
 int saved_cmp = 0;
 
 assert(fs->read_only);
 assert(strlen(tag) < FILE_TAG_MAX_SIZE);
 assert(strcmp(tag, fs->prev_tags[fs->prefix_tags.used]) > 0);

 FILE_PREV_COPY(fs, tag);

 restart_get_entry_tag:
 
 if (!fs->disk_tags.used)
   if (!feof(fs->io))
   {
    int tmp = getc(fs->io);
    int load_tag_return_false = FALSE;

    if (tmp == EOF)
      /* doesn't show EOF untill you get a char :( */
      return (fs->found_on_disk = FALSE);
    
    while (tmp != ':')
    {
     char *disk_tags = fs->disk_tags.tag[fs->disk_tags.used];
     int *disk_tags_len = (fs->disk_tags.len + fs->disk_tags.used);

     *disk_tags_len = 0;

     if (!get_entry_next_tag(disk_tags, disk_tags_len, &tmp, fs))
       return (fs->found_on_disk = FALSE);
     
     seperate_disk_tag_type(fs);

     switch (tmp)
     {
      case '.':
      {
       assert(!strcmp(fs->disk_tags.type[fs->disk_tags.used],
                      FILE_TAG_TYPE_OBJECT));
       
       if (!load_tag_return_false)
       {
        const char *want_tag = NULL;

        if (fs->disk_tags.used < fs->prefix_tags.used)
          want_tag = fs->prefix_tags.tag[fs->disk_tags.used];
        else if (fs->disk_tags.used == fs->prefix_tags.used)
        {
         want_tag = tag;
         assert(strcmp(want_tag, disk_tags));
        }
        
        if ((saved_cmp = strcmp(want_tag, disk_tags)) > 0)
        {
         skip_rest_tag(fs);
         skip_entry(fs);
         goto restart_get_entry_tag;
        }
        else
          if (saved_cmp < 0)
            load_tag_return_false = TRUE;
       }
       
       fs->disk_tags.used++;
      }
      break;
        
      case ':':
      {       
       assert((fs->disk_tags.used == fs->prefix_tags.used) ||
              load_tag_return_false);
       
       if (load_tag_return_false || (saved_cmp = strcmp(tag, disk_tags)) < 0)
       {
        fs->disk_tags.used++;
        return (fs->found_on_disk = FALSE);
       }
       else
         if (saved_cmp > 0)
         {
          skip_entry(fs);
          goto restart_get_entry_tag;
         }

       assert(!strcmp(tag_type, fs->disk_tags.type[fs->disk_tags.used]));
       fs->disk_tags.used++;
       return (fs->found_on_disk = TRUE);
      }

      default:
        assert(FALSE);
        fprintf(stderr, " ERROR: %s\n", 
              " File corruption, hit EOF in the middle of a tag!");
        return (fs->found_on_disk = FALSE);
     }
     
     tmp = getc(fs->io);
    }
    assert(tmp == ':');
   }
   else
     return (fs->found_on_disk = FALSE);

 if (!(saved_cmp = check_entry_tag(tag, fs)))
 {
  assert(!strcmp(tag_type, fs->disk_tags.type[fs->disk_tags.used - 1]));
  return (fs->found_on_disk = TRUE);
 }
 else if (saved_cmp < 0)
   return (fs->found_on_disk = FALSE);
 else
 {
  skip_entry(fs);
  goto restart_get_entry_tag;
 }
}

static int check_entry_tag(const char *tag, file_io *fs)
{
 int tmp = 0;
 int saved_cmp = 0;
 
 while (tmp < fs->prefix_tags.used)
 {
  if ((saved_cmp = strcmp(fs->prefix_tags.tag[tmp], fs->disk_tags.tag[tmp])))
    return (saved_cmp);
  
  ++tmp;
 }

 return (strcmp(tag, fs->disk_tags.tag[tmp]));
}

int file_read_open(const char *file_name, file_io *fs)
{
 struct stat the_file;
 
 if (!stat(file_name, &the_file) && S_ISREG(the_file.st_mode))
 {
  file_setup_file_io(fs);
  if ((fs->io = fopen(file_name, "r")))
  {
   /* ascii 001 is SOH (start of header) which seems apropriate :) */
   fs->version = file_get_int("\001version", fs);
   return (TRUE);
  }
 }
#if 0
 else
 {
  char prog_name[1024];
  int skip_prog = 0;
  
  snprintf(prog_name, 1023, "%s%s%n%s%s",
           "/usr/bin/gzip", " -dc ", &skip_prog, file_name, ".gz");
  prog_name[1023] = 0;
  
  if (!stat(prog_name + skip_prog, &the_file) && S_ISREG(the_file))
  {
   file_setup_file_io(fs);
   fs->io = popen(prog_name, "r");
   fs->use_popen = TRUE;
   /* ascii 001 is SOH (start of header) which seems apropriate :) */
   fs->version = file_get_int("\001version", fs);
   return (TRUE);
  }
 }
#endif
 
 return (FALSE);
}

void file_read_close(file_io *fs)
{
 assert(fs->io);
 assert(!fs->prefix_tags.used);
 
 if (fs->used_popen)
   pclose(fs->io);
 else
   fclose(fs->io);
}

int file_get_string(const char *tag, char *buffer, size_t size, file_io *fs)
{
 assert(size > 0);
 
 if (get_entry_tag(tag, FILE_TAG_TYPE_STRING, fs))
 {
  size_t length;
  size_t entry_length = length = get_entry_length(fs);

  if (length >= size)
    length = size - 1;

  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  if (entry_length > length)
    skip_file_length(entry_length - length, fs);
  
  finnish_get_entry(fs);
  
  return (length);
 }

 buffer[0] = 0;
 return (0);
}

void *file_get_malloc(const char *tag, size_t *return_length, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_STRING, fs))
 {
  size_t length = get_entry_length(fs);
  char *new_str = MALLOC(length + 1);

  if (new_str)
  {
   if (return_length)
     *return_length = length;
   
   fread(new_str, sizeof(char), length, fs->io);
   new_str[length] = 0;
  }
  else
  {
   if (return_length)
     *return_length = 0;
   
   skip_file_length(length, fs);
  }

  finnish_get_entry(fs);
    
  return (new_str);
 }

 return (NULL);
}

unsigned int file_get_unsigned_int(const char *tag, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_UINT, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */

  assert(length < 1024);
  
#endif

  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  finnish_get_entry(fs);
  
  return (strtoul(buffer, NULL, 10));
 }

 return (0);
}

int file_get_int(const char *tag, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_INT, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */

  assert(length < 1024);
  
#endif

  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  finnish_get_entry(fs);
  
  return (strtol(buffer, NULL, 10));
 }

 return (0);
}

unsigned long file_get_unsigned_long(const char *tag, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_ULONG, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */

  assert(length < 1024);

#endif
  
  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  finnish_get_entry(fs);
  
  return (strtoul(buffer, NULL, 10));
 }

 return (0);
}

long file_get_long(const char *tag, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_LONG, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */

  assert(length < 1024);

#endif
  
  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  finnish_get_entry(fs);
  
  return (strtol(buffer, NULL, 10));
 }

 return (0);
}

int file_get_bitflag(const char *tag, file_io *fs)
{ 
 if (get_entry_tag(tag,  FILE_TAG_TYPE_BITFLAG, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */
  
  assert(length < 1024);

#endif
  
  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;
  
  finnish_get_entry(fs);

  length = strtol(buffer, NULL, 2);
  assert((length == TRUE) || (length == FALSE));
  return (length);
 }
 
 return (0);
}

double file_get_double(const char *tag, file_io *fs)
{
 if (get_entry_tag(tag, FILE_TAG_TYPE_DOUBLE, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */

  assert(length < 1024);

#endif
  
  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;

  finnish_get_entry(fs);
  
  return (strtod(buffer, NULL));
 }

 return (0);
}

time_t file_get_time_t(const char *tag, file_io *fs)
{
 time_t now = time(NULL);
 struct tm *the_time = localtime(&now);
 
 if (get_entry_tag(tag,  FILE_TAG_TYPE_TIMESTAMP, fs))
 {
  int length = get_entry_length(fs);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
  char buffer[length + 1];
  char *tmp = buffer;
#else
  char buffer[1024]; /* should be long enough for any _NUMBER_ */
  char *tmp = buffer;
  
  assert(length < 1024);

#endif
  
  fread(buffer, sizeof(char), length, fs->io);
  buffer[length] = 0;
  
  finnish_get_entry(fs);

  while (*tmp)
  {
   int number = 0;
   
   tmp += strspn(tmp, " ");
   
   if (!isdigit((unsigned char) *tmp))
     return (mktime(the_time));
   
   number = skip_atoi((const char **) &tmp);
   
   switch (*tmp)
   {
    case '/': /* year */
      the_time->tm_year = number - 1900; /* years since 1900 */
      break;
    case '.': /* month */
      the_time->tm_mon = number - 1; /* goes from 0 .. 11 */
      break;
    case '-': /* day of month */
      the_time->tm_mday = number;
      break;
    case ':': /* hour */
      the_time->tm_hour = number;
      break;
    case '+': /* minute */
      the_time->tm_min = number;
      break;
    case 0:
      the_time->tm_sec = number;
      continue;
     
    default:
      return (-1);
   }
   ++tmp;
  }
 }
 

 return (mktime(the_time));
}





/* output stuff .... */



/* NOTE: even though we don't store the tag type for reading, we still
 * _NEED_ to have the tag type on disk */
static void put_entry_tag(const char *tag, const char *tag_type, file_io *fs)
{
 int tmp = 0;

 assert(fs && !fs->read_only);
 assert(strlen(tag) < FILE_TAG_MAX_SIZE);
 assert(strcmp(tag, fs->prev_tags[fs->prefix_tags.used]) > 0);

 FILE_PREV_COPY(fs, tag);
 
 while (tmp < fs->prefix_tags.used)
   fprintf(fs->io, "%s%s.", fs->prefix_tags.tag[tmp++], FILE_TAG_TYPE_OBJECT);

 fprintf(fs->io, "%s%s:", tag, tag_type);
}

static void finnish_put_entry(const char *buffer, file_io *fs)
{
 int length = strlen(buffer);
 fprintf(fs->io, "%d:%s\n", length, buffer);
}

int file_write_open(const char *file_name, int file_version, file_io *fs)
{
 file_setup_file_io(fs);
 if ((fs->io = fopen(file_name, "w")))
 {
  fs->read_only = FALSE;
  file_put_int("\001version", file_version, fs);
  return (TRUE);
 }

 return (FALSE);
}

void file_write_close(file_io *fs)
{
 assert(fs->io);
 assert(!fs->prefix_tags.used);

 if (fs->used_popen)
   pclose(fs->io);
 else
   fclose(fs->io);
}

void file_put_string(const char *tag, const char *str,
                     file_io *fs)
{
 put_entry_tag(tag, FILE_TAG_TYPE_STRING, fs); 
 finnish_put_entry(str, fs);
}

void file_put_unsigned_int(const char *tag, unsigned int to_put, file_io *fs)
{
 char buffer[1024];
 
 put_entry_tag(tag, FILE_TAG_TYPE_UINT, fs);

 snprintf(buffer, 1023, "%u", to_put);
 buffer[1023] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_int(const char *tag, int to_put, file_io *fs)
{
 char buffer[1024];
 
 put_entry_tag(tag, FILE_TAG_TYPE_INT, fs);

 snprintf(buffer, 1023, "%d", to_put);
 buffer[1023] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_unsigned_long(const char *tag, unsigned long to_put, file_io *fs)
{
 char buffer[1024];
 
 put_entry_tag(tag, FILE_TAG_TYPE_ULONG, fs);

 snprintf(buffer, 1023, "%lu", to_put);
 buffer[1023] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_long(const char *tag, long to_put, file_io *fs)
{
 char buffer[1024];

 put_entry_tag(tag, FILE_TAG_TYPE_LONG, fs);

 snprintf(buffer, 1023, "%ld", to_put);
 buffer[1023] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_bitflag(const char *tag, int to_put, file_io *fs)
{ 
 char buffer[4];
 
 assert((to_put == TRUE) || (to_put == FALSE));

 put_entry_tag(tag, FILE_TAG_TYPE_BITFLAG, fs);

 snprintf(buffer, 3, "%d", to_put);
 buffer[3] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_double(const char *tag, double to_put, file_io *fs)
{
 char buffer[1024];

 put_entry_tag(tag, FILE_TAG_TYPE_DOUBLE, fs);

 /* not sure how good we want this, but 32 should be ok */
 snprintf(buffer, 1023, "%.32f", to_put);
 buffer[1023] = 0;
 
 finnish_put_entry(buffer, fs);
}

void file_put_time_t(const char *tag, time_t to_put, file_io *fs)
{
 put_entry_tag(tag, FILE_TAG_TYPE_TIMESTAMP, fs); 
 finnish_put_entry(get_time_cmp_string(to_put), fs);
}

