#import <Foundation/Foundation.h>
#import "GuiGdbManager.h"
#import "GuiDisplayProvider_Protocol.h"
#import "DisplayMisc.h"
#import "NSDistantObjectExtras.h"
#import "gdb.h"
#import <sys/ioctl.h>

#define DEBUG_MAIN;
#import "debug.h"

#ifdef DEBUG
FILE	*debug_stream;
#endif

static void gui_display_window_hook(FILE *instream, char **prompt);


@implementation GdbCmd
- initWithCmd:(NSString*)c
       ofType:(Gdb_cmd_type)t
{
    return [self initWithCmd:c
                      ofType:t
                      useTty:NO
               useAnnotation:NO];
}

- initWithCmd:(NSString*)c
       ofType:(Gdb_cmd_type)t
       useTty:(BOOL) tty
useAnnotation:(BOOL)anno
{
    return [self initWithCmd:c
		      ofType:t
		      useTty:tty
	       useAnnotation:anno
		 catchOutput:NO];
}

- initWithCmd:(NSString*)c
       ofType:(Gdb_cmd_type)t
       useTty:(BOOL) tty
useAnnotation:(BOOL)annotation
  catchOutput:(BOOL)catch
{
    [super init];
    cmd = c;
    if (cmd != nil)
        [cmd retain];
    type = t;
    useTty = tty;
    useAnnotation = annotation;
    catchOutput = catch;
    next = nil;
    return self;
}

- (void) dealloc
{
    [cmd release];
}


- (Gdb_cmd_type)getType
{
    return type;
}

- (NSString *)getCmd
{
    return cmd;
}
- (BOOL) useTty
{
    return useTty;
}

- (BOOL) useAnnotation
{
    return useAnnotation;
}

- (BOOL)catchOutput
{
    return catchOutput;
}

@end

//
// To get started, the main thread sticks info into GuiGdbManager
// which is then read by the Debugger Controller thead. GuiGdbManager,
// in the main thread, waits until it is given a display provider
// before it proceeds;
// then it goes into command processing.
//

@implementation GuiGdbManager 

- init
{
    [super init];
    
    controller = nil;

    cmdQLock = [[NSConditionLock alloc] init];
    cmdQHead = nil;

    execLock = [[NSConditionLock alloc] init];
    displayProviderLock = [[NSConditionLock alloc] init];

    DEBUG_INIT;
#ifdef DEBUG
    debug_stream = stderr;
#endif
    
    return self;
}


- (void) setDebuggerControllerConnectionName:(NSString *)n
{
    controllerConnectionName = n;
    [controllerConnectionName retain];
}


#define PROVIDER_NOT_AVAILABLE 0
#define PROVIDER_AVAILABLE 1

// called from controller thread; tells gdb thread a provider is available

- (void) setDisplayProvider:(id)dP
{
    [displayProviderLock lock];
    [super setDisplayProvider: dP];
    [displayProviderLock unlockWithCondition: PROVIDER_AVAILABLE];
    DEBUG_PRINT("gdbManager: just set the provider  and unlocked\n");
}

//
// called from gdb thread.
//
    
