/**
 ** Eval.m
 **/

 
#import "Eval.h"
#import "CodeBrowser.h"
#import "PrefsPanel.h"
#import "EvalWrap.h"
#import "EvalText.h"
#import "EvalListener.h"
#import "GraphicsView.h"
#import "Finder.h"
#import "EvalString.h"
#import <defaults/defaults.h>
#import <objc/List.h>
#import <objc/objc-load.h>
#import <objc/error.h>
#import <streams/streams.h>
#import <libc.h>
#import <stdlib.h>
#import <strings.h>
#import <sys/dir.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/param.h>


// these function like "class" variables...there
// is only 1 instance of Eval, and it contains ivars transcriptText and
// transcriptGraphics. These static vars are initialized to these instance vars.
static id text, graphics ;
static const char *fileTypes[] = { NULL } ;

// provide a location for those pesky unused, but required, integer
// arguments
int unused ;
 
@implementation Eval: Application
{ id  transcriptWindow ;
  id  splitView ;
  id  transcriptText ;
  id  transcriptGraphics ;
  id  textScrollView ;
  id  graphicsScrollView ;
  id  msgTextField  ;
  id  prefsPanel ;
  id  findWindow ;
  char *fileName ;  // name of current file
  // module management
  id moduleBrowser ;
  id moduleComponents ;
  id modulePath ;
  id modList ;
  int openKnt ;
  // help panel's text
  id helpText ;
}

+ clearGraphics ;
{ return [graphics clear] ;
}

+ clearText ;
{ return [text setText: ""] ;
}


+ printf: (char *) format, ... ;
{ // format (as in printf) and place after the
  // current selection in the transcript
  NXStream *aStream ;
  char *textBuf ;
  int textLen, maxLen ;
  va_list argList ;
  NXSelPt start, end, newEnd ;
  aStream = NXOpenMemory(NULL, 0, NX_WRITEONLY) ;
  va_start(argList, format) ;
  NXVPrintf(aStream, format, argList) ;
  va_end(argList) ;
  NXGetMemoryBuffer(aStream, &textBuf, &textLen, &maxLen);
  [text getSel: &start :&end] ;
  if(start.cp < 0)
    start.cp = 0 ;
  if(end.cp < 0)
    end.cp = 0 ;
  if(start.cp > end.cp)
    start.cp = end.cp ;
  [text setSel: end.cp :end.cp] ;
  [text replaceSel: textBuf] ;
  [text getSel: &start :&newEnd] ;
  [text setSel: end.cp :newEnd.cp] ;
  NXCloseMemory(aStream,NX_FREEBUFFER) ;
  [[text window] orderFront: self] ;
  [text scrollSelToVisible] ;
  return self ;
}


+ transcriptText ;
{ // return the transcript text object 
  return text ;
}

+ graphicsView ;
{ // return the graphics view object
  return graphics ;
}

- (BOOL) appAcceptsAnotherFile: sender ;
{ return YES ;
}

- appDidInit: sender ;
{ // set myself as the services delegate
  [[self appListener] setServicesDelegate: self] ;
  // we bump openKnt every time we open a CodeBrowser...
  // this ensures we don't create windows on top of each other
  openKnt = 0 ;
  return self ;
}

- appWillInit: sender ;
{ // set the app listener so we can get messages
  // from Project Builder:
  id myListener = [[EvalListener alloc] init] ;
  [self setAppListener:myListener]  ;
  [myListener checkInAs:[NXApp appName]];
  [myListener addPort];
  return self ;
}


-  (int) app: sender openFile:(const char *)fName type:(const char *)aType
{ List *winList = [self windowList] ;
  int i, windowKnt = [winList count] ;
  id aWin ;
  for(i = 0 ; i < windowKnt ; i++)
  { // check windowList to see if file is already open...
    aWin = [winList objectAt: i] ;
    if([aWin respondsTo: @selector(fileName)])
    { if(!strcmp(fName,[aWin fileName]))
      { // file is already open...bring to front
        [aWin makeKeyAndOrderFront: self] ;
        return YES ;
      }
    }
  }
  // file is not already open:
  if([self openFileInCodeBrowser: fName])
    return YES ;
  else
    return NO ;
}


