#import "gdb.h"
#import <fcntl.h>
#import "loadSymbols.h"
#import "objfiles.h"
#import <sys/stat.h>

typedef struct _ManagerChain {
    SegmentManager *manager;
    CORE_ADDR jmpTableStart;
    CORE_ADDR jmpTableEnd;
    struct _ManagerChain *next;
} ManagerChain;

static ManagerChain *execManagers = NULL;
SegmentManager *execManager = NULL;
extern struct target_ops exec_ops;

int
isShlibJump(CORE_ADDR address)
{
    ManagerChain *mChain;
    for (mChain = execManagers; mChain; mChain = mChain->next) {
	if ((mChain->jmpTableStart <= address)
	                          && (address < mChain->jmpTableEnd))
           return NO;
    }
    return YES;
}

static void initExecManager(ManagerChain *mChain)
{
    const struct section *firstSection;
    SegmentManager *manager = mChain->manager;

    if ([manager isShlib] && (firstSection = [manager firstSection])) {
	mChain->jmpTableStart = firstSection->addr;
	mChain->jmpTableEnd = jmpTableEnd(((void *)[manager getMachHeader])
	                                  + firstSection->offset,
					  firstSection);
    } else {
	mChain->jmpTableStart = 0xFFFFFFFF;
	mChain->jmpTableEnd = 0x0;
    }
    readObjC(manager);
}

void addExecManager(SegmentManager *manager)
{
    ManagerChain *mChain;
    
    if (execManagers) {
	for (mChain = execManagers; mChain->next; mChain = mChain->next);
	mChain->next = malloc(sizeof(*mChain));
	mChain = mChain->next;
    } else {
	execManagers = mChain = malloc(sizeof(*mChain));
	execManager = manager;
    }
    mChain->manager = manager;
    mChain->next = NULL;
    initExecManager(mChain);
}

void removeExecManager(SegmentManager *manager)
{
    removeObjC(manager);
    if (execManagers) {
	ManagerChain *mChain;
	for (mChain = execManagers;
	     mChain && (mChain->next->manager != manager);
	     mChain = mChain->next);
	if (mChain) {
	    ManagerChain *oldChain = mChain->next;
	    mChain->next = oldChain->next;
	    free(oldChain);
	}
    }
}

void execFileAddShlibs(SegmentManager *manager)
{
    struct mach_header *header = [manager getMachHeader];
    int nCmds;
    struct load_command *loadCmd;
    char *name, *originalName;
    SegmentManager *shlibManager;
    if ([manager isExecutable]) {
        for (nCmds = header->ncmds,
	     loadCmd = (struct load_command *)(header + 1);
	     nCmds;
	     nCmds--, ((void *)loadCmd) += loadCmd->cmdsize) {
	    if (loadCmd->cmd == LC_LOADFVMLIB) {
		name = (char *)getShlibName((struct fvmlib_command *)loadCmd,
		                            &originalName);
                if (originalName)
		    printf("Aliasing %s to %s...\n", originalName, name);
		shlibManager = [SegmentManager newShlib: name cpuType: cpuType()];
		if (shlibManager)
		    addExecManager(shlibManager);
		else
		    error("Could not open `%s' as a shlib.", name);
	    }
	}
    }
}

SegmentManager *execFileAdd(char *name, int desc)
{
    SegmentManager *manager = [SegmentManager newExecutable: name
				                 skipShlibs: YES
				                   withDesc: desc
						    cpuType: cpuType()];
    ManagerChain *mChain;
    
    if (!manager)
	error("Could not open `%s' as an executable file.", name);
    addExecManager(manager);    
    execFileAddShlibs(manager);
    validate_files ();
    return manager;
}

/*  Process the first arg in ARGS as the new exec file.

    Note that we have to explicitly ignore additional args, since we can
    be called from file_command(), which also calls symbol_file_command()
    which can take multiple args. */

void
exec_file_command(char *args, int from_tty)
{
    char **argv;
    char *filename;
    
    target_preopen (from_tty);
    
    /* Remove any previous exec file.  */
    unpush_target (&exec_ops);
    
    /* Now open and digest the file the user requested, if any.  */
    
    if (args) {
	char *scratch_pathname;
	int scratch_chan;
	
	/* Scan through the args and pick up the first non option arg
	    as the filename. */
    
	if ((argv = buildargv(args)) == NULL) {
	    nomem(0);
	}
	make_cleanup(freeargv, (char *)argv);
    
	for (; (*argv != NULL) && (**argv == '-'); argv++) {;}
	if (*argv == NULL) {
	    error ("no exec file name was specified");
	}
    
	filename = tilde_expand(*argv);
	make_cleanup(free, filename);
	
	scratch_chan = openp(getenv("PATH"), 1, filename, O_RDONLY, 0,
	                     &scratch_pathname);
	if (scratch_chan < 0)
	    perror_with_name(filename);
	    
	execFileAdd(scratch_pathname, scratch_chan);
	    
	push_target (&exec_ops);
    }
    else if (from_tty)
	printf ("No exec file now.\n");
}

static void
exec_close(int quitting)
{
    if (execManager)
	execManager = [execManager free];
}

static int
exec_xfer_memory (CORE_ADDR memaddr, char *myaddr, int len, int write,
                  struct target_ops *target)
{
    ManagerChain *mChain;
    int bytesRead = 0;
    
