/*
 * fileio.c
 *
 * File manipulation routines. Should be generic.
 *
 */

#include "ztypes.h"
#include "jzexe.h"  /* mol 951115 */

/* Static data */

extern int GLOBALVER;

static FILE *gfp = NULL; /* Game file pointer */
static FILE *sfp = NULL; /* Script file pointer */
static FILE *rfp = NULL; /* Record file pointer */
static char save_name[FILENAME_MAX + 1] = "story.sav";
static char script_name[FILENAME_MAX + 1] = "script.scr";
static char record_name[FILENAME_MAX + 1] = "commands.rec";

static int undo_valid = FALSE;
static zword_t undo_stack[STACK_SIZE];

static int script_file_valid = FALSE;

static long story_offset=0;                            /* mol 951114 */
unsigned char *magic = (unsigned char *) MAGIC_STRING; /* mol */

#ifdef __STDC__
static int save_restore (const char *, int);
#else
static int save_restore ();
#endif

/*
 * analyze_exefile
 *
 * This function is called if the game file seems to be a JZEXE file
 * (standalone game file). If that is the case, story_offset is set
 * to the beginning of the Z code and the function returns TRUE,
 * otherwise no action is taken and it returns FALSE.
 *
 * Magnus Olsson, November 1995
 */
#ifdef __STDC__
int analyze_exefile(void)
#else
int analyze_exefile()
#endif
{
  int c, i;
  
  if (story_offset > 0)
    /* This is wrong; we shouldn't be doing this. */
    return FALSE;
  
  /* Look for the magic string, starting from the beginning. */    
  rewind (gfp); 
  i = 0;

  while ((c = getc(gfp)) > -1) {
    if (c != magic[i]) {
      if (c == magic[0])
	i = 1; /* Next character should match magic[1] */
      else 
	i = 0;
    }
    else if (++i == MAGIC_END) {
      /* Found the magic string! The next byte must be zero. */
      if (getc(gfp) != 0)
	return FALSE;

      /* Read offset and return. We won't concern ourselves with possible
       read errors, since their consequences will be detected later on, 
       when we try to interpret the file as Z code. */
      story_offset = getc(gfp);
      story_offset += 256L * getc(gfp);
      story_offset += 65536L * getc(gfp);
      return TRUE;
    }
  }
  /* If we get here, we've reached the end of the infile without success. */
  return FALSE;
}


/*
 * set_names
 *
 * Set up the story names intelligently.
 * John Holder, 28 Sept 1995
 */
#ifdef __STDC__
void set_names (const char *storyname)
#else
void set_names (storyname)
const char *storyname;
#endif
{
    char *per_pos=0;

    /* experimental setting of save_name, added by John Holder 26 July 1995 */
    per_pos=strrchr(storyname,'.'); /* find last '.' in storyname. */
    if (per_pos) /* The story file looks like "Odius.dat" or "odieus.z3" */
    {
      strcpy(save_name, storyname);
      per_pos=strrchr(save_name,'.');
      *(per_pos)='\0';
      strcat(save_name,".sav");

      strcpy(script_name, storyname);
      per_pos=strrchr(script_name,'.');
      *(per_pos)='\0';
      strcat(script_name,".scr");

      strcpy(record_name, storyname);
      per_pos=strrchr(record_name,'.');
      *(per_pos) = '\0';
      strcat(record_name,".rec");
    }
    else /* The story file looks like: "OdieusQuest" */
    {
      strcat(save_name,".sav");
      strcat(script_name,".src");
      strcat(record_name,".rec");
    }
} /* set_names */

/*
 * open_story
 *
 * Open game file for read.
 *
 */