- appWillTerminate: sender
{ // Check and see if we have any files in /tmp that
  // should be removed. (This version of Eval always
  // creates its temporary files in /tmp, although some
  // future version may allow them to be created in
  // arbitrary directories.)  Then check if we have any
  // CodeBrowsers which have unsaved changes.
  int i, knt ;
  List *aList, *bList ;
  id aWin ;
  aList = [self windowList] ;
  bList = [[List alloc] init] ;
  knt = [aList count] ;
  { // make a list of all edited CodeBrowsers 
    for(i = 0 ; i < knt ; i++)
    { aWin = [aList objectAt: i] ;
      if((aWin != transcriptWindow) && [aWin isKindOf: [CodeBrowser class]] &&
         [aWin isDocEdited])
        [bList addObject: aWin] ;
    }
  }
  knt = [bList count] ;
  if(knt)
  { // then there are edited CodeBrowsers
    int rval ;
    rval = NXRunAlertPanel("","There are edited CodeBrowsers.",
            "Cancel","Quit Anyway","Review Unsaved") ;
    if(rval == NX_ALERTOTHER) // rightmost
    { // review unsaved
      for(i = 0 ; i < knt ; i++)
      { int rval = NXRunAlertPanel("Review Unsaved",
          "Save changes to %s?","Cancel","Don't Save","Save",
          [[bList objectAt: i] title]) ;
        if(rval == NX_ALERTOTHER) // rightmost button...save
          [[bList objectAt: i] save: self] ;
        else if(rval == NX_ALERTALTERNATE)
        {}   // middle button == don't save, i.e. do nothing
        else // rval == NX_ALERTDEFAULT, leftmost button == Cancel
        { [bList free] ;
          return nil ;
        }
      }
    } 
    else if(rval == NX_ALERTDEFAULT)
    { // leftmost button == cancel
      return nil ;
    }
    // else (rval == NX_ALERTALTERNATE) middle button ...quit anyway
  }
  aList = tmpFileList("/tmp") ;
  knt = [aList count] ;
  if(knt)
  { [self message: "Removing temporary files."] ;
    for(i = 0 ; i < knt ; i++)
      unlink([[aList objectAt: i] cString]) ;
  }
  return self ;
}


