/* (c) Copyright by Anders Chrigstroem 1993, All rights reserved */
/* Permission is granted to use this source code and any executables
 * created from this source code as part of the CD Gamedriver as long
 * as it is not used in any way whatsoever for monetary gain. */

#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef SEEK_SET  
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif

#include "config.h"

#ifdef BINARIES /* Skip this entire file if BINARIES not defined */
#include "patchlevel.h"
#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "exec.h"

#define MAGIC "CDBIN"

#define align(x) ( ((x) + (sizeof(void*)-1) )  &  ~(sizeof(void*)-1) )

extern int current_time;
extern int driver_mtime;
extern int comp_flag;

FILE *crfile(name)
    char *name;
{
    char *ptr, *dir = string_copy(name);
    struct stat st;

    ptr = dir;
    for (ptr = strchr(ptr, '/'); ptr; ptr = strchr(ptr + 1, '/'))
    {
	*ptr = '\0';

	if (stat(dir, &st) == -1)
	{
	    if (mkdir(dir, 0774) == -1)
	    {
		free(dir);
		return (FILE *) 0;
	    }
	}
	else if ((st.st_mode & S_IFMT) != S_IFDIR)
	{
	    if (unlink(dir) == -1 ||
		mkdir(dir, 0774) == -1)
	    {
		free(dir);
		return (FILE *) 0;
	    }
	}

	*ptr = '/';
    }
    free(dir);

    return fopen(name, "w");
}


static void
calculate_offsets(struct program *dest, struct program *prog)
{
    int i, j;
    
    for (i = 0; segm_desc[i].sections; i++)
    {
	struct section_desc *sec;
	char *block;

	if (segm_desc[i].ptr_offset != -1)
	    block = *(char **)(((char *)prog) + segm_desc[i].ptr_offset);
	else
	    block = (char *)prog;
	sec = segm_desc[i].sections;

	for (j = 0; sec[j].section != -1; j++)
	{
	    if (sec[j].ptr_offset != -1)
	    {
		*(int *)(((char *)dest) + sec[j].ptr_offset) = 
		    *(char **)(((char *)prog) + sec[j].ptr_offset) - block;
	    }
	}
    }
}

static void
restore_sections(struct program *dest, struct program *prog)
{
    int i, j;
    
    for (i = 0; segm_desc[i].sections; i++)
    {
	struct section_desc *sec;
	char *block;

	if (segm_desc[i].ptr_offset != -1)
	    block = *(char **)(((char *)prog) + segm_desc[i].ptr_offset);
	else
	    block = (char *)dest;

	if (segm_desc[i].swap_idx_offset != -1)
	    *(int *)(((char *)dest) + segm_desc[i].swap_idx_offset) = 0;

	sec = segm_desc[i].sections;

	for (j = 0; sec[j].section != -1; j++)
	{
	    if (sec[j].ptr_offset != -1)
	    {
		*(char **)(((char *)dest) + sec[j].ptr_offset) = 
		    block + *(int *)(((char *)dest) + sec[j].ptr_offset);
	    }
	}
    }
}

int 
save_binary(prog)
struct program *prog;
{
    int i;
    extern int driver_mtime;
    char *binname;
    extern struct object *master_ob;
    extern char *add_slash(char *);
    FILE *f;
    int strsize = 0;
    struct program pgm;
    int strsize_tell;
    struct svalue *ret;

    if (master_ob)
    {
	push_malloced_string(add_slash(prog->name));
	ret = apply_master_ob(M_VALID_SAVE_BINARY, 1);
	if (!ret || ret->type != T_NUMBER || !ret->u.number)
	{
	    if (comp_flag)
		fprintf(stderr, "BIN Save for %s not allowed!\n",
			prog->name);
	    return 0;
	}
    }
    binname = (char *)xalloc(strlen(BINARIES) + 
			     strlen(prog->name) + 1);
    sprintf(binname, "%s%s", BINARIES, prog->name);
    
    if ((f = crfile(binname)) == NULL)
    {
	if (comp_flag)
	    fprintf(stderr, "BIN Can't create file %s!\n",
		    binname);
	free(binname);
	return 0;
    }

    fwrite(MAGIC, sizeof(char), strlen(MAGIC), f);
    fwrite((char *)&driver_mtime, sizeof(driver_mtime), 1, f);
    strsize_tell = ftell(f);
    fwrite((char *)&strsize, sizeof(strsize), 1, f);
    
    load_lineno_from_swap(prog);

    /* Write out the different segments of the program */
    fwrite((char *)prog, prog->total_size, 1, f);
    fwrite((char *)prog->program, prog->exec_size, 1, f);

    fwrite((char *)prog->line_numbers, prog->debug_size, 1, f);