#ifdef __STDC__
void open_story (const char *storyname)
#else
void open_story (storyname)
const char *storyname;
#endif
{
    char *path, *p;
    char tmp[PATHNAME_MAX+1];

    if (!STANDALONE_FLAG)
      story_offset=0;
    else {
      /* standalone game; offset to story start is saved in low-endian */
      /* format after magic string */
      story_offset = magic[MAGIC_END+1] +
		     magic[MAGIC_END+2] * 256L +
		     magic[MAGIC_END+3] * 65536L;
    }

    if ((gfp = fopen (storyname, "rb"))!=NULL) {
      set_names(storyname);
      return;
    }

    if ((path = getenv("INFOCOM_PATH")) == NULL)
      fatal ("Game file not found");

/* dos path will be like:                                                 */
/* SET INFOCOM_PATH = C:\INFOCOM\LTOI1;C:\INFOCOM\LTOI2;C:\INFOCOM\INFORM */
#if defined(MSDOS)
    p = strtok(path,";");
#else
/* UNIX path will be like:                                                */
/* setenv INFOCOM_PATH /usr/local/lib/ltoi1:/usr/local/lib/ltoi2          */
    p = strtok(path,":");
#endif

    while (p)
    {
      sprintf(tmp,"%s/%s",p,storyname);
      if ((gfp = fopen (tmp, "rb"))!=NULL) {
	set_names(storyname);
	return;
      }
#if defined(MSDOS)
      p = strtok(NULL, ";");
#else
      p = strtok(NULL, ":");
#endif
    }

    fatal ("Game file not found");
}/* open_story */


/*
 * close_story
 *
 * Close game file if open.
 *
 */

#ifdef __STDC__
void close_story (void)
#else
void close_story ()
#endif
{

    if (gfp != NULL)
	fclose (gfp);

}/* close_story */

/*
 * get_story_size
 *
 * Calculate the size of the game file. Only used for very old games that do not
 * have the game file size in the header.
 *
 */

#ifdef __STDC__
unsigned int get_story_size (void)
#else
unsigned int get_story_size ()
#endif
{
    unsigned long file_length;

    /* Read whole file to calculate file size */

    rewind (gfp);
    for (file_length = 0; fgetc (gfp) != EOF; file_length++)
	;
    rewind (gfp);

    /* Calculate length of file in game allocation units */

    file_length = (file_length + (unsigned long) (story_scaler - 1)) / (unsigned long) story_scaler;

    return ((unsigned int) file_length);

}/* get_story_size */

/*
 * read_page
 *
 * Read one game file page.
 *
 */

#ifdef __STDC__
void read_page (int page, void *buffer)
#else
void read_page (page, buffer)
int page;
void *buffer;
#endif
{
    unsigned long file_size;
    unsigned int pages, offset;

    /* Seek to start of page */
    fseek (gfp, story_offset + (long) page * PAGE_SIZE, SEEK_SET);

    /* Read the page */

    if (fread (buffer, PAGE_SIZE, 1, gfp) != 1) {

	/* Read failed. Are we in the last page? */

	file_size = (unsigned long) h_file_size * story_scaler;
	pages = (unsigned int) ((unsigned long) file_size / PAGE_SIZE);
	offset = (unsigned int) ((unsigned long) file_size & PAGE_MASK);
	if ((unsigned int) page == pages) {

	    /* Read partial page if this is the last page in the game file */

	    fseek (gfp, story_offset + (long) page * PAGE_SIZE, SEEK_SET);
	    if (fread (buffer, offset, 1, gfp) == 1)
		return;
	}
	fatal ("Game file read error");
    }

}/* read_page */

/*
 * verify
 *
 * Verify game ($verify verb). Add all bytes in game file except for bytes in
 * the game file header.
 *
 */

