/* packdump.c: -*- C -*-  How to dump a package in binary format. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Sat Jul 20 23:52:57 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#if defined (HAVE_CONFIG_H)
#  include <config.h>
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <bprintf/bprintf.h>
#include <xmalloc/xmalloc.h>
#include "symbols.h"
#include "pages.h"
#include "parser.h"

/* The Mac only defines "macintosh" in sys/types.h.  Sigh... */
#include <sys/types.h>

#if defined (macintosh)
#  include <mac_port.h>
#endif

/* Functions for reading from and writing to a string. */
#define STRING_FD_THRESHOLD 1024
#define FD_IS_STRING_BUFFER(fd) ((fd) >= STRING_FD_THRESHOLD)
#define SD_TO_BUFFER_INDEX(sd) ((sd) - STRING_FD_THRESHOLD)
#define BUFFER_INDEX_TO_SD(bi) ((bi) + STRING_FD_THRESHOLD)

static BPRINTF_BUFFER **open_strings = (BPRINTF_BUFFER **)NULL;
static int open_strings_slots = 0;
static int open_strings_index = 0;

static BPRINTF_BUFFER *
buffer_of_sd (int sd)
{
  BPRINTF_BUFFER *result = (BPRINTF_BUFFER *)NULL;

  sd = SD_TO_BUFFER_INDEX (sd);

  if ((sd >= 0) && (sd < open_strings_index))
    result = open_strings[sd];

  return (result);
}

/* Read LEN bytes from SD into DATA. */
int
symdump_read_string_data (int sd, int len, unsigned char *data)
{
  BPRINTF_BUFFER *string_buffer = buffer_of_sd (sd);
  int result = 0;

  if (string_buffer != (BPRINTF_BUFFER *)NULL)
    {
      if (string_buffer->bindex + len <= string_buffer->bsize)
	{
	  register int i;

	  for (i = 0; i < len; i++)
	    data[i] = (unsigned char)
	      (string_buffer->buffer[string_buffer->bindex++]);
	  result = i;
	}
    }
  return (result);
}

/* Write LEN bytes to SD from DATA. */
int
symdump_write_string_data (int sd, int len, unsigned char *data)
{
  BPRINTF_BUFFER *string_buffer = buffer_of_sd (sd);
  int result = 0;

  if (string_buffer != (BPRINTF_BUFFER *)NULL)
    {
      register int i;

      if (len + string_buffer->bindex >= string_buffer->bsize)
	string_buffer->buffer = (char *)xrealloc
	  (string_buffer->buffer, (string_buffer->bsize += (len + 100)));

      for (i = 0; i < len; i++)
	((unsigned char *) (string_buffer->buffer))[string_buffer->bindex++] =
	  data[i];

      result = i;
    }

  return (result);
}

/* Set the current offstring in SD to OFFSET. */
void
symdump_seek_string_data (int sd, int offset)
{
  BPRINTF_BUFFER *string_buffer = buffer_of_sd (sd);

  if ((string_buffer != (BPRINTF_BUFFER *)NULL) &&
      ((offset >= 0) && (offset <= string_buffer->bsize)))
    string_buffer->bindex = offset;
}

/* Set the current offstring in SD to OFFSET. */
void
symdump_set_string_data_buffer_size (int sd, int end)
{
  BPRINTF_BUFFER *string_buffer = buffer_of_sd (sd);

  if ((string_buffer != (BPRINTF_BUFFER *)NULL) &&
      ((end >= 0) && (end <= string_buffer->bsize)))
    string_buffer->bsize = end;
}

/* Return an SD (like an FD, but for the string buffer pool.
   Newly allocates a buffer.
   The SD returned is suitable for passing to symbol_load_package ()
   and symbol_dump_package (). */