    /* Write the inherit names, and the names of the
       inherited programs to the strtab */
    for (i = 0; i < prog->num_inherited; i++)
    {
	char *str;
	struct program *iprog = prog->inherit[i].prog;
	char buff[100];
	
	str = iprog->name;
	fwrite(str, strlen(str) + 1, 1, f);
	strsize += strlen(str) + 1;
	sprintf(buff, "%d", iprog->mod_time);
	fwrite(buff, strlen(buff) + 1, 1, f);
	strsize += strlen(buff) + 1;

	str = prog->inherit[i].name;
	fwrite(str, strlen(str) + 1, 1, f);
	strsize += strlen(str) + 1;
	
    }
    
    
    /* Write the function names to the strtab */
    for (i = 0; i < prog->num_functions; i++)
    {
	char *str = prog->functions[i].name;
	
	fwrite(str, strlen(str) + 1, 1, f);
	strsize += strlen(str) + 1;
    }
    
    /* Write the variable names to the strtab */
    for (i = 0; i < prog->num_variables; i++)
    {
	char *str = prog->variable_names[i].name;
	
	fwrite(str, strlen(str) + 1, 1, f);
	strsize += strlen(str) + 1;
    }
    
    if (!strsize)
    {
	fwrite((char *)&strsize, sizeof(strsize), 1, f);
	strsize = sizeof(strsize);
    }
    /* Write the size of the extra stringtable */
    fseek(f, strsize_tell, SEEK_SET);
    fwrite((char *)&strsize, sizeof(strsize), 1, f);
    
    /* Write a new program header with offsets instead of
       pointers for the sections */
    pgm = *prog;
    calculate_offsets(&pgm, prog);
    fwrite((char *)&pgm, sizeof(pgm), 1, f);

    swap_lineno(prog);
    
    fclose(f);
    if (comp_flag)
	fprintf(stderr, "BIN %s saved!\n",
		binname);
    free(binname);
    return pgm.mod_time;
}

