/**
 ** Eval(ObjC)
 **
 ** Methods for evaluating and loading
 ** objective C code, object modules,
 ** and libraries.
 **/

#import "Eval.h"

@implementation Eval(ObjC)

- evalObjC:(id) pboard userData: (const char *) uBuf error: (char **) msg ;
{ // evaluate the pasteboard selection
  char *text ;
  const char *const *types ;
  int i, textLen ;
  BOOL found = NO ;
  // fetch the data from pasteboard
  types = [pboard types] ;
  for(i = 0 ; types[i]; i++)
  { if(!strcmp(types[i], NXAsciiPboardType))
    { found = YES ;
      break ;
    }
  }
  if(!found) // no ascii found...
    return self ;
  [pboard readType:NXAsciiPboardType data:&text length:&textLen];
  if(!text || !textLen) // nothing there...
    return self ;
  else
  { [self evalObjC: text length: textLen] ;
    [pboard  deallocatePasteboardData:text length:textLen];
  }
  return self ;
}


- evalObjC: (char *) text length: (int) len ;
{ // evaluate objc in text (not necessarily null-terminated)...of length len
  char *lastTextChar, aStr[128], *header, *body, tmpFile[18] = "EvalXXXXXXXX" ;
  FILE *fp ;
  int firstHeaderLine = 1, headerLineKnt = 1 ;
  NXHandler exception;
  Module *mod ;

  [self message: "Creating program text..."] ;
  // get pointer to end of text
  lastTextChar = &text[len - 1] ;
  // find out if we have any header
  header = body = text ;
  // move header pointer to start of header
  while((header <= lastTextChar) && isspace(*header))
  { // scan past any initial whiteSpace, counting lines as we go
    if(*header == '\n')
      firstHeaderLine++ ;
    header++ ;
  }
  if(*header == '#') // then we have a header
  { // move body pointer to start of body
    body = header ;
    while(strncmp(body++,"\n\n", 2)) // "empty" line is end of header  
    { if(body == lastTextChar) // then header ends without a body
      { NXRunAlertPanel("Eval", "No body to evaluate.\n", NULL,NULL,NULL) ;
        [self message: ""] ;
        return self ;
      }
      if(*body == '\n')
        headerLineKnt++ ;
    }
    body++ ; // make body point 1 char beyond end of header,
  }
  else
  { header = "" ;   // no header at all
    firstHeaderLine = 1 ;
    headerLineKnt = 0 ;
  }
  // The string tmpFile is both the name of the class we will create
  // and the base name of the file, within /tmp,  it will live in...
  mkTempFile("/tmp",tmpFile) ;
  sprintf(aStr,"/tmp/%s.m",tmpFile) ;
  fp = fopen(aStr,"w+") ;
  fprintf(fp,
        "%.*s\n"
	"#import <objc/Object.h>\n"
        "@interface ODict: Object\n"
        "+ insertKey: (char *) aKey value: (void *) data ;\n"
        "+ removeKey: (char *) aKey ;"
        "+ (void *) valueForKey: (char *) aKey ;\n"
        "@end\n"
	"@interface Eval: Object\n"
	"+ clearGraphics ;\n"
	"+ clearText ;\n"
	"+ printf: (char *) format, ... ;\n"
	"+ ps: (char *) format, ... ;\n"
	"+ startPS ;\n"
         "+ stopPS ;\n"
	"@end\n"
   	"@interface EvalDoit: Object\n"
   	"+(void) doit ;\n"
   	"@end\n\n"
   	"@implementation EvalDoit\n"
   	"+ (void) doit\n"
   	"{\n"
   	"#line %d\n"
  	 "%.*s\n"
   	"}\n"
   	"@end\n",
    (int) ((unsigned) body - (unsigned) text), header,
     headerLineKnt + firstHeaderLine,
    (int) ((unsigned) lastTextChar - (unsigned) body) + 1,body) ;
  fflush(fp) ;
  fclose(fp) ;
  
  if(mod = [[Module alloc] initPath: aStr])
  { if([self loadModule: mod])
    { [self message: "Executing..."] ;
      exception.code = 0 ;
      NX_DURING
      // send the message...
        objc_msgSend(objc_getClass("EvalDoit"), sel_getUid("doit")) ;
      NX_HANDLER
        exception = NXLocalHandler ;
      NX_ENDHANDLER
      // now remove the module
      [self unloadModule: mod] ;
    }
  }
  // free file we created
  sprintf(aStr,"/tmp/%s.m",tmpFile) ;
  unlink(aStr) ;
  // free the module
  [mod free] ;
  [self message: ""] ;
  return self ;
}