int
symdump_open_string_data (void)
{
  register int i;
  int sd = -1;

  if (open_strings_index + 2 > open_strings_slots)
    {
      open_strings = (BPRINTF_BUFFER **) xrealloc
	(open_strings, (open_strings_slots += 4) * sizeof (BPRINTF_BUFFER *));
      for (i = open_strings_index; i < open_strings_slots; i++)
	open_strings[i] = (BPRINTF_BUFFER *)NULL;
    }

  /* Find first unused slot. */
  for (i = 0; i < open_strings_slots; i++)
    {
      if (open_strings[i] == (BPRINTF_BUFFER *)NULL)
	{
	  sd = i + STRING_FD_THRESHOLD;
	  open_strings[i] = bprintf_create_buffer ();
	  if (i == open_strings_index)
	    open_strings_index++;
	  break;
	}
    }

  return (sd);
}

/* Return the BPRINTF_BUFFER corresponding to SD, and return the allocated
   slot back to the pool.  Does NOT free the BPRINTF_BUFFER. */
BPRINTF_BUFFER *
symdump_close_string_data (int sd)
{
  BPRINTF_BUFFER *string_buffer = buffer_of_sd (sd);

  if (string_buffer != (BPRINTF_BUFFER *)NULL)
    {
      /* Reset the end of the buffer. */
      string_buffer->bsize = string_buffer->bindex;

      /* Allow slot to be reused. */
      open_strings[SD_TO_BUFFER_INDEX (sd)] = (BPRINTF_BUFFER *)NULL;
    }
  return (string_buffer);
}

static int
write_data (int fd, int len, unsigned char *data)
{
  if (FD_IS_STRING_BUFFER (fd))
    return (symdump_write_string_data (fd, len, data));
  else
    return (write (fd, data, len));
}

static int
read_data (int fd, int len, unsigned char *data)
{
  if (FD_IS_STRING_BUFFER (fd))
    return (symdump_read_string_data (fd, len, data));
  else
    return (read (fd, data, len));
}

static int
write_integer (int fd, int integer)
{
  unsigned char byte;
  int amount = 0;

  byte = (unsigned char) ((integer & 0x000000ff) >> 0);
  amount += write_data (fd, 1, &byte);

  byte = (unsigned char) ((integer & 0x0000ff00) >> 8);
  amount += write_data (fd, 1, &byte);

  byte = (unsigned char) ((integer & 0x00ff0000) >> 16);
  amount += write_data (fd, 1, &byte);

  byte = (unsigned char) ((integer & 0xff000000) >> 24);
  amount += write_data (fd, 1, &byte);

  return (amount);
}

static int
read_integer (int fd)
{
  unsigned char b1, b2, b3, b4;
  int safety = 0;
  int integer = 0;

  safety += read_data (fd, 1, &b1);
  safety += read_data (fd, 1, &b2);
  safety += read_data (fd, 1, &b3);
  safety += read_data (fd, 1, &b4);

  if (safety != 4)
    integer = 0;
  else
    integer =
      (((int)b4) << 24) | (((int)b3) << 16) | (((int)b2) << 8) | ((int)b1);

  return (integer);
}