int load_binary(FILE *f, char *lname)
{
    extern int current_id_number;
    struct program pgm;
    struct program *progp;
    extern struct program *prog;
    extern char *inherit_file;
    extern driver_mtime;
    int comp_driver_mtime;
    extern int total_lines;   /* not used */
    char buf[100];
    char *strtab, *strptr;
    int strsize;
    int i;
    extern int total_prog_block_size, total_program_size,
    total_num_prog_blocks;
    int pgm_seek;
    extern void hash_func (int size, struct function *from,
			   struct function_hash *to);
    if (inherit_file)
    {
	free(inherit_file);
	inherit_file	= (char *)0;
    }

    /* Is the file compatible with the current driver? */
    fread(buf, sizeof(char), strlen(MAGIC), f);
    fread((char *)&comp_driver_mtime, sizeof(comp_driver_mtime), 1, f);
    if (strncmp(buf, MAGIC, strlen(MAGIC)) ||
	comp_driver_mtime != driver_mtime)
    {
	fclose(f);
	if (comp_flag)
	    fprintf(stderr, "BIN Wrong magic for %s!\n",
		    lname);
	
	prog = 0;
	return -1;
    }

    /* Read strtab size and program header */
    fread((char *)&strsize, sizeof(strsize), 1, f);
    if (!strsize)
    {
	prog = 0;
	return -1;
    }

    pgm_seek = ftell(f);
    fread((char *)&pgm, sizeof(pgm), 1 ,f);

    /* Allocate segments */
    progp = (struct program *)xalloc(pgm.total_size);
    pgm.program = xalloc(pgm.exec_size);
    pgm.line_numbers = xalloc(pgm.debug_size);

    /* Read segments */
    fseek(f, pgm_seek, SEEK_SET);
    fread((char *)progp, pgm.total_size, 1, f);
    fread(pgm.program, pgm.exec_size, 1, f);
    fread(pgm.line_numbers, pgm.debug_size, 1, f);
    
    /* Calculate pointers for sections from the offsets in the header */
    restore_sections(progp, &pgm);

    /* Read the extra strtab */
    strptr = strtab = xalloc(strsize);
    fread(strtab, strsize, 1, f);

    /* Done reading from the file! */
    fclose(f);
    
    /* Now comes an check of the ages of all INCLUDED files: */
    if (progp->sizeof_include_files) 
    {
	struct stat stb;
	int age;
	char *t, *s;

	t = progp->include_files;
	while(t < progp->include_files + progp->sizeof_include_files) 
	{
	    age = atoi(t);
	    s = strchr(t, ':');
	    if (!s)
	    {
		if (comp_flag)
		    fprintf(stderr,
			    "BIN Can not separate age & iname %s->%s!\n",
			    lname, t);
		/* Free allocated memory */
		free(pgm.program);
		free((char *)pgm.line_numbers);
		free((char *)progp);
		free(strtab);
		prog = 0;
		return -1;
	    }
	    else
		t = s + 1;
	    if (stat(t, &stb) == -1) 
	    {
		if (comp_flag)
		    fprintf(stderr,
			    "BIN Included file not found (%s->%s)\n",
			    lname, t);
		/* Free allocated memory */
		free(pgm.program);
		free((char *)pgm.line_numbers);
		free((char *)progp);
		free(strtab);
		prog = 0;
		return -1;
	    }
	    if (stb.st_mtime != age)
	    {
		if (comp_flag)
		    fprintf(stderr,"BIN Included file changed (%s->%s)\n",
			    lname, t);
		/* Free allocated memory */
		free(pgm.program);
		free((char *)pgm.line_numbers);
		free((char *)progp);
		free(strtab);
		prog = 0;
		return -1;
	    }
	    t += strlen(t) + 1;
	}
    }

    /* Find the inherited programs */
    for (i = 0; i < progp->num_inherited - 1; i++)
    {
	extern struct object *find_object2(char *);
	struct object *ob;
	int mod_time;
	
	ob = find_object2(strptr);
	if (!ob)
	{
	    inherit_file = string_copy(strptr);
	    for (i--; i >= 0; i--)
		free_string(progp->inherit[i].name);
	    if (comp_flag)
		fprintf(stderr,"BIN Inherited file not loaded (%s->%s)\n",
			lname, inherit_file);
	    /* Free allocated memory */
	    free(pgm.program);
	    free((char *)pgm.line_numbers);
	    free((char *)progp);
	    free(strtab);
	    prog = 0;
	    return 0;
	}
	strptr += strlen(strptr) + 1;
	mod_time = atoi(strptr);
	if (mod_time != ob->prog->mod_time)
	{
	    /* Free allocated memory */
	    for (i--; i >= 0; i--)
		free_string(progp->inherit[i].name);
	    free(pgm.program);
	    free((char *)pgm.line_numbers);
	    free((char *)progp);
	    free(strtab);
	    if (comp_flag)
		fprintf(stderr,"BIN Inherited file changed (%s(%d)->%s(%d))\n",
			lname, mod_time, ob->prog->name, mod_time);
	    prog = 0;
	    return -1;
	}
	strptr += strlen(strptr) + 1;
	progp->inherit[i].prog = ob->prog;
	progp->inherit[i].name = make_shared_string(strptr);
	strptr += strlen(strptr) + 1;
    }

    /* Fix the inherit self */
    /* Check that the name is correct */
    if (strcmp(lname, strptr))
    {
	if (comp_flag)
	    fprintf(stderr,"BIN Wrong file name (%s->%s)\n",
		    lname, strptr);
        /* Free allocated memory */
	for (i--; i >= 0; i--)
	    free_string(progp->inherit[i].name);
	free(pgm.program);
	free((char *)pgm.line_numbers);
	free((char *)progp);
	free(strtab);
	prog = 0;
	return -1;
    }
    progp->name = string_copy(strptr);
    strptr += strlen(strptr) + 1;
    strptr += strlen(strptr) + 1; /* skip mod_time */
    progp->inherit[i].name = make_shared_string(strptr);
    strptr += strlen(strptr) + 1; 
    progp->inherit[i].prog = progp;
    
    
    /* Read the function names */
    for (i = 0; i < progp->num_functions; i++)
    {
	progp->functions[i].name =
	    make_shared_string(strptr);
	strptr += strlen(strptr) + 1; 
#ifdef PROFILE_FUNS
	progp->functions[i].num_calls = 0;
	progp->functions[i].time_spent = 0;
#ifdef SOLARIS
	progp->functions[i].ticks_call = 0;
#else
	progp->functions[i].stime_call = 0;
	progp->functions[i].utime_call = 0;
#endif
#endif
    }

    /* Read the variable name */
    for (i = 0; i < progp->num_variables; i++)
    {
	progp->variable_names[i].name =
	    make_shared_string(strptr);
	strptr += strlen(strptr) + 1;
    }

    free(strtab);

    /* Done! */

    hash_func(progp->num_functions, progp->functions, progp->func_hash);

    progp->ref = 0;
    progp->swap_num = 0;
    progp->id_number = current_id_number++;
    progp->cpu = 0;
    progp->swap_lineno_index = 0;
    progp->load_time = current_time;

    for(i = 0; i < (int)progp->num_inherited; i++)
	reference_prog(progp->inherit[i].prog, "inheritance");
    
    total_prog_block_size += progp->total_size;
    total_program_size += progp->exec_size;
    
    total_num_prog_blocks += 1;
    register_program(progp);
    progp->load_time = current_time;
    swap_lineno(progp);
    
    prog = progp;
    return 0;
}
#endif