/* GdbDisplayController.m created by ovidiu on Wed 19-Mar-1997 */

#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSBrowser.h>
#import "GdbDisplayController.h"
#import "../Document.h"
#import "DebuggerController_Protocol.h"
#import "Stack.h"
#import "Frame.h"
#import "Variable.h"
#import "GdbBrowserCell.h"
#import "FrameBrowserCell.h"

/* Private class used to store an object and a selector */
@interface ObjectSelector : NSObject
{
  id object;
  SEL selector;
}

+ (ObjectSelector*)newWithObject:(id)anObject selector:(SEL)aSelector;
- (void)invokeWithArg:(id)gdbOutput type:(GdbOutputType)outputType;

@end

@implementation ObjectSelector

+ (ObjectSelector*)newWithObject:(id)anObject selector:(SEL)aSelector
{
  ObjectSelector* obj = [[self new] autorelease];
  obj->object = [anObject retain];
  obj->selector = aSelector;
  return obj;
}

- (void)dealloc
{
  [object release];
  [super dealloc];
}

- (void)invokeWithArg:(id)gdbOutput type:(GdbOutputType)outputType
{
  IMP imp = [object methodForSelector:selector];
  
//  NSLog (@"%@ output = %@", NSStringFromSelector (_cmd), gdbOutput);
  (*((void (*)(id, SEL, id, GdbOutputType))imp))
    (object, selector, gdbOutput, outputType);
}

@end


@implementation GdbDisplayController

static NSString* stackBrowserFrameName = @"Stack Browser Window";
static  NSString* selectionMatrixFrameName = @"Gdb Controller Window";

- init
{
  self = [super init];
  stack = [Stack new];
  [stack setGdbController:self];
  objectsToBeNotified = [NSMutableArray new];
  currentFrame = -1;

  return self;
}

- (void)awakeFromNib
{
  NSWindow* stackBrowserWindow = [stackBrowser window];
  NSWindow* selectionMatrixWindow = [selectionMatrix window];
  NSString* frameString;

  NSLog (@"awakeFromNib");
  [stackBrowserWindow setFrameAutosaveName:stackBrowserFrameName];
  [selectionMatrixWindow setFrameAutosaveName:selectionMatrixFrameName];
#if 0
  frameString = [stackBrowserWindow stringWithSavedFrame];
  if (frameString)
    [[stackBrowser window] setFrameFromString:frameString];
  NSLog (@"frame1 = %@", frameString);

  frameString = [selectionMatrixWindow stringWithSavedFrame];
  if (frameString)
    [[selectionMatrix window] setFrameFromString:frameString];
  NSLog (@"frame2 = %@/n", frameString);
#endif
}

- (void)dealloc
{
  [stack release];
  [objectsToBeNotified release];
  [super dealloc];
}

/* GuiDisplayProvider2 protocol methods */
- (oneway void) setDebuggerController:(id)dC
{
  gdbManager = [dC retain];
  [gdbManager executeCmd:@"set print repeats 0"
                 withTty:YES
          withAnnotation:NO
             catchOutput:NO];
  [gdbManager executeCmd:@"set print elements 0"
                 withTty:YES
          withAnnotation:NO
             catchOutput:NO];
}

- (oneway void) breakpointChanged:(int)bpNum
                         newState:(BreakpointState)state
                           inFile:(NSString *)fileName
                           atLine:(int)lineNumber
{
//  NSLog (@"%@: bkpNum %d, file %@, line %d",
//         NSStringFromSelector(_cmd), bpNum, fileName, lineNumber);
}

- (oneway void) frameChanged:(int)newFrame
{
//  NSLog (@"frameChanged: newFrame %d", newFrame);
  [stackBrowser selectRow:[self browserCellForFrame:newFrame] inColumn:0];
  currentFrame = newFrame;
}

- (void)executeGDBCommand:(NSString*)command
                 annotate:(BOOL)flag
             notifyObject:object
                 selector:(SEL)selector
{
  [gdbManager executeCmd:command
                 withTty:YES
          withAnnotation:flag
             catchOutput:YES];

  /* Disable all the buttons until we receive the backtrace result */
  [self disableAllButtons];

  /* Queue the object and selector. The GDB commands are executed in FIFO
    order, when GDB sends outputFromGDB:type: the first object in array is
    dequeued. */
  [objectsToBeNotified addObject:
    [ObjectSelector newWithObject:object selector:selector]];
}