static void
write_symbol (Symbol *symbol, int fd)
{
  register int i;
  unsigned char byte;

  if (symbol == (Symbol *)NULL) return;

  /* Start with the name. */
  byte = (unsigned char)symbol->name_len;
  write_data (fd, 1, &byte);
  write_data (fd, byte, symbol->name);

  /* Now the type. */
  byte = (unsigned char)symbol->type;
  write_data (fd, 1, &byte);

  /* Now any flags which this symbol may have. */
  write_integer (fd, symbol->flags);

  /* Different methods for different symbol types. */
  switch (symbol->type)
    {
    case symtype_STRING:
      /* Write the number of values that this symbol has. */
      write_integer (fd, symbol->values_index);

      /* Write the values themselves. */
      for (i = 0; i < symbol->values_index; i++)
	{
	  int len = symbol->values[i] ? strlen (symbol->values[i]) : 0;
	  char *data_pointer = symbol->values[i];

	  if (!len)
	    {
	      len = 1;
	      data_pointer = "";
	    }

	  write_integer (fd, len);
	  write_data (fd, len, symbol->values[i]);
	}
      break;

    case symtype_FUNCTION:
      /* We write no data for functions at this time. */
      break;

    case symtype_BINARY:
      /* Write out the datablock. */
      {
	Datablock *block = (Datablock *)symbol->values;
	write_integer (fd, block->length);
	write_data (fd, block->length, block->data);
      }
      break;

    case symtype_USERFUN:
      /* Write out the user function. */
      {
	UserFunction *fun = (UserFunction *)symbol->values;
	int len;

	/* Write the type of function. */
	byte = (unsigned char)fun->type;
	write_data (fd, 1, &byte);

	/* Write the function's flags. */
	byte = (unsigned char)fun->flags;
	write_data (fd, 1, &byte);

	/* We don't have to write the name of this function.
	   It is the same as the name of the symbol that we already
	   wrote. */

	/* Write the name of the package to wrap around this function. */
	byte = (unsigned char) (fun->packname ? strlen (fun->packname) : 0);
	write_data (fd, 1, &byte);
	if (fun->packname)
	  write_data (fd, (int)byte, fun->packname);

	/* Write the number of named parameters that this function takes. */
	for (i = 0; fun->named_parameters && fun->named_parameters[i]; i++);
	byte = (unsigned char )i;
	write_data (fd, 1, &byte);

	/* If there were any, write them now. */
	for (i = 0; i < byte; i++)
	  {
	    unsigned char l = strlen (fun->named_parameters[i]);
	    write_data (fd, 1, &l);
	    write_data (fd, (int)l, fun->named_parameters[i]);
	  }

	/* Write the function body. */
	len = fun->body ? strlen (fun->body) : 0;
	write_integer (fd, len);
	write_data (fd, len, fun->body);
      }
      break;

    default:
      abort ();
    }
}