    if (write)
	error("Can't write to executable files.");
    for (mChain = execManagers; mChain && !bytesRead; mChain = mChain->next) {
	bytesRead = [mChain->manager getDataAt: (void *)memaddr
	                                   for: len into: myaddr];
    }
    return bytesRead;
}

static void
NeXT_add_file(char *arg_string, int from_tty)
{
    char *name;
    CORE_ADDR text_addr = 0;
    
    dont_repeat ();
    
    if (arg_string == 0)
	error ("add-symbol-file takes a file name and an optional address");
    
    arg_string = tilde_expand (arg_string);
    make_cleanup (free, arg_string);
    
    for(; *arg_string == ' '; arg_string++ );
    name = arg_string;
    
    for(; *arg_string && *arg_string != ' ' ; arg_string++ );
    
    if (*arg_string) {
	LoadInfo loadInfo;
	char *theNames[] = {name, NULL};
	
        *arg_string++ = '\0';
	loadInfo.headerAddr
	= (struct mach_header *)parse_and_eval_address(arg_string);
	loadSymbolsForInfo(&loadInfo, theNames);
    } else {
	SegmentManager *manager = execFileAdd(name, -1);
	symfile_add(manager, 0, 0, 0, 0, 0);
    }
}

/* Return the name of the executable file as a string.
   ERR nonzero means get error if there is none specified;
   otherwise return 0 in that case.  */

char *
get_exec_file(int err)
{
    if (execManager)
	return [execManager executableName];
    else if (err)
	error("No executable file specified.\n"
	      "Use the \"file\" or \"exec-file\" command.");
    else
	return NULL;
}

void
file_command (arg, from_tty)
     char *arg;
     int from_tty;
{
  /* FIXME, if we lose on reading the symbol file, we should revert
     the exec file, but that's rough.  */
  exec_file_command (arg, from_tty);
  symbol_file_command (arg, from_tty);
}

void reread_exec()
{
    struct objfile *obj, *foundObj = NULL;
    struct stat buf;
    int res;
    ManagerChain *mChain;
    if (execManagers) {
	for (mChain = execManagers; mChain; mChain = mChain->next) {
	    res = stat([mChain->manager executableName], &buf);
	    if (res == 0) {
		if (buf.st_mtime != [mChain->manager mtime]) {
		    printf("'%s' has changed, rereading symbols.\n", 
			    [mChain->manager executableName]);
		    removeObjC(mChain->manager);
		    [mChain->manager invalidate];
		    [mChain->manager getImages];
		    [mChain->manager readInAllRelocs];		
		    initExecManager(mChain);
		    for (obj = object_files; obj && !foundObj; obj = obj->next) {
			if (obj->manager == mChain->manager)
			    foundObj = obj;
		    }
		    if (foundObj)
			free_objfile(foundObj);
		    symfile_add(mChain->manager, 0, 0, 0, 0, 0);
		}
	    }
	}
    }
}   

struct target_ops exec_ops = {
    "exec",			/* to_shortname */
    "Local exec file",		/* to_longname */
    "Use an executable file as a target.\n"
    "Specify the filename of the executable file.",	/* to_doc */
    exec_file_command,		/* to_open */
    exec_close,			/* to_close */
    find_default_attach,	/* to_attach */
    0, 				/* to_detach */
    0,				/* to_resume */
    0,				/* to_wait */
    0,				/* to_fetch_registers */
    0,				/* to_store_registers */
    0,				/* to_prepare_to_store */
    exec_xfer_memory,		/* to_xfer_memory */
    0,				/* to_files_info */
    0,				/* to_insert_breakpoint */
    0,				/* to_remove_breakpoint */
    0,				/* to_terminal_init */
    0, 				/* to_terminal_inferior */
    0,				/* to_terminal_ours_for_output */
    0,				/* to_terminal_ours */
    0,				/* to_terminal_info */
    0,				/* to_kill */
    0,				/* to_load */
    0,				/* to_lookup_symbol */
    find_default_create_inferior,	/* to_create_inferior */
    0,				/* to_mourn_inferior */
    0,				/* to_can_run */
    0, 				/* to_notice_signals */
    file_stratum,		/* to_stratum */
    0,				/* to_next */
    0,				/* to_has_all_memory */
    1,				/* to_has_memory */
    0,				/* to_has_stack */
    0,				/* to_has_registers */
    0,				/* to_has_execution */
    0,				/* sections */
    0,				/* sections_end */
    OPS_MAGIC			/* to_magic */
};

void
_initialize_nextexec ()
{
  add_com ("add-file", class_files, NeXT_add_file,
   "Load the symbols from FILE, assuming FILE has been dynamically loaded.\n"
   "If a second argument is provided it indicates the starting address of\n"
   "the file's text.\n"
   "Otherwise the file is assumed to be the debug output file as produced by\n"
   "rld_load or objc_loadModules.\n");
  add_com ("file", class_files, file_command,
	   "Use FILE as program to be debugged.\n\
It is read for its symbols, for getting the contents of pure memory,\n\
and it is the program executed when you use the `run' command.\n\
If FILE cannot be found as specified, your execution directory path\n\
($PATH) is searched for a command of that name.\n\
No arg means to have no executable file and no symbols.");

  add_com ("exec-file", class_files, exec_file_command,
	   "Use FILE as program for getting contents of pure memory.\n\
If FILE cannot be found as specified, your execution directory path\n\
is searched for a command of that name.\n\
No arg means have no executable file.");

}