- (oneway void) stackChanged:(int)newSize limitReached:(BOOL)maxedOut
{
//  NSLog (@"%@", NSStringFromSelector(_cmd));
  [self executeGDBCommand:@"backtrace"
                 annotate:YES
             notifyObject:stack
                 selector:@selector(createStackFromGdbBacktrace:type:)];
  [stackBrowser selectRow:[[stackBrowser matrixInColumn:0] numberOfRows] - 1
                 inColumn:0];
}

- (oneway void) lineChangedForThread:(int) t
                              inFile:(NSString *)f
                         atStartLine:(int)sl
                           toEndLine:(int)el
{
//  NSLog (@"lineChangedForThread: file = %@, line = %d", f, sl);
  [self selectInFile:f startLine:sl endLine:el];
  if (currentFrame >= 0) {
    [[[stack frames] objectAtIndex:currentFrame]
        setFilename:f startLine:sl endLine:el];
    [stackBrowser selectRow:[self browserCellForFrame:currentFrame]
                   inColumn:0];
  }
}

- (oneway void) inferiorStateChanged:(DebuggerState) newState
{
#if 0
  NSString* states[] = {
    @"DBG_STATE_NOT_ACTIVE",
    @"DBG_STATE_ACTIVE",
    @"DBG_STATE_INFERIOR_LOADED",
    @"DBG_STATE_INFERIOR_EXITED",
    @"DBG_STATE_INFERIOR_LOGICALLY_RUNNING",
    @"DBG_STATE_INFERIOR_RUNNING",
    @"DBG_STATE_INFERIOR_STOPPED"
  };

  NSLog (@"inferiorStateChanged: %@", states[newState]);
#endif

  debuggerState = newState;
  [self setButtonsConformingToState:newState];
}

- (int)query:(NSString*)queryString
{
//  NSLog (@"%@", NSStringFromSelector (_cmd));
  return 0;
}

- (oneway void) outputFromGDB:(NSString*)output
                         type:(GdbOutputType)outputType
{
  id invoke = [objectsToBeNotified objectAtIndex:0];

  [invoke invokeWithArg:output type:outputType];

  /* Remove the invocation object when all the output has been received.
    The message with GDB_OUTPUT_ANNOTATION as argument must be the last
    message sent by GDB. */
  if (outputType == GDB_OUTPUT_ANNOTATION)
    [objectsToBeNotified removeObjectAtIndex:0];

  /* If all the commands sent to GDB have been executed display the state */
  if (![objectsToBeNotified count])
    [self setButtonsConformingToState:debuggerState];
}

/* Our methods */
- (void)display:sender
{
  [[selectionMatrix window] makeKeyAndOrderFront:nil];
}

- (void)close
{
  [[selectionMatrix window] orderOut:nil];
  [[stackBrowser window] orderOut:nil];
}

- (void)setButtonsConformingToState:(DebuggerState)state
{
  switch (state) {
    case DBG_STATE_NOT_ACTIVE:
    case DBG_STATE_ACTIVE:
      [runButton setEnabled:NO];
      [stopButton setEnabled:NO];
      [quitButton setEnabled:NO];
      [execCmdMatrix setEnabled:NO];
      [lineMatrix setEnabled:NO];
      [selectionMatrix setEnabled:NO];
      break;

    case DBG_STATE_INFERIOR_LOADED:
    case DBG_STATE_INFERIOR_EXITED:
      [runButton setEnabled:YES];
      [stopButton setEnabled:NO];
      [quitButton setEnabled:YES];
      [execCmdMatrix setEnabled:NO];
      [lineMatrix setEnabled:YES];
      [selectionMatrix setEnabled:NO];
      break;

    case DBG_STATE_INFERIOR_STOPPED:
    case DBG_STATE_INFERIOR_LOGICALLY_RUNNING:
      [runButton setEnabled:YES];
      [stopButton setEnabled:NO];
      [quitButton setEnabled:YES];
      [execCmdMatrix setEnabled:YES];
      [lineMatrix setEnabled:YES];
      [selectionMatrix setEnabled:YES];
      break;

    case DBG_STATE_INFERIOR_RUNNING:
      [runButton setEnabled:NO];
      [stopButton setEnabled:YES];
      [quitButton setEnabled:NO];
      [execCmdMatrix setEnabled:NO];
      [lineMatrix setEnabled:NO];
      [selectionMatrix setEnabled:NO];
      break;
  }
}

