/*
   File: abase_fileutil.c
   Defines routines to handle files and filenames, and save and load
   binary data (lexica, compiled grammars, etc. ) in a machine and
   OS independent way.

   The format of the binary IO is taken from the ELF standard for the
   primitive types char, int and string. Furthermore we assume that
   structured data is saved recursively, field by field. Binary files
   contain an AGFL specific header, version and checksum.

   CVS ID: "$Id: abase_fileutil.c,v 1.1 2006/07/22 20:36:11 marcs Exp $"
*/

/* include config.h if autoconfigured */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* global includes */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>

/* local includes */
#include "abase_porting.h"
#include "abase_error.h"
#include "abase_memalloc.h"
#include "abase_fileutil.h"

/* Define the path separator (move this one to abase_porting.h?) */
#ifdef WIN32
#define DIRSEP '\\'
#else
#define DIRSEP '/'
#endif

/* primitive routines to check the existence of a path */
int abs_file_exists (char *path)
	{
#ifdef WIN32
	  struct _stat sbuf;
	  if (_stat (path, &sbuf)) return (0);
#else
	  struct stat sbuf;
	  if (stat (path, &sbuf)) return (0);
#endif
	  return (1);
	}

int abs_is_normal_file (char *path)
        {
#ifdef WIN32
          struct _stat sbuf;
          if (_stat (path, &sbuf)) return (0);
          return (sbuf.st_mode & _S_IFREG);
#else
          struct stat sbuf;
          if (stat (path, &sbuf)) return (0);
          return (sbuf.st_mode & S_IFREG);
#endif
        }

int abs_is_directory (char *path)
        {
#ifdef WIN32
          struct _stat sbuf;
          if (_stat (path, &sbuf)) return (0);
          return (sbuf.st_mode & _S_IFDIR);
#else
          struct stat sbuf;
          if (stat (path, &sbuf)) return (0);
          return (sbuf.st_mode & S_IFDIR);
#endif
        }

/* path construction */
char *abs_construct_path (char *directory, char *fname)
	{ return (abs_new_fmtd_string ("abs_construct_path", "%s%c%s", directory, DIRSEP, fname));
	}

/* Binary file type definition and initialisation */
struct bin_file_rec
	{ FILE *file;
	  char *path;
	  int writing;
	  u_int32 checksum;
	};
	  
BinFile abs_bin_fopen (char *path, char *mode)
	{ char *bmode = NULL;
	  int writing = 0;
	  FILE *file;
	  BinFile bf;

	  if (strcmp (mode, "rb") == 0)		{ writing = 0; bmode = "rb"; }
	  else if (strcmp (mode, "r") == 0)	{ writing = 0; bmode = "rb"; }
	  else if (strcmp (mode, "wb") == 0)	{ writing = 1; bmode = "wb"; }
	  else if (strcmp (mode, "w") == 0)	{ writing = 1; bmode = "wb"; }
	  else abs_abort ("abs_bin_fopen",
			  "Illegal mode '%s' to open binary file '%s'", mode, path);
	  file = fopen (path, bmode);
	  if (file == NULL)
	    abs_abort ("abs_bin_fopen",
		       "Could not open binary file '%s' with mode '%s'", path, mode);
	  bf = (BinFile) abs_malloc (sizeof (struct bin_file_rec), "abs_bin_fopen");
	  bf -> file = file;
	  bf -> path = path;
	  bf -> writing = writing;
	  bf -> checksum = 0;
	  return (bf);
	}

void abs_bin_fclose (BinFile bf)
	{ if (bf == NULL)	  abs_abort ("abs_bin_fclose", "Called with null pointer");
	  if (bf -> file == NULL) abs_abort ("abs_bin_fclose", "Binary file not open");
	  fclose (bf -> file);
	  abs_free ((void *) bf, "abs_bin_fclose");
	}

/* Saving routines */
void abs_bin_save_eof (BinFile bf)
	{ if (bf == NULL)	  abs_abort ("abs_bin_save_eof", "Called with null pointer");
	  if (bf -> file == NULL) abs_abort ("abs_bin_save_eof", "Binary file not open");
	  if (!bf -> writing) abs_abort ("abs_bin_save_eof", "Binary file not open for writing");
	  fputc ((0xff - bf -> checksum) & 0xff, bf -> file); 
	}

void abs_bin_save_char (BinFile bf, char x)
	{ if (bf == NULL)	  abs_abort ("abs_bin_save_char", "Called with null pointer");
	  if (bf -> file == NULL) abs_abort ("abs_bin_save_char", "Binary file not open");
	  if (!bf -> writing) abs_abort ("abs_bin_save_char", "Binary file not open for writing");
	  bf -> checksum = (bf -> checksum + (u_int32) ((unsigned char) x)) & 0xff;
	  fputc (x, bf -> file);
	}