- awakeFromNib ;
{ // Do some setup work.
  static BOOL inited = NO ;
  NXRect aRect ;
  if(inited)
    return self ;
  else
    inited = YES ;
  [prefsPanel setup] ;
  [transcriptWindow awakeFromNib] ;
  // size the text and graphics scrollView
  sscanf(NXGetDefaultValue([NXApp appName],"TextScrollViewSize"),
    "%f %f", &aRect.size.width, &aRect.size.height) ;
  [textScrollView sizeTo: aRect.size.width :aRect.size.height] ;
  sscanf(NXGetDefaultValue([NXApp appName],"GraphicsScrollViewSize"),
    "%f %f", &aRect.size.width, &aRect.size.height) ;
  [graphicsScrollView sizeTo: aRect.size.width :aRect.size.height] ;
  // put views into splitview
  [splitView addSubview:
    [graphicsScrollView removeFromSuperview]] ;
  [splitView addSubview:
    [textScrollView removeFromSuperview]] ;
  sscanf(NXGetDefaultValue([NXApp appName],"Frame"),
    "%f %f %f %f", &aRect.origin.x, &aRect.origin.y,
    &aRect.size.width, &aRect.size.height) ;
  [transcriptWindow sizeWindow: aRect.size.width :aRect.size.height] ;
  [transcriptWindow moveTo:aRect.origin.x :aRect.origin.y] ;
  [transcriptWindow display] ;
  // setup the graphics view
  [graphics setUp] ;
  // set up the module list
  modList = [[List alloc] init] ;
  // become the transcript window's delegate so that
  // window moves can be written to defaults database
  [transcriptWindow setDelegate: self] ;
  return self  ;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ // delegate method for browser object 
  if(sender == moduleBrowser)
    return [modList count] ; 
  return 0 ;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{  if(sender == moduleBrowser)
   { [cell setStringValue: [[modList objectAt: row] name]];
     [cell setLeaf:YES] ;
   }
   return self ;
}

- browserHit: sender
{ if(sender == moduleBrowser)
  { // create a string from the components of this module
    NXStream *aStream = NXOpenMemory(NULL, 0, NX_WRITEONLY) ;
    char *buf, **components ;
    id aMod ;
    int i = 0;
    aMod = [modList objectAt: [[sender matrixInColumn: 0] selectedRow]] ;
    components = [aMod components] ;
    while(components[i]) // components is a null-terminated structure
      NXPrintf(aStream,"%s\n",components[i++]) ;
    NXPrintf(aStream,"%c",0) ;
    NXGetMemoryBuffer(aStream, &buf, &unused, &unused);
    [moduleComponents setText: buf] ;
    [modulePath setStringValue: [aMod path]] ;
    NXCloseMemory(aStream,NX_FREEBUFFER) ;
  }
  return self ;
}

- clearTranscriptGraphics: sender ;
{ return [Eval clearGraphics] ;
}

- clearTranscriptText: sender ;
{ return [Eval clearText] ;
}

- finder ;
{ return findWindow ;
}

- (const char * const *) getFileList  ;
{ // Pop up an open panel, pointing to the location of the last file
  // opened.  Return a list of these files (or NULL if user aborts).
  id openPanel ;
  const char *path ;
  char *directory, *file, *loc ;
  int pathLen ;
  const char * const * fileNames ;
  openPanel = [OpenPanel new] ;
  [openPanel allowMultipleFiles: YES] ;
  path = NXGetDefaultValue([NXApp appName],"LastFileOpened") ;
  // parse the path into its directory and file components
  directory = (char *) malloc(pathLen = (strlen(path) + 1)) ;
  strcpy(directory,path) ;
  loc = strrchr(directory,'/') ;
  if(loc)
  { *loc = '\0' ;
    file = (char *) malloc(pathLen - strlen(directory) + 1) ;
    strcpy(file,&loc[1]) ;
  }
  else // no directory part found
  { file = directory ;
    directory = NULL ;
  }      
  if([openPanel runModalForDirectory: directory file: file types: fileTypes])
    fileNames = [openPanel filenames] ;
  else
    fileNames = NULL ;
  free(file) ;
  if(directory)
     free(directory) ;
  return fileNames ;
}

- helpText: sender
{ // Figure out the id of the lower text field in 
  // the help panel...only do it once, cache the 
  // result.  Called from Eval(Eval-PostScript) evalPsHelpPanel:
  // and Eval(Eval-ObjC) evalObjCHelpPanel:
  if(!helpText)
  { int i,knt ;
    id subviews ;
    id x ;
    x = [[sender window] contentView] ;
    subviews = [x subviews] ;
    knt = [subviews count] ;
    for(i = 0 ; i < knt ; i++)
    { // search for the splitview
      x = [subviews objectAt: i] ;
      if([x isKindOf: [NXSplitView class]])
        break ;
    }
    subviews = [x subviews] ;
    helpText = [[[x subviews] objectAt: 1] docView] ;
  }
  return helpText ;
}

- message: (char *) aMsg ;
{ // show aMsg in the msg textfield of the current window
  if([[NXApp mainWindow] respondsTo: @selector(message:)])
    [[NXApp mainWindow] message: aMsg] ;
  return self ;
}

- moduleBrowser ;
{ return moduleBrowser ;
}

- new: sender ;
{ // open a new untitled CodeBrowser
  id aCodeBrowser = [NXApp loadNibSection:"CodeBrowser.nib" owner: self] ;
  // offset window on-screen so multiple windows don't stack
  // on to of each other
  NXRect frameRect ;
  [aCodeBrowser getFrame: &frameRect] ;
  frameRect.origin.x +=  25.0  * openKnt ;
  frameRect.origin.y -=  25.0  * openKnt++ ;
  if(openKnt == 25)
    openKnt = 0 ;
  [aCodeBrowser moveTo: frameRect.origin.x :frameRect.origin.y] ;
  [aCodeBrowser makeKeyAndOrderFront: self] ;
  return self;
}

- open:sender
{ // Pop up an open panel, pointing to the location of the last file
  // opened, then load the user's selection(s) into (a) new CodeBrowser(s)
  // or, if the file is already open in a CodeBrowser, bring it to the
  // front.
  int i ;
  const char * const * fileNames = [self getFileList] ;
  id openPanel = [OpenPanel new] ;
  if(fileNames)
  { List *winList = [self windowList] ;
    int windowKnt = [winList count] ;
    id aWin ;
    for( ; *fileNames ; fileNames++)
    { // for each file the user wants opened...
      char fName[2048] ;
      sprintf(fName,"%s/%s",[openPanel directory],*fileNames) ;
      for(i = 0 ; i < windowKnt ; i++)
      { // check windowList to see if file is already open...
        aWin = [winList objectAt: i] ;
        if([aWin respondsTo: @selector(fileName)])
        { if(!strcmp(fName,[aWin fileName]))
          { // file is already open...bring to front
            [aWin makeKeyAndOrderFront: self] ;
            break ;
          }
        }
      }
      if(i == windowKnt)
      { // then fName is not currently open
        [self openFileInCodeBrowser: fName] ;
      }
    }
  }
  return self ;
}


- openFileInCodeBrowser:  (const char *) fName
{ // Open a code browser on the named file.
  // Returns id of codeBrowser window if successful, else returns nil.
  NXStream *fileStream ;
  if((fileStream = NXMapFile(fName,NX_READONLY)) != NULL)
  { NXRect frameRect ;
    CodeBrowser *aCodeBrowser ;
    aCodeBrowser =  [NXApp loadNibSection:"CodeBrowser.nib" 
       owner: self] ;
    [aCodeBrowser fileName: fName] ;
    [aCodeBrowser readStream: fileStream] ;
    [aCodeBrowser display] ;
    // offset window on-screen so multiple windows don't stack
    // on to of each other
    [aCodeBrowser getFrame: &frameRect] ;
    frameRect.origin.x +=  25.0  * openKnt ;
    frameRect.origin.y -=  25.0  * openKnt++ ;
    if(openKnt == 25)
      openKnt = 0 ;
    [aCodeBrowser moveTo: frameRect.origin.x :frameRect.origin.y] ;
    [aCodeBrowser makeKeyAndOrderFront: self] ;
    NXWriteDefault([NXApp appName],"LastFileOpened",fName) ;
    NXCloseMemory(fileStream,NX_FREEBUFFER) ;
    return aCodeBrowser ;
  }
  else
  { NXRunAlertPanel("Eval", "Error, couldn't read: %s",
      NULL,NULL,NULL,fName) ;
    return nil ;
  }
}


- prefsPanel ;
{ return prefsPanel ;
}

- printFileToTranscript: (char *) theFileName ;
{ // append the text of the indicated file to the end of the
  // current transcript selection, and select it
  char *aStr ;
  struct stat statBuf ;
  int fd ;
  if(!(fd = open(theFileName,O_RDONLY)))
  { NXRunAlertPanel("Eval","Cannot open: %c",NULL,NULL,NULL,theFileName) ;
    return self ;
  }
  fstat(fd,&statBuf) ;
  aStr = malloc(statBuf.st_size + 1) ;
  read(fd,aStr,statBuf.st_size) ;
  aStr[statBuf.st_size] = '\0' ;
  [Eval printf: aStr] ;
  close(fd) ;
  free(aStr) ;
  return self ;
}

- printPSCode: sender ;
{ id mw = [NXApp mainWindow] ;
  if([mw respondsTo: @selector(codeBrowserText)])
  { if(mw == transcriptWindow)
    { // for the transcript, allow printing of the graphics
      // view if its the first responder
      return [[mw firstResponder] printPSCode: sender] ;
    }
    else return [[mw  codeBrowserText] printPSCode: sender] ;
  }
  return self ;
}


- reopen:sender
{ // Pop up an open panel, pointing to the location of the last file
  // opened, then load the user's selection(s) into (a) new CodeBrowser(s)
  // or, regardless of whether or not the file is already open.
  id openPanel = [OpenPanel new] ;
  const char * const * fileNames = [self getFileList] ;
  if(fileNames)
  { for( ; *fileNames ; fileNames++)
    { // for each file the user wants opened...
      char fName[2048] ;
      sprintf(fName,"%s/%s",[openPanel directory],*fileNames) ;
      [self openFileInCodeBrowser: fName] ;
    }
  }
  return self ;
}

- setGraphicsScrollView: sender ;
{ // stash the docView in both our ivar, transcriptGraphics,
  // and the global var graphics. 
  // once control returns to application loop.
  transcriptGraphics = graphics = [sender docView] ;
  graphicsScrollView = sender ;
  return self ;
}


- setTextScrollView: sender ;
{ // stash the docView in both our ivar, transcriptText,
  // and the global var transcript.
  transcriptText = text = [sender docView] ;
  textScrollView = sender ;
  return self ;
}

- showHelpPanel: sender ;
{ static BOOL inited = NO ;
  if(!inited)
  { // if we haven't seen the help panel yet...
    NXRect aRect ;
    NXRect b1Rect = {{8.0,19.0},{100.0,17.0}} ;
    NXRect b2Rect = {{8.0,2.0},{100.0,17.0}} ;
    id p = [NXHelpPanel new] ;
    id b1 = [[Button alloc] initFrame: &b1Rect] ;
    id b2 = [[Button alloc] initFrame: &b2Rect] ;
    id subviews, aView  = nil ;
    int i, knt ;
    [b1 setTitle: "Eval ObjC"] ;
    [b1 setTarget: NXApp] ;
    [b1 setAction: @selector(evalObjCHelpPanel:)] ;
    [b2 setTitle: "PostScript"] ;
    [b2 setTarget: NXApp] ;
    [b2 setAction: @selector(evalPsHelpPanel:)] ;
    subviews = [[p contentView] subviews] ;
    knt = [subviews count] ;
    for(i = 0 ; i < knt ; i++)
    { // find the lowest view in the contentView
      aView = [subviews objectAt: i] ;
      if([aView isKindOf: [ScrollView class]] &&
        [[aView docView] isKindOf: [Text class]])
          break ;
    }
    if(aView)
    { // make it smaller
      [aView getFrame: &aRect] ;
      aRect.origin.y += 41.0 ;
      aRect.size.height -= 41.0 ;
      [aView setFrame: &aRect] ;
      // add our own buttons
      [[p contentView] addSubview: b1] ;
      [[p contentView] addSubview: b2] ;
    }  
    inited = TRUE ;
  }
  return [super showHelpPanel: sender] ;
}

- showTranscript: sender ;
{ [transcriptWindow orderFront: self] ;
  return self ;
}

- showTranscript: (id) pboard userData: (const char *) uBuf error: (char **) msg ;
{ [transcriptWindow orderFront: self] ;
  return self ;
}


- splitViewDidResizeSubviews: sender
{ // write textScrollView size to defaults database
  NXRect aRect ;
  char prefString[80] ;
  [textScrollView getBounds: &aRect] ;
  sprintf(prefString,"%f %f ",aRect.size.width, aRect.size.height) ;
  NXWriteDefault([NXApp appName],"TextScrollViewSize",prefString) ;
  [graphicsScrollView getBounds: &aRect] ;
  sprintf(prefString,"%f %f ",aRect.size.width, aRect.size.height) ;
  NXWriteDefault([NXApp appName],"GraphicsScrollViewSize",prefString) ;
  return self ;
}

- transcriptGraphics ;
{ return transcriptGraphics ;
}

- transcriptText ;
{ return transcriptText ;
}

- unloadAllModules: sender
{ // unload all loaded modules 
  int i,knt = [modList count] ;
  for(i = knt - 1 ; i >= 0  ; i--)
  { [[self unloadModule: [modList objectAt: i]] free] ;
    [[modList removeObjectAt: i] free] ;
  }
  [moduleComponents setText: ""] ;
  [modulePath setStringValue: ""] ;
  [moduleBrowser loadColumnZero] ; 
  return self ;
}


- unloadSelectedModule: sender
{ // unload and free the module selected in the moduleBrowser
  int index = [[moduleBrowser matrixInColumn: 0] selectedRow] ;
  [[self unloadModule: [modList objectAt: index]] free] ;
  [moduleComponents setText: ""] ;
  [modulePath setStringValue: ""] ;
  [moduleBrowser loadColumnZero] ;
  return self ; 
}

- windowDidBecomeMain: sender ;
{ if([[NXApp mainWindow] respondsTo: @selector(windowDidBecomeMain:)])
    [[NXApp mainWindow]windowDidBecomeMain: sender] ;
  return self ;
}

- windowDidMove: sender ;
{ return [self windowDidResize: sender] ;
}

- windowDidResize: sender ;
{ // set the Eval Frame preference for the transcript window
  // location and size.  Note that we use the frame size of the
  // contentView...there is no method to set the size of a window,
  // only the size of its content view.
  NXRect aRect, bRect ;
  char prefString[80] ;
  [transcriptWindow getFrame: &aRect] ;
  [[transcriptWindow contentView] getFrame: &bRect] ;
  sprintf(prefString,"%f %f %f %f", aRect.origin.x, aRect.origin.y,
    bRect.size.width, bRect.size.height) ;
  NXWriteDefault([NXApp appName],"Frame",prefString) ;
  return self ;  
}


@end