#ifdef __STDC__
void verify (void)
#else
void verify ()
#endif
{
    unsigned long file_size;
    unsigned int pages, offset;
    unsigned int start, end, i, j;
    zword_t checksum = 0;
    zbyte_t buffer[PAGE_SIZE];

    /* Print version banner */

/*      print_number (get_byte (H_INTERPRETER)); */
    new_line();
    write_string ("Running on ");
    write_string (JZIPVER);
    write_string (" (");
    switch (JTERP)
    {
      case INTERP_GENERIC: write_string ("Generic");
			   break;
      case INTERP_MACINTOSH:  write_string ("Macintosh");
			      break;
      case INTERP_AMIGA:  write_string ("Amiga");
			      break;
      case INTERP_ATARI_ST:  write_string ("Atari ST");
			      break;
      case INTERP_MSDOS:  write_string ("DOS");
			      break;
      case INTERP_TANDY:  write_string ("Tandy");
			      break;
      case INTERP_UNIX:  write_string ("UNIX");
			      break;
      case INTERP_VMS:  write_string ("VMS");
			      break;
    }
    write_string (").");
    new_line();
    write_string ("Release ");
    write_string (JZIPRELDATE);
    new_line();
    write_string ("Playing a Version ");
    print_number (GLOBALVER);
    write_string (" Story.");

    new_line();
/*
     write_char (get_byte (H_INTERPRETER_VERSION));
     write_string (".");
*/
    new_line ();

    /* Calculate game file dimensions */

    file_size = (unsigned long) h_file_size * story_scaler;
    pages = (unsigned int) ((unsigned long) file_size / PAGE_SIZE);
    offset = (unsigned int) file_size & PAGE_MASK;

    /* Sum all bytes in game file, except header bytes */

    for (i = 0; i <= pages; i++) {
	read_page (i, buffer);
	start = (i == 0) ? 64 : 0;
	end = (i == pages) ? offset : PAGE_SIZE;
	for (j = start; j < end; j++)
	    checksum += buffer[j];
    }

    /* Make a conditional jump based on whether the checksum is equal */

    conditional_jump (checksum == h_checksum);

}/* verify */

/*
 * save
 *
 * Save game state to disk. Returns:
 *     0 = save failed
 *     1 = save succeeded
 *
 */

#ifdef __STDC__
int save (void)
#else
int save ()
#endif
{
    char new_save_name[FILENAME_MAX + 1];
    int status = 1;

    /* Get the file name */

    if (get_file_name (new_save_name, save_name, GAME_SAVE) == 0) {

	/* Do a save operation */

	if (save_restore (new_save_name, GAME_SAVE) == 0) {

	    /* Cleanup file */

	    file_cleanup (new_save_name, GAME_SAVE);

	    /* Save the new name as the default file name */

	    strcpy (save_name, new_save_name);

	    /* Indicate success */

	    status = 0;

	}

    }

    /* Return result of save to Z-code */

    if (h_type < V4)
	conditional_jump (status == 0);
    else
	store_operand ((status == 0) ? 1 : 0);

    return (status);

}/* save */

/*
 * restore
 *
 * Restore game state from disk. Returns:
 *     0 = restore failed
 *     2 = restore succeeded
 *
 */

#ifdef __STDC__
int restore (void)
#else
int restore ()
#endif
{
    char new_save_name[FILENAME_MAX + 1];
    int status = 1;

    /* Get the file name */

    if (get_file_name (new_save_name, save_name, GAME_RESTORE) == 0) {

	/* Do the restore operation */

	if (save_restore (new_save_name, GAME_RESTORE) == 0) {

	    /* Reset the status region (this is just for Seastalker) */

	    if (h_type < V4) {
		set_status_size (0);
		blank_status_line ();
	    }

	    /* Cleanup file */

	    file_cleanup (new_save_name, GAME_SAVE);

	    /* Save the new name as the default file name */

	    strcpy (save_name, new_save_name);

	    /* Indicate success */

	    status = 0;

	}

    }

    /* Return result of save to Z-code */

    if (h_type < V4)
	conditional_jump (status == 0);
    else
	store_operand ((status == 0) ? 2 : 0);

    return (status);

}/* restore */

/*
 * undo_save
 *
 * Save the current Z machine state in memory for a future undo. Returns:
 *    -1 = feature unavailable
 *     0 = save failed
 *     1 = save succeeded
 *
 */

#ifdef __STDC__
void undo_save (void)
#else
void undo_save ()
#endif
{

    /* Check if undo is available first */

    if (undo_datap != NULL) {

	/* Save the undo data and return success */

	save_restore (NULL, UNDO_SAVE);

	undo_valid = TRUE;

	store_operand (1);

    } else 

	/* If no memory for data area then say undo is not available */

	store_operand ((zword_t) -1);

}/* undo_save */

/*
 * undo_restore
 *
 * Restore the current Z machine state from memory. Returns:
 *    -1 = feature unavailable
 *     0 = restore failed
 *     2 = restore succeeded
 *
 */

