/*  Copyright (C) 1987 Barak Pearlmutter and Kevin Lang    */


#include <stdio.h>
#include <ctype.h>
#include "emulator.h"


#ifdef PROTOTYPES

extern ref	contig( ref r, bool just_new);
extern long	flread( char *ptr, 		
			unsigned int size_of_ptr, 		
			long count, 		
			FILE *stream);		
extern ref	read_ref(FILE *d);		
extern FILE *	prompt_file(char *the_prompt, char *mode);		
extern void	dump_binary_world(bool just_new);
extern void	dump_ascii_world(bool just_new);
extern void	dump_world(bool just_new);
extern void	reoffset(ref baseAddr, ref *start, long count);
extern void	read_world(char *str);
extern long	string_to_int(char string[]);

#endif

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

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

  if (just_new)
    if (NEW_PTR(p))
      return (p - new.start)*4 | r&3;
    else
      printf("Non-new pointer %ld found.\n", r);
  else if (SPATIC_PTR(p))
    return (p - spatic.start)*4 | r&3;
  else if (NEW_PTR(p))
    return (p - new.start + spatic.size)*4 | 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_LSC

long flread(ptr, size_of_ptr, count, stream)
	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

/* Odd that the 3rd arg of fread is int; long would be more intuitive. */
#define flread(a,b,c,d) fread((a),(b),(int)(c),(d))

#endif

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

  /* It's easy to read a reference from a binary file. */
  if (input_is_binary) {
    (void)flread((char *)&a, sizeof(ref), 1L, d);
    return a;
  }

#ifdef BIG_ENDIAN
  while ( isspace(c=getc(d)) || c=='^' )
#else
  while ( isspace(c=getc(d)) )
#endif
    if (c == EOF)
      {
	(void)printf("Apparently truncated cold load file!\n");
	exit(1);
      }
  {
#ifndef BIG_ENDIAN
    bool swapem = c == '^';

    if (swapem)
      if ((c = getc(d)) == EOF)
	{
	  (void)printf("Apparently truncated cold load file!\n");
	  exit(1);
	}
#endif

    while (isxdigit(c))
      {
	a = a<<4;
	if (c <= '9')
	  a |= c-'0';
	else if (c <= 'Z')
	  a |= c-'A'+10;
	else
	  a |= c-'a'+10;
	c = getc(d);
      }

#ifndef BIG_ENDIAN
    if (c=='^') (void)ungetc(c,d);

    if (swapem)
      a = a<<16 | a>>16;
#endif
    
    return a;
  }
}




FILE *prompt_file(the_prompt,mode)
     char *the_prompt, *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 1024

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

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

#ifndef AMIGA
  extern fwrite();
#endif

  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);
}







void dump_ascii_world(just_new)
     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(just_new)
     bool just_new;
{
  if (dump_binary)
    dump_binary_world(just_new);
  else
    dump_ascii_world(just_new);
}



void reoffset(baseAddr, start, count)
	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(str)
     char *str;
{
  FILE *d;
  int magichar;

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

  {
    char *p = str;
#ifdef BIG_ENDIAN
    char *next = "ol";
#else
    char *next = "lo";
#endif

    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(1);
    }

  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;
#ifdef BIG_ENDIAN
      printf("Big Endian.\n");
#else
      printf("Little Endian.\n");
#endif
    }

  /* 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);
}





/* This is used to decode the -h option on the command line. */
long string_to_int(string)
     char string[];     
{
  long n = 0;
  char *cs = string;
  while(*cs >= '0' && *cs <= '9')
    n = n*10 + *cs++ - '0';
  while (1)
    switch(*cs++)
      {
      case 'K':
      case 'k':
	n *= 1024;
	break;
      case 'M':
      case 'm':
	n *= (1024 * 1024);
	break;
      case 'W':
      case 'w':
	n *= sizeof(ref);
	break;
      case '\0':
	return(n);
      default:
	printf("Unable to parse %s as a number.\n", string);
	exit(1);
      }
}