void abs_bin_save_int (BinFile bf, int x)
	{ int value = x;
	  int more = 1;
	  do
	     { int byte = value & 0x7f;			/* Cut off 7 lsbs */
	       value >>= 7;				/* Discard them but keep sign */
	       if ((value == 0) && !(byte & 0x40)) more = 0;
	       if ((value == -1) && (byte & 0x40)) more = 0;
	       if (more) byte |= 0x80;
	       abs_bin_save_char (bf, (char) byte);
	     }
	  while (more);
	}

void abs_bin_save_string (BinFile bf, char *x)
	{ int len = (int) strlen (x);
	  int ix;
	  abs_bin_save_int (bf, len);
	  for (ix = 0; ix < len; ix++) abs_bin_save_char (bf, x[ix]);
	}

void abs_bin_save_version (BinFile bf, char *kind)
	{ abs_bin_save_string (bf, "AGFL");
	  abs_bin_save_string (bf, kind);
	  abs_bin_save_string (bf, VERSION);
	}

/* Loading routines */
void abs_bin_load_char (BinFile bf, char *x)
	{ int ch;
	  if (bf == NULL)	  abs_abort ("abs_bin_load_char", "Called with null pointer");
	  if (bf -> file == NULL) abs_abort ("abs_bin_load_char", "Binary file not open");
	  if (bf -> writing) abs_abort ("abs_bin_load_char", "Binary file not open for reading");
	  ch = fgetc (bf -> file);
	  if (ch == EOF)
	     abs_abort ("abs_bin_load_char", "Binary file '%s' ends prematurely", bf -> path);
	  bf -> checksum = (bf -> checksum + (u_int32) ch) & 0xff;
	  *x = (char) ch;
	}

void abs_bin_verify_eof (BinFile bf)
	{ char ch;
	  abs_bin_load_char (bf, &ch);		/* Load checksum */
	  if ((bf -> checksum & 0xff) != 0xff)
	     abs_abort ("abs_bin_verify_eof",
			"Binary file '%s' has an incorrect checksum", bf -> path);
	  if (fgetc (bf -> file) != EOF)
	     abs_abort ("abs_bin_verify_eof", "Binary file '%s' has trailing garbage", bf -> path);
	}

void abs_bin_load_int (BinFile bf, int *x)
	{ int value = 0;
	  int shift = 0;
	  char bb;
	  do
	     { abs_bin_load_char (bf, &bb);
	       value |= (((u_int) (bb & 0x7f)) << shift);
	       shift += 7;
	     }
	  while (bb & 0x80);
	  if (shift > 31) shift = 31;
	  if (bb & 0x40) value |= -(1 << shift);
	  *x = value;
	}

void abs_bin_load_string (BinFile bf, char **x)
	{ char buffer[MAXSTRLEN];
	  int ix, size;
	  char ch;
	  abs_bin_load_int (bf, &size);
	  if (size >= MAXSTRLEN)
	     abs_abort ("abs_bin_load_string",
			"Binary file '%s' contains a string of length %d", bf -> path, size);
	  for (ix = 0; ix < size; ix++)
	     { abs_bin_load_char (bf, &ch);
	       buffer[ix] = ch;
	     };
	  buffer[size] = '\0';
	  *x = abs_new_string (buffer, "abs_bin_load_string");
	}

void abs_bin_verify_version (BinFile bf, char *kind)
	{ char *hdr;
	  abs_bin_load_string (bf, &hdr);
	  if (strcmp (hdr, "AGFL") != 0)
	     abs_abort ("abs_bin_verify_version",
			"File '%s' does not appear to be an AGFL binary file", bf -> path);
	  abs_free (hdr, "abs_bin_verify_version");
	  abs_bin_load_string (bf, &hdr);
	  if (strcmp (hdr, kind) != 0)
	     abs_abort ("abs_bin_verify_version",
			"File '%s' does not appear to be a %s file", bf -> path, kind);
	  abs_free (hdr, "abs_bin_verify_version");
	  abs_bin_load_string (bf, &hdr);
	  if (strcmp (hdr, VERSION) != 0)
	     abs_abort ("abs_bin_verify_version",
			"File '%s' is generated by AGFL version %s\n"
			"you should regenerate it with the current AGFL version %s",
			bf -> path, hdr, VERSION);
	  abs_free (hdr, "abs_bin_verify_version");
	}