#ifdef __STDC__
void undo_restore (void)
#else
void undo_restore ()
#endif
{

    /* Check if undo is available first */

    if (undo_datap != NULL) {

	/* If no undo save done then return an error */

	if (undo_valid == TRUE) {

	    /* Restore the undo data and return success */

	    save_restore (NULL, UNDO_RESTORE);

	    store_operand (2);

	} else

	    store_operand (0);

    } else 

	/* If no memory for data area then say undo is not available */

	store_operand ((zword_t) -1);

}/* undo_restore */

/*
 * swap_bytes
 *
 * Swap the low and high bytes in every word of the specified array.
 * The length is specified in BYTES!
 *
 * This routine added by Mark Phillips(msp@bnr.co.uk), Thanks Mark!
 */

#ifdef __STDC__
void swap_bytes(zword_t *ptr, int len)
#else
void swap_bytes(ptr, len)
zword_t *ptr;
int len;
#endif
{
  unsigned char *pbyte;
  unsigned char tmp;

  len/=2; /* convert len into number of 2 byte words */

  pbyte=(unsigned char*)ptr;

  while (len)
  {
    tmp=pbyte[0];
    pbyte[0]=pbyte[1];
    pbyte[1]=tmp;
    pbyte+=2;
    len--;
  }
  return;
}


/*
 * save_restore
 *
 * Common save and restore code. Just save or restore the game stack and the
 * writeable data area.
 *
 */

#ifdef __STDC__
static int save_restore (const char *file_name, int flag)
#else
static int save_restore (file_name, flag)
const char *file_name;
int flag;
#endif
{
    FILE *tfp = NULL;
    int scripting_flag = 0, status = 0;
    zword_t zw;
    int little_endian=0;

    /* Find out if we are big-endian */
    zw=0x0001;
    if ( *(zbyte_t *)&zw )
    {      /* We are little-endian, like an Intel 80x86 chip. */
      little_endian=1;
    }

    /* Open the save file and disable scripting */

    if (flag == GAME_SAVE || flag == GAME_RESTORE) {
	if ((tfp = fopen (file_name, (flag == GAME_SAVE) ? "wb" : "rb")) == NULL) {
	    output_line ("Cannot open SAVE file");
	    return (1);
	}
	scripting_flag = get_word (H_FLAGS) & SCRIPTING_FLAG;
	set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));
    }

    /* Push PC, FP, version and store SP in special location */

    stack[--sp] = (zword_t) (pc / PAGE_SIZE);
    stack[--sp] = (zword_t) (pc % PAGE_SIZE);
    stack[--sp] = fp;
    stack[--sp] = h_version;
    stack[0] = sp;

    /* Save or restore stack */

    if (flag == GAME_SAVE) {
	if (little_endian) swap_bytes(stack, sizeof(stack));  
	if (status == 0 && fwrite (stack, sizeof (stack), 1, tfp) != 1)
	    status = 1;        
	if (little_endian) swap_bytes(stack, sizeof(stack));  
    } else if (flag == GAME_RESTORE) {
	if (little_endian) swap_bytes(stack, sizeof(stack));  
	if (status == 0 && fread (stack, sizeof (stack), 1, tfp) != 1)
	    status = 1;
	if (little_endian) swap_bytes(stack, sizeof(stack));  
    } else if (flag == UNDO_SAVE) {
	memmove (undo_stack, stack, sizeof (stack));
    } else /* if (flag == UNDO_RESTORE) */
	memmove (stack, undo_stack, sizeof (stack));

    /* Restore SP, check version, restore FP and PC */

    sp = stack[0];
    if (stack[sp++] != h_version)
	fatal ("Wrong game or version");
    fp = stack[sp++];
    pc = stack[sp++];
    pc += (unsigned long) stack[sp++] * PAGE_SIZE;

    /* Save or restore writeable game data area */

    if (flag == GAME_SAVE) {
	if (status == 0 && fwrite (datap, h_restart_size, 1, tfp) != 1)
	    status = 1;
    } else if (flag == GAME_RESTORE) {
	if (status == 0 && fread (datap, h_restart_size, 1, tfp) != 1)
	    status = 1;
    } else if (flag == UNDO_SAVE) {
	memmove (undo_datap, datap, h_restart_size);
    } else /* if (flag == UNDO_RESTORE) */
	memmove (datap, undo_datap, h_restart_size);

    /* Close the save file and restore scripting */

    if (flag == GAME_SAVE || flag == GAME_RESTORE) {
	fclose (tfp);
	if (scripting_flag)
	    set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);
    }

    /* Handle read or write errors */

    if (status) {
	if (flag == GAME_SAVE) {
	    output_line ("Write to SAVE file failed");
	    remove (file_name);
	} else {
	    output_line ("Read from SAVE file failed");
	}
    }

    return (status);

}/* save_restore */

