/****************************************************************************
*       worldio.c
*
*       World I/O Routines
* **********************************************************************
* *     Copyright (c) by Barak Pearlmutter and Kevin Lang, 1987-98.    *
* *     Portions copyright (c) by Alex Stuebinger, 1998.               *
* *     Placed under the terms of the GNU General Public License       *
* *     as published by the Free Software Foundation.                  *
* **********************************************************************
*
*       Revision log:
*       *************
*
*       date     time   author  comments
*       ================================================================
*       17/01/98 15:13  stuebi  initial revision
*       ----------------------------------------------------------------
*
*       ================================================================
*
*
*       Description:
*       ************
*
*
*
*       Planned:
*       ********
*
*
*
*/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "emulator.h"


extern int byte_order;


/* These are for making the world zero-based and contiguous in dumps. */

static ref contig(ref r, bool just_new)
/*************************************/
{
  ref *p = ANY_TO_PTR(r);

  if (just_new)
    if (NEW_PTR(p))
      return ((ref)(p - _new.start)<<2 ) | (r&3);
    else
      printf("Non-new pointer %ld found.\n", r);
  else if (SPATIC_PTR(p))
    return ((ref)(p - spatic.start)<<2) | (r&3);
  else if (NEW_PTR(p))
    return ((ref)(p - _new.start + spatic.size)<<2) | (r&3);
  else
    printf("Non-new or spatic pointer %ld found.\n", r);

  return r;
}

#define contigify(r) ((r)&0x2 ? contig((r),just_new) : (r))
#define CONTIGIFY(v) { if ((v)&2) (v) = contig((v),just_new); }


bool dump_decimal= FALSE;	/* dumped worlds in base 10 (not 16) */
bool dump_binary = FALSE;	/* dumped worlds in base 2  (not 16) */


/*
 * Format of Oaklisp world image:
 *
 * UNUSED: <size of value stack>
 * UNUSED: <size of context stack>
 * <reference to method for booting>
 * <number of words to load>
 *
 * <words to load>
 *
 * <size of weak pointer table>
 * <contents of weak pointer table>
 */


bool input_is_binary;

#ifdef __mac__

static long flread(char *ptr, unsigned int size_of_ptr,
                   long count, FILE *stream)
/******************************************************/
{
  long		result = 0;
  int		block, items;

  while (count > 0) {
    block = (count > 32000 ? 32000 : count);
    items = fread(ptr, size_of_ptr, block, stream);
    ptr = (char *) ((long) size_of_ptr * items + (long) ptr);
    count  -= items;
    result += items;
  }

  return result;
}

#else   /* not __mac__ */

#define flread(a,b,c,d) fread((a),(b),(size_t)(c),(d))

#endif


static ref read_ref(FILE *d)  /* Read a reference from a file: */
/**************************/
{   int c;
    ref a = 0;

    /* It's easy to read a reference from a binary file. */
    if (input_is_binary) {
        flread((void *)&a, sizeof(ref), 1L, d);
        return a;
    } else {
        if( byte_order == __little_endian__ )
        {   while ( isspace(c=getc(d)) )
                if (c == EOF)
                    {   printf("Apparently truncated cold load file!\n");
                        exit(EXIT_FAILURE);
                    }
            {   bool swapem = c == '^';
                if (swapem)
                    if ((c = getc(d)) == EOF)
                    {   printf("Apparently truncated cold load file!\n");
                        exit(EXIT_FAILURE);
                    }
                while (isxdigit(c))
                {   a = a<<4;
                    if (c <= '9') a |= ((ref) c-'0');
                    else if (c <= 'Z') a |= ((ref) c-'A'+10);
                    else a |= ((ref) c-'a'+10);
                    c = getc(d);
                }
                if (c=='^') ungetc(c, d);
                if (swapem) a = (a<<16 | a>>16);
            }
            return a;
        } else { /* byte_order == __big_endian__ */
            while ( isspace(c=getc(d)) || c=='^' )
                if (c == EOF){
                    printf("Apparently truncated cold load file!\n");
                    exit(EXIT_FAILURE);
                }
            while (isxdigit(c))
            {   a = a<<4;
                if (c <= '9') a |= ((ref) c-'0');
                else if (c <= 'Z') a |= ((ref) c-'A'+10);
                else a |= ((ref) c-'a'+10);
                c = getc(d);
            }
            return a;
        } /* byte_order */
    } /* input_is_binary */
}