- (int) establishConnection
{
    NSDistantObject		*c;
    int				i;
    
    fork_and_start_debugger_controller(self);

    
    DEBUG_PRINT("gdbManager: waiting for the PROVIDER to become available\n");
    
    [displayProviderLock lockWhenCondition: PROVIDER_AVAILABLE];
    [displayProviderLock unlock];

#if 0
    // Dec 1995: this workaround no longer needed
    // workaround: the DO info for the display provider is valid
    // for the other (controller) thread; we have to make correct
    // for our thread.
    displayProvider = [(NSDistantObject *)displayProvider
        		  proxyWithProperConnection];
#endif
	
    /* Now, we, the gdb thread, and the debugger controller will only
       communicate via DO or the command queue. Most of our data
       is now Read-Only so we don't use locks to access it.
     */
	
    for (i = 0; i < 10; i++) {
        c = [NSConnection
             rootProxyForConnectionWithRegisteredName: controllerConnectionName
                                                 host: nil];
        if (c)
	    break;
    }
    if (c == nil) {
	NSLog(@"GuiGdbManager: could not get connection to debugger controller");
	return 0;
    }

    [c setProtocolForProxy:@protocol(DebuggerControllerInternal)];
    controller = (id<DebuggerControllerInternal>) c;
    [controller retain];
    
#if 1
    displayProvider = [[controller displayProvider] retain];
#endif

    DEBUG_PRINT("gdbManager: waitForClient: got client & controller DO connection\n");
    return 1;
}
- (void) tellControllerWeAreReadyForInput
{
    [controller gdbReadyForInput];
}


- (WindowHookFunction) windowHookFunction
{
    return gui_display_window_hook;
}



//
// Implementation of the GdbManagerExecLock protocol
//  
- (void) lockExecLock
{
    [execLock lock];
}

- (void) unlockExecLock
{
    [execLock unlock];
}



#define NO_CMD		0
#define CMD_AVAILABLE	1


- (void)enqueueCmd:(GdbCmd*)newCmd
{
    GdbCmd	*cmd;

    [cmdQLock lock];
    cmd = cmdQHead;
    if (cmd == nil) {
        cmdQHead = newCmd;
    }
    else {
        // we expect the cmd queue will be short;
        // so just find the end each time
        while (cmd->next != nil) {
            cmd = cmd->next;
        }
        cmd->next = newCmd;
    }
    [cmdQLock unlockWithCondition: CMD_AVAILABLE];
}

- (GdbCmd*) dequeueCmd
{
    GdbCmd	*cmd;

    DEBUG_PRINT("gdbManager dequeCmd: waiting for command\n");
    [cmdQLock lockWhenCondition: CMD_AVAILABLE];
    cmd = cmdQHead;
    cmdQHead = cmd->next;
    if (cmdQHead)
        [cmdQLock unlockWithCondition: CMD_AVAILABLE];
    else
        [cmdQLock unlockWithCondition: NO_CMD];
    DEBUG_PRINT("gdbManager dequeCmd: got command\n");
    return cmd;
}

// execute a gdb command; assumes we have the exec lock


static void
noop (foo)
     int foo;
{
}

static int useTty = 0;
static int catchOutput = 0;

NSMutableString* stdoutOutput = nil;
NSMutableString* stderrOutput = nil;
NSMutableString* annotationOutput = nil;

/* This variable is set to annotationOutput in -executeCommand: if the
   annotation is used. Otherwise it is set to nil. */
NSMutableString* annotationString = nil;

static void my_fputs_unfiltered_hook(const char *linebuffer, FILE *stream)
{
  id string;

  if (annotationString)
    string = annotationString;
  else if (stream == stdout)
    string = stdoutOutput;
  else if (stream == stderr)
    string = stderrOutput;
  else
    string = nil;

  [string appendString:[NSString stringWithCString:linebuffer]];
}

static void
execute_command_for_PB(char *command)
{
    struct cleanup *old_chain = make_cleanup (noop, 0);

    execute_command(command, useTty);
    bpstat_do_actions (&stop_bpstat);
    do_cleanups (old_chain);
}

+ (void)initialize
{
  stdoutOutput = [NSMutableString new];
  stderrOutput = [NSMutableString new];
  annotationOutput = [NSMutableString new];
}