/*
 * open_script
 *
 * Open the scripting file.
 *
 */

#ifdef __STDC__
void open_script (void)
#else
void open_script ()
#endif
{
    char new_script_name[FILENAME_MAX + 1];

    /* Open scripting file if closed */

    if (scripting == OFF) {

	if (script_file_valid == TRUE) {

	    sfp = fopen (script_name, "a");

	    /* Turn on scripting if open succeeded */

	    if (sfp != NULL)
		scripting = ON;
	    else
		output_line ("Script file open failed");

	} else {

	    /* Get scripting file name and record it */

	    if (get_file_name (new_script_name, script_name, GAME_SCRIPT) == 0) {

		/* Open scripting file */

		sfp = fopen (new_script_name, "w");

		/* Turn on scripting if open succeeded */

		if (sfp != NULL) {

		    script_file_valid = TRUE;

		    /* Make file name the default name */

		    strcpy (script_name, new_script_name);

		    /* Turn on scripting */

		    scripting = ON;
	    
		} else

		    output_line ("Script file create failed");

	    }

	}

    }

    /* Set the scripting flag in the game file flags */

    if (scripting == ON)
	set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);
    else
	set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));

}/* open_script */

/*
 * close_script
 *
 * Close the scripting file.
 *
 */

#ifdef __STDC__
void close_script (void)
#else
void close_script ()
#endif
{

    /* Close scripting file if open */

    if (scripting == ON) {

	fclose (sfp);
#if 0
	/* Cleanup */

	file_cleanup (script_name, GAME_SCRIPT);
#endif
	/* Turn off scripting */

	scripting = OFF;

    }

    /* Set the scripting flag in the game file flags */

    if (scripting == OFF)
	set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));
    else
	set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);

}/* close_script */

/*
 * script_char
 *
 * Write one character to scripting file.
 *
 * Check the state of the scripting flag first. Older games only set the
 * scripting flag in the game flags instead of calling the set_print_modes
 * function. This is because they expect a physically attached printer that
 * doesn't need opening like a file.
 */

#ifdef __STDC__
void script_char (int c)
#else
void script_char (c)
int c;
#endif
{

    /* Check the state of the scripting flag in the game flags. If it is on
       then check to see if the scripting file is open as well */

    if ((get_word (H_FLAGS) & SCRIPTING_FLAG) != 0 && scripting == OFF)
	open_script ();

    /* Check the state of the scripting flag in the game flags. If it is off
       then check to see if the scripting file is closed as well */

    if ((get_word (H_FLAGS) & SCRIPTING_FLAG) == 0 && scripting == ON)
	close_script ();

    /* If scripting file is open, we are in the text window and the character is
       printable then write the character */

    if (scripting == ON && scripting_disable == OFF && (c == '\n' || (isprint (c))))
	putc (c, sfp);

}/* script_char */

/*
 * script_string
 *
 * Write a string to the scripting file.
 *
 */

#ifdef __STDC__
void script_string (const char *s)
#else
void script_string (s)
const char *s;
#endif
{

    /* Write string */

    while (*s)
	script_char (*s++);

}/* script_string */

/*
 * script_line
 *
 * Write a string followed by a new line to the scripting file.
 *
 */

#ifdef __STDC__
void script_line (const char *s)
#else
void script_line (s)
const char *s;
#endif
{

    /* Write string */

    script_string (s);

    /* Write new line */

    script_new_line ();

}/* script_line */

/*
 * script_new_line
 *
 * Write a new line to the scripting file.
 *
 */

#ifdef __STDC__
void script_new_line (void)
#else
void script_new_line ()
#endif
{

    script_char ('\n');

}/* script_new_line */