- (void)disableAllButtons
{
  [self setButtonsConformingToState:DBG_STATE_NOT_ACTIVE];
}

/* Outlet methods */
- (void)run:sender
{
  int start = YES;

  if ((debuggerState == DBG_STATE_INFERIOR_STOPPED
      || debuggerState == DBG_STATE_INFERIOR_LOGICALLY_RUNNING)
      && !NSRunAlertPanel (@"Warning", @"Program is running. Do you really "
                            @"want to restart it?", @"Yes", @"No", NULL))
        start = NO;

  if (start) {
    [gdbManager executeCmd:@"set confirm off" withTty:YES withAnnotation:NO];
    [gdbManager executeCmd:@"run" withTty:YES withAnnotation:NO];
    [gdbManager executeCmd:@"set confirm on" withTty:YES withAnnotation:NO];
  }
}

- (void)interrupt:sender
{
  [gdbManager interrupt];
}

- (void)quit:sender
{
  int quit = YES;

  if ((debuggerState == DBG_STATE_INFERIOR_STOPPED
      || debuggerState == DBG_STATE_INFERIOR_LOGICALLY_RUNNING)
      && !NSRunAlertPanel (@"Warning", @"Program is running. Do you really "
                            @"want to quit?", @"Yes", @"No", NULL))
    quit = NO;

  if (quit) {
    [gdbManager executeCmd:@"set confirm off" withTty:YES withAnnotation:NO];
    [gdbManager executeCmd:@"quit" withTty:YES withAnnotation:NO];
//    [[stackBrowser window] saveFrameUsingName:stackBrowserFrameName];
//    [[selectionMatrix window] saveFrameUsingName:selectionMatrixFrameName];

    [[stackBrowser window] setFrameUsingName:[[stackBrowser window] stringWithSavedFrame]];
    [[selectionMatrix window] setFrameUsingName:[[selectionMatrix window] stringWithSavedFrame]];
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSLog (@"stack frame %@", NSStringFromRect([[stackBrowser window] frame]));
    NSLog (@"matrix frame %@", NSStringFromRect([[selectionMatrix window] frame]));
  }
}

- (void)continue:sender
{
  [gdbManager executeCmd:@"continue" withTty:YES withAnnotation:NO];
}

- (void)step:sender
{
  [gdbManager executeCmd:@"step" withTty:YES withAnnotation:NO];
}

- (void)finish:sender
{
  [gdbManager executeCmd:@"finish" withTty:YES withAnnotation:NO];
}

- (void)next:sender
{
  [gdbManager executeCmd:@"next" withTty:YES withAnnotation:NO];
  [self invalidateCurrentFrameVariables];
}

- (void)breakAt:sender
{
  Document* document = [Document currentDocument];
  int line;
  NSString* command;

  [document getStartLine:&line endLine:NULL];
  command = [NSString stringWithFormat:@"future-break %@:%d",
    [[document documentName] lastPathComponent], line];

  [gdbManager executeCmd:command withTty:YES withAnnotation:NO];
}

- (void)runUntil:sender
{
  Document* document = [Document currentDocument];
  int line;
  NSString* command;

  [document getStartLine:&line endLine:NULL];
  command = [NSString stringWithFormat:@"until %@:%d",
    [[document documentName] lastPathComponent], line];

  [gdbManager executeCmd:command withTty:YES withAnnotation:NO];
}

- (void)print:sender
{
  Document* document = [Document currentDocument];
  NSString* selection = [document selection];
  NSString* command = [NSString stringWithFormat:@"print %@", selection];

  [gdbManager executeCmd:command withTty:YES withAnnotation:NO];
}

- (void)printIndirect:sender
{
  Document* document = [Document currentDocument];
  NSString* selection = [document selection];
  NSString* command = [NSString stringWithFormat:@"print *(%@)", selection];

  [gdbManager executeCmd:command withTty:YES withAnnotation:NO];
}

- (void)showStackFrameWindow:sender
{
  [stackBrowser loadColumnZero];
  [[stackBrowser window] makeKeyAndOrderFront:nil];
}