static FILE *prompt_file(char *the_prompt,char *mode)
/***************************************************/
{
  char filename[80];
  FILE *fp;

  while (TRUE)
    {
      (void)printf(the_prompt);
      if (scanf("%s", filename) == EOF)
	/* Fix the problem with EOF in batch files. */
	{
	  (void)fprintf(stderr, "EOF in input.\n");
	  exit(1);
	}
      fp = fopen(filename, mode);
      if (fp != NULL)
	return fp;
      else
	(void)fprintf(stderr,"Can't open %s.\n", filename);
    }
}


#define REFBUFSIZ 256

static void dump_binary_world(bool just_new)
/******************************************/
{
  FILE *wfp = NULL;
  ref *memptr;
  ref theref;
  static ref refbuf[REFBUFSIZ];

  int imod = 0;
  unsigned long worlsiz = free_point - _new.start;
  unsigned long DUMMY = 0;

  if (dump_file != NULL && ((wfp=fopen(dump_file,WRITE_BINARY_MODE)) == NULL))
    fprintf(stderr, "Can't open \"%s\"; querying user.\n", dump_file);

  while (wfp == NULL)
    {
      wfp = prompt_file("Binary world file to write: ", WRITE_BINARY_MODE);
    }

  if (!just_new) worlsiz += spatic.size;

  (void)putc('\002', wfp); (void)putc('\002', wfp);
  (void)putc('\002', wfp); (void)putc('\002', wfp);

  /* Header information. */
  (void)fwrite((char *)&DUMMY, sizeof(ref), 1, wfp);
  (void)fwrite((char *)&DUMMY, sizeof(ref), 1, wfp);
  theref = contigify(e_boot_code);
  (void)fwrite((char *)&theref, sizeof(ref), 1, wfp);
  (void)fwrite((char *)&worlsiz, sizeof(ref), 1, wfp);

  /* Dump the heap. */
  /* Maybe dump spatic space. */
  if (!just_new)
    for (memptr = spatic.start; memptr < spatic.end; memptr++)
      {
	theref = *memptr;
	CONTIGIFY(theref);
	refbuf[imod++] = theref;
	if (imod == REFBUFSIZ)
	  {
	    (void)fwrite((char *)&refbuf[0], sizeof(ref), imod, wfp);
	    imod = 0;
	  }
      }
  /* Dump new space. */
  for (memptr = _new.start; memptr < free_point; memptr++)
    {
      theref = *memptr;
      CONTIGIFY(theref);
      refbuf[imod++] = theref;
      if (imod == REFBUFSIZ)
	{
	  (void)fwrite((char *)&refbuf[0], sizeof(ref), imod, wfp);
	  imod = 0;
	}
    }
  if (imod != 0)
    (void)fwrite((char *)&refbuf[0], sizeof(ref), imod, wfp);


  /* Weak pointer table. */
  theref = (ref)wp_index;
  (void)fwrite((char *)&theref, sizeof(ref), 1, wfp);

  for (imod = 0; imod<wp_index; imod++)
    {
      theref = wp_table[1+imod];
      CONTIGIFY(theref);
      (void)fwrite((char *)&theref, sizeof(ref), 1, wfp);
    }

  (void)fclose(wfp);
}