/*
 * open_record
 *
 * Turn on recording of all input to an output file.
 *
 */

#ifdef __STDC__
void open_record (void)
#else
void open_record ()
#endif
{
    char new_record_name[FILENAME_MAX + 1];

    /* If recording or playback is already on then complain */

    if (recording == ON || replaying == ON) {

	output_line ("Recording or playback are already active.");

    } else {

	/* Get recording file name */

	if (get_file_name (new_record_name, record_name, GAME_RECORD) == 0) {

	    /* Open recording file */

	    rfp = fopen (new_record_name, "w");

	    /* Turn on recording if open succeeded */

	    if (rfp != NULL) {

		/* Make file name the default name */

		strcpy (record_name, new_record_name);

		/* Set recording on */

		recording = ON;

	    } else

		output_line ("Record file create failed");

	}

    }

}/* open_record */

/*
 * record_line
 *
 * Write a string followed by a new line to the recording file.
 *
 */

#ifdef __STDC__
void record_line (const char *s)
#else
void record_line (s)
const char *s;
#endif
{

    if (recording == ON && replaying == OFF) {

	/* Write string */

	fprintf (rfp, "%s\n", s);

    }

}/* record_line */

/*
 * record_key
 *
 * Write a key followed by a new line to the recording file.
 *
 */

#ifdef __STDC__
void record_key (int c)
#else
void record_key (c)
int c;
#endif
{

    if (recording == ON && replaying == OFF) {

	/* Write the key */

	fprintf (rfp, "<%0o>\n", c);

    }

}/* record_key */

/*
 * close_record
 *
 * Turn off recording of all input to an output file.
 *
 */

#ifdef __STDC__
void close_record (void)
#else
void close_record ()
#endif
{

    /* Close recording file */

    if (rfp != NULL) {
	fclose (rfp);
	rfp = NULL;

	/* Cleanup */

	if (recording == ON)
	    file_cleanup (record_name, GAME_RECORD);
	else /* (replaying == ON) */
	    file_cleanup (record_name, GAME_PLAYBACK);
    }

    /* Set recording and replaying off */

    recording = OFF;
    replaying = OFF;

}/* close_record */

/*
 * open_playback
 *
 * Take input from command file instead of keyboard.
 *
 */

#ifdef __STDC__
void open_playback (int arg)
#else
void open_playback (arg)
int arg;
#endif
{
    char new_record_name[FILENAME_MAX + 1];

    /* If recording or replaying is already on then complain */

    if (recording == ON || replaying == ON) {

	output_line ("Recording or replaying is already active.");

    } else {

	/* Get recording file name */

	if (get_file_name (new_record_name, record_name, GAME_PLAYBACK) == 0) {

	    /* Open recording file */

	    rfp = fopen (new_record_name, "r");

	    /* Turn on recording if open succeeded */

	    if (rfp != NULL) {

		/* Make file name the default name */

		strcpy (record_name, new_record_name);

		/* Set replaying on */

		replaying = ON;

	    } else

		output_line ("Record file open failed");

	}

    }

}/* open_playback */

/*
 * playback_line
 *
 * Get a line of input from the command file.
 *
 */

#ifdef __STDC__
int playback_line (int buflen, char *buffer, int *read_size)
#else
int playback_line (buflen, buffer, read_size)
int buflen;
char *buffer;
int *read_size;
#endif
{
    char *cp;

    if (recording == ON || replaying == OFF)
	return (-1);

    if (fgets (buffer, buflen, rfp) == NULL) {
	close_record ();
	return (-1);
    } else {
	cp = strrchr (buffer, '\n');
	if (cp != NULL)
	    *cp = '\0';
	*read_size = strlen (buffer);
	output_line (buffer);
    }

    return ('\n');

}/* playback_line */

/*
 * playback_key
 *
 * Get a key from the command file.
 *
 */

#ifdef __STDC__
int playback_key (void)
#else
int playback_key ()
#endif
{
    int c;

    if (recording == ON || replaying == OFF)
	return (-1);

    if (fscanf (rfp, "<%o>\n", &c) == EOF) {
	close_record ();
	c = -1;
    }

    return (c);

}/* playback_key */