static Symbol *
read_symbol (int fd, Package *package)
{
  register int i;
  static char *buffer = (char *)NULL;
  static int buflen = 0;
  unsigned char byte;
  Symbol *sym = (Symbol *)NULL;

  /* Read the length of the symbol name. */
  read_data (fd, 1, &byte);

  if (byte + 1 > buflen)
    buffer = (char *)xrealloc (buffer, (buflen += (byte + 128)));

  /* Read the symbol name. */
  read_data (fd, (int)byte, buffer);
  buffer[(int)byte] = '\0';

  /* Trash the symbol if it already exists. */
  sym = symbol_remove_in_package (package, buffer);
  if (sym != (Symbol *)NULL) symbol_free (sym);

  /* Make the symbol. */
  sym = symbol_intern_in_package (package, buffer);

  /* Get its type. */
  read_data (fd, 1, &byte);
  sym->type = (int)byte;

  /* And the flags. */
  sym->flags = read_integer (fd);

  /* Do different things for each symbol type. */
  switch (sym->type)
    {
    case symtype_STRING:
      /* Find out how many values are stored here. */
      sym->values_index = read_integer (fd);

      /* Allocate space for that many. */
      sym->values_slots = sym->values_index + 1;
      sym->values = (char **)xmalloc ((sym->values_slots * sizeof (char *)));

      /* Fill in the slots from the data in the file. */
      for (i = 0; i < sym->values_index; i++)
	{
	  int temp = read_integer (fd);
	  sym->values[i] = (char *)xmalloc (1 + temp);
	  read_data (fd, temp, sym->values[i]);
	  sym->values[i][temp] = '\0';
	}
      sym->values[i] = (char *)NULL;
      break;

    case symtype_FUNCTION:
      /* We read no data for C code primitives at this time. */
      break;

    case symtype_BINARY:
      /* Read in and create a datablock value. */
      {
	Datablock *block = (Datablock *)xmalloc (sizeof (Datablock));
	block->length = read_integer (fd);
	block->data = (unsigned char *)xmalloc (block->length);
	read_data (fd, block->length, block->data);
      }
      break;

    case symtype_USERFUN:
      /* Read in the user function definition. */
      {
	UserFunction *fun = (UserFunction *)xmalloc (sizeof (UserFunction));

	memset (fun, 0, sizeof (UserFunction));
	/* Get the function type. */
	read_data (fd, 1, &byte);
	fun->type = (int)byte;

	/* Get the flags. */
	read_data (fd, 1, &byte);
	fun->flags = (int)byte;
	fun->debug_level = 0;

	/* Put the function's name in place.  It is the same as the name of
	   this symbol. */
	fun->name = strdup (sym->name);

	/* Get the name of the surrounding package. */
	read_data (fd, 1, &byte);

	if (byte != 0)
	  {
	    fun->packname = (char *)xmalloc (1 + (int) byte);
	    read_data (fd, (int)byte, fun->packname);
	    fun->packname[(int)byte] = '\0';
	  }
	else
	  fun->packname = 0;

	/* Get named parameters list. */
	read_data (fd, 1, &byte);

	if (byte != 0)
	  {
	    unsigned char l;

	    fun->named_parameters = 
	      (char **)xmalloc ((1 + byte) * sizeof (char *));

	    for (i = 0; i < byte; i++)
	      {
		read_data (fd, 1, &l);
		fun->named_parameters[i] = (char *)xmalloc (1 + l);
		read_data (fd, (int)l, fun->named_parameters[i]);
		fun->named_parameters[i][(int)l] = '\0';
	      }
	    fun->named_parameters[i] = (char *)NULL;
	  }
	else
	  fun->named_parameters = (char **)NULL;

	/* Get the function body. */
	i = read_integer (fd);
	fun->body = (char *)xmalloc (1 + i);
	read_data (fd, i, fun->body);
	fun->body[i] ='\0';

	/* Make this the value of this symbol. */
	sym->values = (char **)fun;
      }
      break;

    default:
      abort ();
    }

  return (sym);
}

/* Write out PACKAGE to FD such that SYMBOL_LOAD_PACKAGE can read
   it in again. */
void
symbol_dump_package (int fd, Package *package)
{
  register int i;
  unsigned char byte;
  Symbol **symbols = package ? symbols_of_package (package) : (Symbol **)NULL;

  if (symbols == (Symbol **)NULL)
    return;

  for (i = 0; symbols[i] != (Symbol *)NULL; i++);

  /* Start by writing the name of this package. */
  byte = (unsigned char) package->name_len;
  write_data (fd, 1, &byte);
  write_data (fd, (int)byte, package->name);

  /* Next, write the number of symbols present in this package. */
  write_integer (fd, i);

  /* Now write each symbol, and its data. */
  for (i = 0; symbols[i] != (Symbol *)NULL; i++)
    write_symbol (symbols[i], fd);

  /* That's all we wrote! */
}

/* Read a package from FD, installing the symbols and values found within. */
Package *
symbol_load_package (int fd)
{
  register int i;
  int num_syms = 0;
  unsigned char byte;
  static char *buffer = (char *)NULL;
  static int buflen = 0;
  Package *package = (Package *)NULL;

  /* Read the length of the package name. */
  if (read_data (fd, 1, &byte) < 1)
    return (package);

  if (byte + 1 > buflen)
    buffer = (char *)xrealloc (buffer, (buflen += (byte + 128)));

  /* Read the package name. */
  read_data (fd, (int)byte, buffer);
  buffer[(int)byte] = '\0';
  package = symbol_get_package (buffer);

  /* Get the number of symbols stored in this package. */
  num_syms = read_integer (fd);

  /* Read those symbols into this package. */
  for (i = 0; i < num_syms; i++)
    read_symbol (fd, package);

  return (package);
}