static void dump_ascii_world(bool just_new)
/*****************************************/
{
  ref *memptr, theref;
  long	i;
  int	eighter = 0;
  char *control_string = (dump_decimal ? "%ld " : "%lx ");
  FILE *wfp = NULL;


  if (dump_file != NULL && ((wfp = fopen(dump_file, WRITE_MODE)) == NULL))
    fprintf(stderr, "Can't open \"%s\"; querying user.\n", dump_file);

  while (wfp == NULL)
    {
      wfp = prompt_file("World file to write: ", WRITE_MODE);
    }

  (void)fprintf(wfp, control_string, 0 /*val_stk_size*/);
  (void)fprintf(wfp, control_string, 0 /*cxt_stk_size*/);
  (void)fprintf(wfp, control_string, contigify(e_boot_code));
  (void)fprintf(wfp, control_string,
        free_point - _new.start + (just_new ? 0 : spatic.size));

  /* Maybe dump spatic space. */
  if (!just_new)
    for (memptr = spatic.start; memptr < spatic.end; memptr++)
      {
	if (eighter == 0) (void)fprintf(wfp, "\n");
	theref = *memptr;
	CONTIGIFY(theref);
	(void)fprintf(wfp,control_string, theref);
	eighter = (eighter + 1) % 8;
      }

  eighter = 0;
  for (memptr = _new.start; memptr < free_point; memptr++)
    {
      if (eighter == 0) (void)fprintf(wfp, "\n");
      theref = *memptr;
      CONTIGIFY(theref);
      (void)fprintf(wfp,control_string, theref);
      eighter = (eighter + 1) % 8;
    }
  (void)fprintf(wfp, "\n");

  /* Write the weak pointer table. */

  (void)fprintf(wfp, control_string, wp_index);

  eighter = 0;

  for (i = 0; i<wp_index; i++)
    {
      if (eighter == 0) (void)fprintf(wfp, "\n");
      theref = wp_table[1+i];
      CONTIGIFY(theref);
      (void)fprintf(wfp, control_string, theref);
      eighter = (eighter + 1) % 8;
    }

  (void)fclose(wfp);
}

void dump_world(bool just_new)
/****************************/
{
  if (dump_binary)
    dump_binary_world(just_new);
  else
    dump_ascii_world(just_new);
}



static void reoffset(register ref baseAddr,
                     register ref *start,
                     register long count)
/*****************************************/
{
  register long	index;
  register ref	*next;

  next = start;
  for (index=0; index<count; index++) {
    if ( *next&2 )
      *next += baseAddr;
    next++;
  }
}

void read_world(char *str)
/************************/
{
  FILE *d;
  int magichar;

  /* Replace "++" in file name with "ol" on bigendian machines and
     with "lo" on littleendian ones. */

  {
    char *p = str,
         *next;

    if( byte_order == __little_endian__ )
        next = "lo";
    else
        next = "ol";

    while ((*p != '\0') && (*next != '\0'))
      if (*p == '+') *p++ = *next++;
      else p++;
  }

  if ((d = fopen(str, READ_BINARY_MODE)) == NULL)
    {
      (void)printf("Can't open \"%s\".\n", str);
      exit(EXIT_FAILURE);
    }

  magichar = getc (d);
  if (magichar == (int)'\002')
    {
      (void)getc(d); (void)getc(d); (void)getc(d);
      input_is_binary = 1;
    }
  else
    {
      (void)ungetc(magichar, d);
      input_is_binary = 0;
      if( byte_order == __little_endian__ )
         printf("Little Endian.\n");
      else
         printf("Big Endian.\n");
    }

  /* OBSOLESCENT: read val_space_size and cxt_space_size: */
  (void)read_ref(d);
  (void)read_ref(d);

  e_boot_code = read_ref(d);

  spatic.size = (unsigned long)read_ref(d);
  alloc_space(&spatic);

  e_boot_code += (ref)spatic.start;

  {
    long load_count;
    ref *mptr, next;

    load_count = spatic.size;
    mptr = spatic.start;

    if (input_is_binary) {
      (void)flread((char *)spatic.start, sizeof(ref), load_count, d);
      reoffset((ref) spatic.start, spatic.start, load_count);
    }
    else
      while (load_count != 0)
	{
	  next = read_ref(d);
	  if ( next&2 ) next += (ref)spatic.start;
	  *mptr++ = next;
	  --load_count;
	}
    /* Load the weak pointer table. */
    wp_index = read_ref(d);
    (void)flread((char *)&wp_table[1], sizeof(ref), (long)wp_index, d);
    reoffset((ref) spatic.start, &wp_table[1], wp_index);
  }

  /* The weak pointer hash table is rebuilt when e_nil is set. */

  (void)fclose(d);
}





