#import "DebuggerController_Private.h"
#import <Foundation/Foundation.h>

/* for the kill() call in -interrupt */
#import <sys/types.h>
#import <sys/signal.h>

#import "debug.h"

static DebuggerController	*debuggerController;

void
fork_and_start_debugger_controller(GdbManager *gm)
{
    
    (void)[NSRunLoop class];	// workaround: force RunLoop to go thru +initialize
	                        // and setup support for multithreading
			    
    debuggerController = [[DebuggerController alloc] init];

    [NSThread detachNewThreadSelector: @selector(startController:)
                             toTarget: debuggerController
                           withObject: gm];
}



@implementation DebuggerController

- init
{
    [super init];

    displayProvider = nil;
    stdinPFD = nil;
        
    return self;
}

#if 1
- (id) displayProvider 
{
    return displayProvider;
}
#endif

//
// This is the first method called in the new thread.
// Advertise ourself using the given name for the connection.
// A Gui system, like ProjectBuilder, will be looking for
// us at that name.
//
// Also, create a connection for our local (i.e. same process)
// connection to the GuiGdbManager.
//
// Then go into the runloop and wait for the Gui system to
// give us the DisplayProvider object.
//
- (void) startController: gm
{
    NSString			*localName;
    NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];
    NSDistantObject		*dP;
 
    gdbManager = (GdbManager <GuiGdbManagerInternal,
                  	      GuiGdbManagerExecLock> *)gm;

    NSLog(@"Debugger Controller: looking for connection named %@ on host %@", 
    	  [gdbManager displayProviderConnectionName],
    	  [gdbManager displayProviderHostName]);
	
    dP = [NSConnection
           rootProxyForConnectionWithRegisteredName:[gdbManager displayProviderConnectionName]
                                               host:[gdbManager displayProviderHostName]];

    if ([dP conformsToProtocol:@protocol(GuiDisplayProvider2)])
      [dP setProtocolForProxy:@protocol(GuiDisplayProvider2)];
    else
      [dP setProtocolForProxy:@protocol(GuiDisplayProvider)];
    displayProviderConnection = [dP connectionForProxy];
    displayProvider = (id<GuiDisplayProvider>)[dP retain];
    [displayProvider setDebuggerController:self];
    
    localName = [[NSProcessInfo processInfo] globallyUniqueString];
    [gdbManager setDebuggerControllerConnectionName:localName];

    gdbConnection = [[NSConnection alloc] init]; 
    [gdbConnection setRootObject: self];
    [gdbConnection registerName: localName];
    
    DEBUG_PRINT("Dbg Controller: start: exported connections\n");

    [[NSNotificationCenter defaultCenter]
       addObserver: self
          selector: @selector(handleConnectionDeath:)
              name: NSConnectionDidDieNotification
            object: displayProviderConnection];

    [[NSNotificationCenter defaultCenter]
       addObserver: self
          selector: @selector(handleConnectionDeath:)
              name: NSConnectionDidDieNotification
            object: gdbConnection];

    // This has the side-effect of unblocking the gdbManager.
    [gdbManager setDisplayProvider: dP];
    
    [pool release];

    [[NSRunLoop currentRunLoop] run];
}


//
// Sent by the gdb thread; tells us to look for
// activity on stdin.
//
- (oneway void) gdbReadyForInput
{
    if (stdinPFD == nil) {
        stdinPFD = [[NSPosixFileDescriptor alloc]
		    initWithFileDescriptor:fileno(stdin)];
        [stdinPFD monitorFileActivity: NSPosixReadableActivity
                             delegate: self];
    }
    else
    	[stdinPFD monitorFileActivity: NSPosixReadableActivity];
}

// Called from (remote) client of us, e.g. ProjectBuilder

// execute cmd in gdb
- (oneway void) executeCmd:(NSString *)c
                   withTty:(BOOL)t
            withAnnotation:(BOOL)a
{
    GdbCmd	*cmd;

    // send cmd to Gdb 
    /* copy of command c should not be needed; 
       work around for DO problems */
    cmd = [[GdbCmd alloc] initWithCmd:[c copy]
                               ofType:GDB_CMD_EXEC
                               useTty:t
                        useAnnotation:a];
    [gdbManager enqueueCmd:cmd];
}

- (oneway void)executeCmd:(NSString*)c
  withTty:(BOOL)tty
  withAnnotation:(BOOL)annotation
  catchOutput:(BOOL)catch
{
    GdbCmd	*cmd;

    // send cmd to Gdb 
    /* copy of command c should not be needed; 
       work around for DO problems */
    cmd = [[GdbCmd alloc] initWithCmd:[c copy]
                               ofType:GDB_CMD_EXEC
                               useTty:tty
                        useAnnotation:annotation
			catchOutput:catch];
    [gdbManager enqueueCmd:cmd];
}

/* 
   Interrupt; right now, this only interrupts the inferior.
   Might want to extend this to interrupt gdb thread someday.
 */
- (oneway void) interrupt
{
    extern int inferior_pid;

    if (inferior_pid > 0)
	kill(inferior_pid, SIGINT);	
}

- (void) handleConnectionDeath: (NSNotification *) notification
{
    if ([notification object] == displayProviderConnection) {
        // client has gone away
    }
    else {
	// gdb connection
    }
    // FIXME: do a better job of cleanning up
    exit(22);
}
@end

// Called when input is pending on stdin

@implementation DebuggerController (NSPosixFileDescriptorDelegateMethods)

- (void) posixFileDescriptor:(NSPosixFileDescriptor *)pfd
             currentActivity:(NSPosixFileActivity) activity
{
    GdbCmd	*cmd;
    
    // turn off monitoring until GDB tells to turn it back on
    [pfd monitorFileActivity: NSPosixNoActivity];

    // send cmd to Gdb (which will wake it up); gdb will read stdin 
    cmd = [[GdbCmd alloc] initWithCmd:nil ofType:GDB_CMD_INPUT_PENDING];
    [gdbManager enqueueCmd:cmd];
}
@end