-(void)executeCommand: (GdbCmd *)command
{
    int 	wasError;
    const char	*cmd;
    int		old_verbose;
    extern int	info_verbose;
    extern	annotation_level;
    void (*old_fputs_hook)(const char*, FILE*) = fputs_unfiltered_hook;

    catchOutput = (int)[command catchOutput];
    if (catchOutput) {
      fputs_unfiltered_hook = my_fputs_unfiltered_hook;
      [stdoutOutput setString:@""];
      [stderrOutput setString:@""];
      [annotationOutput setString:@""];
      annotationString = annotationOutput;
    }
    else
      annotationString = nil;

    cmd = [[command getCmd] cString];

    if ([command useAnnotation]) {
        annotation_level = 2;
        printf_unfiltered("\n\032\032annotation-begin\n");
    }

    useTty = (int)[command useTty];
    old_verbose = info_verbose;
    info_verbose = 0;
    
    catch_errors_was_error(execute_command_for_PB, cmd, &wasError, RETURN_MASK_ALL);
    info_verbose = old_verbose;
    
    if ([command useAnnotation]) {
        annotation_level = 0;
        printf_unfiltered("\n\032\032annotation-end\n");
    }
    
    if (catchOutput) {
      id client
	  = [self displayProviderForProtocol:@protocol(GuiDisplayProvider2)];

      [client outputFromGDB:stdoutOutput type:GDB_OUTPUT_STDOUT];
      [client outputFromGDB:stderrOutput type:GDB_OUTPUT_STDERR];
      [client outputFromGDB:annotationOutput type:GDB_OUTPUT_ANNOTATION];
      fputs_unfiltered_hook = old_fputs_hook;
    }
    
    if (wasError) {
       // fprintf(stderr, "Error occured processing DO command.\n");
    }
    fflush(stdout);
    fflush(stderr);
}
@end


static GuiGdbManager	*guiGdbManager;

GdbManager * make_gui_gdb_manager()
{
     return (guiGdbManager = [[GuiGdbManager alloc] init]);       
}

static void
gui_display_window_hook(FILE *instream,
                        char **prompt)
{
    static int	lock_is_pending = 0; // true if we have the lock
    GdbCmd		*cmd;
    extern int 	noprompt;
    
    DEBUG_PRINT("in WindowHook\n");

    // we will block waiting for input. So first we 
    // send out prompt (if is OK to do so); 
    if (noprompt == 0)
	fputs(*prompt, stdout);

    *prompt = NULL; // disable outputing of prompt by main cmd loop
    
    fflush(stdout);

    {
	int	bytesAvail;
	int	retVal;
	
	/* in our configuration, we know stdin is a pipe;
	   FIONREAD may not work correctly on a pty.
	 */
	retVal = ioctl(0, FIONREAD, &bytesAvail);
	if (retVal < 0) {
	    NSLog(@"gdb: GuiGdbManager: FIONREAD ioctl fails.");
	    return;
	}
	if (bytesAvail > 0)
	    return;	/* keep processing stdin until it is empty */
    }
    
    [guiGdbManager tellControllerWeAreReadyForInput];

    if (lock_is_pending) {
        [guiGdbManager unlockExecLock];
        lock_is_pending = 0;
    }

    // process cmds from server; will block waiting for cmds
    while (cmd = [guiGdbManager dequeueCmd]) {

        if ((++pool_num_times) >= POOL_RELEASE_MAX_TIMES) {
            [pool release];
            pool_num_times = 0;
            pool = [[NSAutoreleasePool alloc] init];
        }

        switch ([cmd getType]) {
          case GDB_CMD_EXEC:
            [guiGdbManager lockExecLock];
            [guiGdbManager executeCommand:cmd];
#ifdef DEBUG
            {
                char		buff[512];

                sprintf(buff, "got command: %s\n", [[cmd getCmd] cString]);
                DEBUG_PRINT(buff);
            }
#endif
            [guiGdbManager unlockExecLock];
            [cmd release];
            break;

          case GDB_CMD_INPUT_PENDING:
            [guiGdbManager lockExecLock];
            [cmd release];
            lock_is_pending = 1;
            return;	// return to gdb tty-base IO & command processing

          default:
            //FIXME: report an error here
            break;
        } // end switch
        
    } // end while
}