- evalObjCHelpPanel: sender ;
{ // evaluate selection in help panel
  NXSelPt start, end ;
  int textLen ;
  char *textBuf ;
  if(!helpText)
    [self helpText: sender] ;
  [helpText getSel:&start :&end] ;
  textLen = end.cp - start.cp ;
  textBuf = malloc(textLen + 1) ;
  if(textBuf)
  { [helpText getSubstring:textBuf start:start.cp length:textLen] ;
    [self evalObjC: textBuf length: textLen] ;
  }
  return self ;
}


- loadFromFile: sender ;
{ // Load a .m,.o, or .a file from disk.
  static const char *loadFileTypes[] = {"m","o","a",NULL } ;
  char buf[1024] ;
  Module *mod ;
  id openPanel = [OpenPanel new] ;
  if(![openPanel runModalForTypes: loadFileTypes])
    return nil ;
  else
    sprintf(buf,"%s/%s",[openPanel directory],*[openPanel filenames]) ;
  if(mod = [[Module alloc] initPath: buf])
    [self loadModule: mod] ;
  [moduleBrowser loadColumnZero] ;
  return self ;
}

- loadModule: (Module *) aMod ;
{ // Load module mod.  If a module which equals: mod is already
  // loaded, it and everything loaded after it must be unloaded,
  // then mod is loaded, and finally everything loaded after 
  // must be reloaded.
  int i,j,knt = [modList count] ;
  for(i = knt  ; i > 0 ; i--)
  { id currentMod = [modList objectAt: i - 1] ;
    if([aMod equals: currentMod])
    { // unload mudules loaded after currentMod, then unload currentMod
      // remove it from the moduelList and free it
      for(j = 0 ; j < i ; j++)
        [[modList objectAt: j] unload] ;
      [[modList removeObjectAt: i - 1] free] ;
      i-- ;
      break ;
    }
  }
  [modList insertObject: aMod at: i] ;
  for( ; i >= 0 ; i--)
    [[modList objectAt: i] load] ; 
  return self ;
}

- loadObjC:(id) pboard userData: (const char *) uBuf error: (char **) msg ;
{ // compile and load the pasteboard selection
  char *text ;
  const char *const *types ;
  int i, textLen ;
  BOOL found = NO ;
  // fetch the data from pasteboard
  types = [pboard types] ;
  for(i = 0 ; types[i]; i++)
  { if(!strcmp(types[i], NXAsciiPboardType))
    { found = YES ;
      break ;
    }
  }
  if(!found) // no ascii found...
    return self ;
  [pboard readType:NXAsciiPboardType data:&text length:&textLen];
  if(!text || !textLen) // nothing there...
    return self ;
  else
  { [self loadObjC: text length: textLen] ;
    [pboard  deallocatePasteboardData:text length:textLen];
  }
  [moduleBrowser loadColumnZero] ;
  return self ;
}

- loadObjC: (char *) textBuf length: (int) textLen
{ // Load ObjC class(es) from textbuf.
  // First, create a file...
  int tmpFileLen ;
  char tmpFile[20] = "/tmp/EvalXXXXXXXX" ;
  int fd ;
  // write text into a file in /tmp
  mkTempFile(NULL,tmpFile) ;
  tmpFileLen = strlen(tmpFile) ;
  tmpFile[tmpFileLen] = '.' ; // add .m extension
  tmpFile[tmpFileLen+1] = 'm' ; 
  tmpFile[tmpFileLen+2] = '\0' ; 
  fd = open(tmpFile,O_WRONLY | O_CREAT | O_TRUNC, 0666) ;    
  if((fd == -1) || (write(fd,textBuf,textLen) == -1))
  { // run an alert panel...
    NXRunAlertPanel("Eval","Can't create temporary file: %s",NULL,NULL,NULL,tmpFile) ;
    if(fd != -1) // if file was opened,
      close(fd) ; // close it
    return self ;
  }
  [self loadModule: [[Module alloc] initPath: tmpFile]] ;
  unlink(tmpFile) ;
  return self ;
}

- unloadModule: (Module *) aMod ;
{ // Unload module mod.  If aMod is not on the top of
  // the loadList, then everything loaded after it must
  // first be unloaded before aMod may be unloaded, and
  // finally these modules must be reloaded. Returns aMod.
  int i,j,knt = [modList count] ;
  for(i = knt  ; i > 0 ; i--)
  { id currentMod = [modList objectAt: i - 1] ;
    if([aMod equals: currentMod])
    { // unload mudules loaded after currentMod, then unload currentMod
      // and remove it from the moduleList
      for(j = 0 ; j < i ; j++)
        [[modList objectAt: j] unload] ;
      [modList removeObjectAt: i - 1] ;
      i-- ;
      break ;
    }
  }
  i-- ;
  for( ; i >= 0 ; i--)
    [[modList objectAt: i] load] ; 
  return aMod ;
}


@end