/* Browser delegate methods */
- (void)browser:(NSBrowser*)sender
  createRowsForColumn:(int)column
  inMatrix:(NSMatrix*)matrix
{
  int i, count = 0;

  if (column == 0) {
    NSArray* frames = [stack frames];
    Frame* frame;
    FrameBrowserCell* cell;

    [matrix setPrototype:[[FrameBrowserCell new] autorelease]];

    for (i = 0, count = [[stack frames] count]; i < count; i++) {
      [matrix addRow];
      cell = [matrix cellAtRow:i column:0];
      frame = [frames objectAtIndex:[self frameNumberForBrowserCell:i]];
      [cell setStringValue:[frame functionName]];
      [cell setLeaf:([frame fileName] == nil)];
      [cell setFrame:frame];
      [cell setTarget:cell];
      [cell setAction:@selector(_selectLineInFile:)];
      [cell setTag:i];
    }
  }
  else if (column == 1) {
    int frameNumber = [self frameNumberForBrowserCell:[sender selectedRowInColumn:0]];
    Frame* frame = [[stack frames] objectAtIndex:frameNumber];
    id cell;

    [matrix setPrototype:[[GdbBrowserCell new] autorelease]];
    [stack setSelectedFrame:frameNumber];

    if ([frame variablesAreValid]) {
      count = [frame numberOfVariables];
      for (i = 0; i < count; i++) {
        [matrix addRow];
        cell = [matrix cellAtRow:i column:0];
        [cell setObjectToDisplay:[frame variableAtIndex:i]];
      }
    }
    else
      [frame getVariablesFromGDB];
  }
  else if (column > 1) {
    int frameNumber = [self frameNumberForBrowserCell:[sender selectedRowInColumn:0]];
    Frame* frame = [[stack frames] objectAtIndex:frameNumber];
    id displayableObject;
    NSArray* components;
    int i, count, index;

    [matrix setPrototype:[[GdbBrowserCell new] autorelease]];
    [stack setSelectedFrame:frameNumber];

    index = [[sender matrixInColumn:1] selectedRow];
    displayableObject = [frame variableAtIndex:index];

    /* Determine the indirected variable we must show */
    for (i = 2; i < column; i++) {
      index = [[sender matrixInColumn:i] selectedRow];
      displayableObject = [[displayableObject indirectedComponents] objectAtIndex:index];
    }

    /* Now we have the variable we have to display. If its indirected value is
      known already display them, otherwise issue a query to gdb to get them.
    */
    if ([displayableObject indirectedComponentsAreKnown]) {
      BOOL displayableObjectIsArray
          = [[displayableObject value] isKindOfClass:[ArrayValue class]];
      id cell;

      components = [displayableObject indirectedComponents];
      count = [components count];
      for (i = 0; i < count; i++) {
        [matrix addRow];
        cell = [matrix cellAtRow:i column:0];
        [cell setObjectToDisplay:[components objectAtIndex:i]];
        [cell setObjectIsArrayComponent:displayableObjectIsArray];
      }
    }
    else
      [displayableObject getIndirectedComponentsFromGDB];
  }
}

- (int)frameNumberForBrowserCell:(int)row
{
  return [[stack frames] count] - row - 1;
}

- (int)browserCellForFrame:(int)frameNumber
{
  return [[stack frames] count] - frameNumber - 1;
}


/* Changing the appearence of stack browser */
- (void)stackChanged
{
  [stackBrowser loadColumnZero];
}

- (void)updateCurrentFrame
{
  [stackBrowser reloadColumn:[stackBrowser lastColumn]];
}

/* Other methods */
- (id)gdbManager		{ return gdbManager; }
- (id)stackBrowser		{ return stackBrowser; }

- (void)selectInFile:(NSString*)file startLine:(int)sl endLine:(int)el
{
  Document* document;

  if ([Document openDocumentWithPath:file encoding:UnknownStringEncoding]) {
    document = [Document documentForPath:file];
    [document selectFromLine:sl toEndLine:el];
    [document jumpToSelection:nil];
  }
}

- (void)invalidateCurrentFrameVariables
{
  int selectedRow, frameNumber;

  if ((selectedRow = [stackBrowser selectedRowInColumn:0]) >= 0) {
    frameNumber = [self frameNumberForBrowserCell:selectedRow];
    [[[stack frames] objectAtIndex:frameNumber]
      invalidateCurrentVariables];
    [self updateCurrentFrame];
  }
}

@end
