#import "Controller.h"

@implementation Controller

+ initialize
{
    static char buffer [1025];
    static NXDefaultsVector MyDefaults = {
        {"SayAvailable", "false"},
        {"SayPath",      "/usr/local/bin/say"},
        {"SndplayPath",  "/usr/bin/sndplay"},
        {"SoundPath",    buffer},
        {NULL}
    };
    sprintf (buffer, "%s/Library/Sounds", NXHomeDirectory ());
    NXRegisterDefaults ("FIX", MyDefaults);

    return self;
}

- init
{
  commands = NULL;
  variables = [[NXStringTable alloc] init];
  constants = [[NXStringTable alloc] init];
  directory = NULL;
  filename = NULL;
  return [super init];
}

- awakeFromNib
{
  [self revertDefaults:self];
  return self;
}

- defaultsOK:sender
{
  NXWriteDefault ("FIX", "SayAvailable", [sayAvailableButton state] ? "true": "false");
  NXWriteDefault ("FIX", "SayPath",      [sayPathField stringValue] ? 
                                         [sayPathField stringValue] : "");
  NXWriteDefault ("FIX", "SoundPath",    [soundPathField stringValue] ?
                                         [soundPathField stringValue] : "");
  NXWriteDefault ("FIX", "SndplayPath",  [sndplayPathField stringValue] ?
                                         [sndplayPathField stringValue] : "");
  return self;
}

- revertDefaults:sender
{
  const char * sayAvailable;
  const char * sayPath;
  const char * soundPath;
  const char * sndplayPath;

  sayAvailable = NXGetDefaultValue ("FIX", "SayAvailable");
  if (sayAvailable == NULL || !*sayAvailable || 
      strcmp (sayAvailable, "true") != 0)
    [sayAvailableButton setState:NO];
  else
    [sayAvailableButton setState:YES];

  sayPath = NXGetDefaultValue ("FIX", "SayPath");
  if (sayPath == NULL || !*sayPath)
    [sayPathField setStringValue:""];
  else
    [sayPathField setStringValue:sayPath];

  soundPath = NXGetDefaultValue ("FIX", "SoundPath");
  if (soundPath == NULL || !*soundPath)
    [soundPathField setStringValue:""];
  else
    [soundPathField setStringValue:soundPath];

  sndplayPath = NXGetDefaultValue ("FIX", "SndplayPath");
  if (sndplayPath == NULL || !*sndplayPath)
    [sndplayPathField setStringValue:""];
  else
    [sndplayPathField setStringValue:sndplayPath];

  return self;
}

- setKey:(const char *)key toValue:(const char *)value inStringTable:table
{
  [table insertKey:NXUniqueString(key) value:(char *)NXUniqueString(value)];
  return self;
}

- setNextCommand:(command *)command
{
  nextCommand = command;
  if (nextCommand != NULL)
    [programText setSel:nextCommand->startPosition :nextCommand->endPosition];
  else
    [programText setSel:0 :0];
  return self;
}

- analyseConstantLine:(const char *)line
{
  char   name [80];
  char * blankPtr;

  blankPtr = index (line, ' ');
  if (blankPtr == NULL) {
    [self setMessage:[translation valueForStringKey:
                     "Feste Daten mössen im Format <name> <wert> sein!"]];
    return NULL;
  }

  strncpy (name, line, blankPtr - line);
  name[blankPtr - line] = '\0';

  [self setKey:name toValue:blankPtr + 1 inStringTable:constants];
  return self;
}

- addCommand:(commandCode)code label:(char *)label argument:(char *)arg
  start:(int)start end:(int)end
{
  command * c, * n;

  c = malloc (sizeof (command));
  c->succ = NULL;
  c->startPosition = start;
  c->endPosition = end;
  strcpy (c->label, label);
  c->code = code;
  strcpy (c->argument, arg);

  if (commands == NULL)
    commands = c;
  else {
    n = commands; 
    while (n->succ != NULL)
      n = n->succ;
    n->succ = c;
  }

  return self;
}

- missingArgument:(char *)arg inCommand:(char *)command
{ 
  char buffer[256];

  sprintf (buffer, [translation valueForStringKey:
                   "Der Befehl %s braucht %s als Argument."], command, 
                   [translation valueForStringKey:arg]);
  [self setMessage:buffer];
  return self;
}

- analyseProgramLine:(const char *)line start:(int)start end:(int)end
{
  char label [80];
  char name [80];
  char argument [256];
  char * firstBlankPtr;
  char * secondBlankPtr;
  BOOL hasArgument;

  if (line[0] == ' ')
    while (line[1] == ' ')
      line++;

  firstBlankPtr = index (line, ' ');
  if (firstBlankPtr == NULL) {
    [self setMessage:[translation valueForStringKey:
       "Programmzeilen mössen im Format <nummer> <befehl> <argument> sein!"]];
    return NULL;
  }

  strncpy (label, line, firstBlankPtr - line);
  label[firstBlankPtr - line] = '\0';

  secondBlankPtr = index (firstBlankPtr + 1, ' ');
  if (secondBlankPtr == NULL) {
    hasArgument = NO;
    strcpy (name, firstBlankPtr + 1);
    strcpy (argument, "");
  }
  else {
    hasArgument = YES;
    strncpy (name, firstBlankPtr + 1, secondBlankPtr - firstBlankPtr - 1);
    name[secondBlankPtr - firstBlankPtr - 1] = '\0';
    strcpy (argument, secondBlankPtr + 1);
  }

  if (strcasecmp (name, "L") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"L"];
      return NULL;
    }
    [self addCommand:C_L label:label argument:argument start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcasecmp (name, "S") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"B"];
      return NULL;
    }
    [self addCommand:C_S label:label argument:argument start:start end:end];
    if (![self addVariable:argument]) {
      [self setMessage:[translation valueForStringKey:
        "Du kannst keinen festen Wert in einem S-Befehl verwenden"]];
      return NULL;
    }
  }
  if (strcasecmp (name, "D") == 0) {
    if (!hasArgument) {
      [self missingArgument:"einen Wert" inCommand:"D"];
      return NULL;
    }
    [self addCommand:C_D label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "GO") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Befehlsadresse" inCommand:"GO"];
      return NULL;
    }
    [self addCommand:C_GO label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "IF") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"IF"];
      return NULL;
    }
    [self addCommand:C_IF label:label argument:argument start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcasecmp (name, "TH") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Befehlsadresse" inCommand:"TH"];
      return NULL;
    }
    [self addCommand:C_TH label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "EL") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Befehlsadresse" inCommand:"EL"];
      return NULL;
    }
    [self addCommand:C_EL label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "E") == 0) {
    if (!hasArgument)
      [self addCommand:C_E label:label argument:"" start:start end:end];
    else {
      [self addCommand:C_E label:label argument:argument start:start end:end];
      if (![self addVariable:argument]) {
        [self setMessage:[translation valueForStringKey:
          "Du kannst keinen festen Wert in einem E-Befehl verwenden"]];
        return NULL;
      }
    }
  }
  if (strcasecmp (name, "A") == 0) {
    if (!hasArgument)
      [self addCommand:C_A label:label argument:"" start:start end:end];
    else {
      [self addCommand:C_A label:label argument:argument start:start end:end];
      [self addVariableOrConstOrDirectValue:argument];
    }
  }
  if (strcmp (name, "+") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"+"];
      return NULL;
    }
    [self addCommand:C_PLUS label:label argument:argument 
          start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcmp (name, "-") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"-"];
      return NULL;
    }
    [self addCommand:C_MINUS label:label argument:argument 
          start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcmp (name, "*") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"*"];
      return NULL;
    }
    [self addCommand:C_MUL label:label argument:argument 
          start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcmp (name, "/") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"/"];
      return NULL;
    }
    [self addCommand:C_DIV label:label argument:argument 
          start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcmp (name, "|") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Datenadresse" inCommand:"|"];
      return NULL;
    }
    [self addCommand:C_APP label:label argument:argument 
          start:start end:end];
    [self addVariableOrConstOrDirectValue:argument];
  }
  if (strcasecmp (name, "P") == 0) {
    if (!hasArgument) {
      [self missingArgument:"einen Wert" inCommand:"P"];
      return NULL;
    }
    [self addCommand:C_P label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "F") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Frage" inCommand:"F"];
      return NULL;
    }
    [self addCommand:C_F label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "I") == 0) {
    if (!hasArgument) {
      [self missingArgument:"eine Frage" inCommand:"I"];
      return NULL;
    }
    [self addCommand:C_I label:label argument:argument start:start end:end];
  }
  if (strcasecmp (name, "END") == 0) {
    [self addCommand:C_END label:label argument:"" start:start end:end];
  }
  if (strcasecmp (name, "SAY") == 0) {
    if (!hasArgument)
      [self addCommand:C_SAY label:label argument:"" start:start end:end];
    else {
      [self addCommand:C_SAY label:label argument:argument start:start end:end];
      [self addVariableOrConstOrDirectValue:argument];
    }
  }
  if (strcasecmp (name, "PLAY") == 0) {
    if (!hasArgument)
      [self addCommand:C_PLAY label:label argument:"" start:start end:end];
    else {
      [self addCommand:C_PLAY label:label argument:argument start:start end:end];
      [self addVariableOrConstOrDirectValue:argument];
    }
  }
  if (strcasecmp (name, "ALERT") == 0) {
    if (!hasArgument)
      [self addCommand:C_ALERT label:label argument:"" start:start end:end];
    else {
      [self addCommand:C_ALERT label:label argument:argument start:start end:end];
      [self addVariableOrConstOrDirectValue:argument];
    }
  }
  if (strcasecmp (name, "") == 0 || strcasecmp (name, "NOP") == 0) {
    [self addCommand:C_NOP label:label argument:"" start:start end:end];
  }
  return self;
}

- showVariables
{
  NXHashState  state;
  char       * key; 
  char       * value; 
  char buffer [1024];

  [variablesText setText:""];
  [variablesText selectText:self];
  state = [variables initState]; 
  while ([variables nextState:&state key:(const void **)&key 
                                     value:(void **)&value]) {
    sprintf (buffer, "%s %s\n", key, value);
    [variablesText replaceSel:buffer];
  }

  return self;
}

- (command *)searchCommandWithLabel:(char *)label
{
  command * n;

  for (n = commands; n != NULL; n = n->succ)
    if (strcasecmp (n->label, label) == 0)
      return n;

  return NULL;
}

- (const char *)getDirectValue:(const char *)name
{
  static char buffer[256];
  int len;

  if (name != NULL && (len = strlen (name)) > 1 && *name == '"') {

      // lets first copy the string without opening quote
    len--;
    strncpy (buffer, name+1, len);

      // now lets remove spaces from the end
    while (len > 0 && buffer[len - 1] == ' ')
      len--;

      // if len == 0, it was only one quote
      // if buffer[len - 1] != '"', it is not terminated with a quote
    if (len > 0 && buffer[len - 1] == '"') {
      buffer[len - 1] = '\0';
      return buffer;
    }
  }
  return NULL;
}

- (const char *)getVarOrConstOrDirectValue:(const char *)name
{
  const char * value;

  if ((value = [self getDirectValue:name]) != NULL)  
    return value;

  value = [constants valueForStringKey:name];
  if (value == NULL)
    value = [variables valueForStringKey:name];
  return value;
}

- setVar:(const char *)name toValue:(const char *)value
{
  char buffer[256];

  if ([constants valueForStringKey:name] != NULL) {
    sprintf (buffer, [translation valueForStringKey:
                   "Der feste Datenwert %s kann nicht öberschrieben werden"],
                   name);
    [self setMessage:buffer];
    return nil;
  }

  [self setKey:name toValue:value inStringTable:variables];
  return self;
}

- addVariable:(const char *)name
{
  if ([constants valueForStringKey:name] != NULL)
    return nil;

  [self setKey:name toValue:"0" inStringTable:variables];
  return self;
}

- addVariableOrConstOrDirectValue:(const char *)name
{
  if ([self getDirectValue:name] == NULL &&
      [constants valueForStringKey:name] == NULL)
    [self setKey:name toValue:"0" inStringTable:variables]; // its a variable
  return self;
}

- findInputLine:(int)line start:(int *)start end:(int *)end 
{
  char buffer[2];

  *start = [inputText positionFromLine:line];
  if (*start == -1)
    return nil;

  *end = [inputText positionFromLine:line + 1];  
  if (*end == -1)
    *end = [inputText textLength];

  if (*start == *end)
    return self;

  if ([inputText getSubstring:buffer start:(*end) - 1 length:1] != 1)
    return self;

  if (buffer[0] == '\n')
    (*end)--;

  return self;
}

- unselectNextInputLine
{
  int start, end;
  char buffer [3];

  [self findInputLine:nextInputLine start:&start end:&end];  
  if (start == -1)
    return self;

  if (end - start > 1 &&
      [inputText getSubstring:buffer start:start length:2] == 2 &&
      strncmp (buffer, "> ", 2) == 0) {
    [inputText setSel:start :start + 2];
    [inputText replaceSel:""];
  }
  return self;
}

- selectNextInputLine 
{
  int start, end;

  [self findInputLine:nextInputLine start:&start end:&end];  
  if (start == -1)
    return self;

  [inputText setSel:start :start];
  [inputText replaceSel:"> "];
  return self;
}

- (const char *) input
{
  int start, end;
  static char buffer[256];

  [self unselectNextInputLine];
  [self findInputLine:nextInputLine start:&start end:&end];  

  if (start < end)
    [inputText getSubstring:buffer start:start length:end - start];

  buffer[end - start] = '\0';

  nextInputLine++;
  [self selectNextInputLine];
  return buffer;
}

- output:(const char *)line
{
  int end;

  end = [outputText textLength];
  [outputText setSel:end :end];
  [outputText replaceSel:line];
  [outputText replaceSel:"\n"];
  [outputText setSel:end :[outputText textLength] - 1];
  [outputText scrollSelToVisible];
  return self;
}

- (const char *)askForValue:(const char *)question
{
  [questionField setStringValue:question? question : ""];
  [answerField setStringValue:""];
  [answerField selectText:self];
  cancelRequested = NO;
  [questionPanel makeKeyAndOrderFront:self];
  [NXApp runModalFor:questionPanel]; // returns with OK
  [questionPanel orderOut:self];

  if (cancelRequested)
    return NULL;

  return [answerField stringValue];
}

- answerOK:sender
{
  cancelRequested = NO;

  [NXApp stopModal];
  return self;
}

- answerCancel:sender
{
  cancelRequested = YES;

  [NXApp stopModal];
  return self;
}

- say:(const char *)value
{
  char buffer [1000];
  const char * sayAvailable;
  const char * sayPath;
  
  sayAvailable = NXGetDefaultValue ("FIX", "SayAvailable");
  if (sayAvailable == NULL || !*sayAvailable || strcmp (sayAvailable, "true") != 0) {
    NXBeep ();
    return self;
  }

  sayPath = NXGetDefaultValue ("FIX", "SayPath");
  if (sayPath == NULL || !*sayPath) {
    NXBeep ();
    return self;
  }

  if (value != NULL) {
    sprintf (buffer, "%s %s", sayPath, value);
    system (buffer);
  } 
  return self;
}

- (BOOL)fileExists:(const char *)name
{
  FILE * fp;

  if ((fp = fopen (name, "r")) != NULL) {
    fclose (fp);
    return YES;
  }
  return NO;
}

- (const char *)findSoundFile:(const char *)name
{
  static char buffer [1000];
  const char * soundPath;

  sprintf (buffer, "%s", name);
  if ([self fileExists:buffer])
    return buffer;

  strcat (buffer, ".snd");
  if ([self fileExists:buffer])
    return buffer;

  if (directory != NULL) {
    sprintf (buffer, "%s/%s", directory, name);
    if ([self fileExists:buffer])
      return buffer;

    strcat (buffer, ".snd");
    if ([self fileExists:buffer])
      return buffer;
  }

  soundPath = NXGetDefaultValue ("FIX", "SoundPath");
  if (soundPath != NULL && *soundPath) {
    sprintf (buffer, "%s/%s", soundPath, name);
    if ([self fileExists:buffer])
      return buffer;
  }

  strcat (buffer, ".snd");
  if ([self fileExists:buffer])
    return buffer;

  return NULL;
}

- play:(const char *)name
{
  char         msgbuffer [1000];
  char         buffer [1000];
  const char * file = NULL;
  int          code;
  const char * sndplayPath;
  const char * soundPath;

  sndplayPath = NXGetDefaultValue ("FIX", "SndplayPath");
  if (sndplayPath == NULL || !*sndplayPath) {
    NXBeep ();
    return self;
  }

  soundPath = NXGetDefaultValue ("FIX", "SoundPath");
  if (soundPath != NULL && !*soundPath)
    soundPath = NULL;

  if ((file = [self findSoundFile:name]) == NULL) {
    if (soundPath || directory) {
      strcpy (msgbuffer, [translation valueForStringKey:"Habe auch in "]);
      if (soundPath) {
        strcat (msgbuffer, soundPath);
        if (directory)
          strcat (msgbuffer, [translation valueForStringKey:" und "]);
      }
      if (directory)
        strcat (msgbuffer, directory);
      strcat (msgbuffer, [translation valueForStringKey:" gesucht."]);
    }
    else
      strcpy (msgbuffer, [translation valueForStringKey:"Es ist weder ein Sounddirectory in den Einstellungen angegeben noch ist die aktuelle Datei abgespeichert."]); 

    code = NXRunAlertPanel ("Play", 
               [translation valueForStringKey:
                "Kann die Sounddatei %s nicht finden! %s"],
                     "OK", [translation valueForStringKey:"Abbrechen"],
                     NULL, name, msgbuffer);
    if (code == NX_ALERTDEFAULT)
      return self;
    else
      return nil;
  }
  if (file != NULL) {
    sprintf (buffer, "%s %s", sndplayPath, file);
    system (buffer);
  }
  return self;
}

- alert:(const char *)value
{
  int code;

  if (value == NULL)
    value = "nix los!";

  code = NXRunAlertPanel ([translation valueForStringKey:"FIX teilt mit:"],
                          value, "OK", 
                          [translation valueForStringKey:"Abbrechen"], NULL);
  if (code == NX_ALERTDEFAULT)
    return self;
  else
    return nil;
}

- performNextCommand
{
  command    * next;
  int          ival;
  char         buffer [1024];
  const char * answer;
  int          code;
  id           result;

  if (nextCommand == NULL)
    return self;

  switch (nextCommand->code) {
  case C_L:   [accumulator setStringValue:
                 [self getVarOrConstOrDirectValue:nextCommand->argument]];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_S:   [self setVar:nextCommand->argument 
                  toValue:[accumulator stringValue]];
              [self showVariables];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_D:   [accumulator setStringValue:nextCommand->argument];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_GO:  next = [self searchCommandWithLabel:nextCommand->argument];
              if (next == NULL) {
                [self setMessage:
                 [translation valueForStringKey:"Unbekanntes Sprungziel"]];
                return nil;
              }
              [self setNextCommand:next];
              break;
  case C_IF:  if (strcasecmp ([accumulator stringValue],
                   [self getVarOrConstOrDirectValue:nextCommand->argument]) == 0)
                [testResult setState:1];
              else
                [testResult setState:0];

              [self setNextCommand:nextCommand->succ];
              break;
  case C_TH:  next = [self searchCommandWithLabel:nextCommand->argument];
              if (next == NULL) {
                [self setMessage:
                   [translation valueForStringKey:"Unbekanntes Sprungziel"]];
                return nil;
              }
              if ([testResult state] != 0)
                [self setNextCommand:next];
              else
                [self setNextCommand:nextCommand->succ];
              break;
  case C_EL:  next = [self searchCommandWithLabel:nextCommand->argument];
              if (next == NULL) {
                [self setMessage:
                   [translation valueForStringKey:"Unbekanntes Sprungziel"]];
                return nil;
              }
              if ([testResult state] != 0)
                [self setNextCommand:nextCommand->succ];
              else
                [self setNextCommand:next];
              break;
  case C_E:   if (nextCommand->argument[0] == '\0')
                [accumulator setStringValue:[self input]];
              else {
                [self setVar:nextCommand->argument toValue:[self input]];
                [self showVariables];
              }
              [self setNextCommand:nextCommand->succ];
              break;
  case C_A:   if (nextCommand->argument[0] == '\0')
                [self output:[accumulator stringValue]];
              else
                [self output:[self getVarOrConstOrDirectValue:nextCommand->argument]];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_P:   [self output:nextCommand->argument];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_F:   code = NXRunAlertPanel ([translation valueForStringKey:"FIX fragt:"],
                                      nextCommand->argument,
                               [translation valueForStringKey:"Nein"], 
                               [translation valueForStringKey:"Ja"], 
                               [translation valueForStringKey:"Abbrechen"]);
              switch (code) {
              case NX_ALERTDEFAULT:   [testResult setState:0];
                                      [self setNextCommand:nextCommand->succ];
                                      break;
              case NX_ALERTALTERNATE: [testResult setState:1];
                                      [self setNextCommand:nextCommand->succ];
                                      break;
              case NX_ALERTOTHER:     [self setNextCommand:NULL];
                                      break;
              }
              break;
  case C_I:   if ((answer = [self askForValue:nextCommand->argument]) == NULL) {
                [self setNextCommand:NULL];
                break;
              }
              [accumulator setStringValue:answer];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_PLUS:ival = atoi ([self getVarOrConstOrDirectValue:nextCommand->argument]);
              [accumulator setIntValue:[accumulator intValue] + ival]; 
              [self setNextCommand:nextCommand->succ];
              break;
  case C_MINUS:ival = atoi ([self getVarOrConstOrDirectValue:nextCommand->argument]);
              [accumulator setIntValue:[accumulator intValue] - ival]; 
              [self setNextCommand:nextCommand->succ];
              break;
  case C_MUL: ival = atoi ([self getVarOrConstOrDirectValue:nextCommand->argument]);
              [accumulator setIntValue:[accumulator intValue] * ival]; 
              [self setNextCommand:nextCommand->succ];
              break;
  case C_DIV: ival = atoi ([self getVarOrConstOrDirectValue:nextCommand->argument]);
              if (ival == 0) {
                [self setMessage:
                   [translation valueForStringKey:"Division durch Null"]];
                return nil;
              }
              [accumulator setIntValue:[accumulator intValue] / ival]; 
              [self setNextCommand:nextCommand->succ];
              break;
  case C_APP: sprintf (buffer, "%s%s", [accumulator stringValue],
                           [self getVarOrConstOrDirectValue:nextCommand->argument]);
              [accumulator setStringValue:buffer]; 
              [self setNextCommand:nextCommand->succ];
              break;
  case C_NOP: [self setNextCommand:nextCommand->succ];
              break;
  case C_END: [self setNextCommand:NULL];
              break;
  case C_SAY: if (nextCommand->argument[0] == '\0')
                [self say:[accumulator stringValue]];
              else
                [self say:[self getVarOrConstOrDirectValue:nextCommand->argument]];
              [self setNextCommand:nextCommand->succ];
              break;
  case C_PLAY:
              if (nextCommand->argument[0] == '\0')
                result = [self play:[accumulator stringValue]];
              else
                result = [self play:[self getVarOrConstOrDirectValue:nextCommand->argument]];
              if (result)
                [self setNextCommand:nextCommand->succ];
              else
                [self setNextCommand:NULL];
              break;
  case C_ALERT:
              if (nextCommand->argument[0] == '\0')
                result = [self alert:[accumulator stringValue]];
              else
                result = [self alert:[self getVarOrConstOrDirectValue:nextCommand->argument]];
              if (result)
                [self setNextCommand:nextCommand->succ];
              else
                [self setNextCommand:NULL];
              break;
  default:    [self setMessage:
                [translation valueForStringKey:"Unbekannter Kommandocode"]];
              return nil;
  }
  return self;
}

- reset:sender
{
  NXStream * stream;
  char       buffer[1024];
  char *     p;
  int        c;
  int        position, start;
  command *  comm, * next;
  
  [constants empty];
  [variables empty];
  [self unselectNextInputLine];

  // read and analyse constant lines
  stream = [constantsText stream];
  NXSeek (stream, 0L, NX_FROMSTART);
  position = 0;

  while (1) {
    start = position;
    p = buffer;
    while ((c = NXGetc (stream)) != '\n' && c != EOF) {
      position++;
      *p++ = c;
    }

    *p = '\0';
    if (p - buffer > 0)
      if (![self analyseConstantLine:buffer]) {
        [constantsText setSel:start :position];
        return self;
      }
    position++;

    if (c == EOF)
      break;
  }

  // free commands

  comm = commands; 
  while (comm != NULL) {
    next = comm->succ;
    free (comm);
    comm = next;
  }
  commands = NULL;

  // read and analyse program lines
  stream = [programText stream];
  NXSeek (stream, 0L, NX_FROMSTART);
  position = 0;
  while (1) {
    start = position;
    p = buffer;
    while ((c = NXGetc (stream)) != '\n' && c != EOF) {
      position++;
      *p++ = c;
    }

    *p = '\0';
    if (p - buffer > 0)
      if (![self analyseProgramLine:buffer start:start end:position]) {
        [programText setSel:start :position];
        return self;
      }
    position++;

    if (c == EOF)
      break;
  }

  [self showVariables];
  nextInputLine = 1;
  [self selectNextInputLine];
  [self setNextCommand:commands];
  [outputText setText:""];
  [accumulator setStringValue:"0"];
  [self setMessage:""];
  return self;
}

- step:sender
{
  if (![self performNextCommand])
    return nil;

  if (nextCommand == NULL)
    [self setMessage:[translation valueForStringKey:"Fertig!"]];

  return self;
}

/*
- run:sender
{
  int count;

  count = 0;
  while (nextCommand != NULL) {
    if (++count > 1000) // cycling control
      break;
    if ([self step:sender] == nil)
      break;
  }
  return self;
}
*/

- run:sender
{
  id result;

  if (nextCommand == NULL)
    return self;

  result = [self step:sender];
  if (result == nil)
    return self;

  [self perform:@selector(run:) with:sender afterDelay:50 cancelPrevious:YES];
  return self;
}

- stop:sender
{
  [self perform:@selector(run:) with:sender afterDelay:-1 cancelPrevious:YES];
  return self;
}

- toggleProgramVisibility:sender
{
  if ([sender state]) { // on is invisible
    [programText setBackgroundGray:NX_DKGRAY];
    [programText setTextGray:NX_DKGRAY];
    [constantsText setBackgroundGray:NX_DKGRAY];
    [constantsText setTextGray:NX_DKGRAY];
    [variablesText setBackgroundGray:NX_DKGRAY];
    [variablesText setTextGray:NX_DKGRAY];
  }
  else {
    [programText   setBackgroundGray:NX_WHITE];
    [programText   setTextGray:NX_BLACK];
    [constantsText setBackgroundGray:NX_WHITE];
    [constantsText setTextGray:NX_BLACK];
    [variablesText setBackgroundGray:NX_LTGRAY];
    [variablesText setTextGray:NX_BLACK];
  }
  [programText update];
  [constantsText update];
  [variablesText update];
  return self;
}

- setMessage:(const char *)msg
{
  [programText scrollSelToVisible];
  [messageText setStringValue:msg];
  return self;
}

- save:sender
{
  id         savePanel;
  int        tag;
  NXStream * stream;

  if (directory == NULL)
    directory = NXCopyStringBuffer ("~/Library/FIX");

  if (filename == NULL)
    filename = NXCopyStringBuffer ([translation valueForStringKey:"OHNE_NAMEN.fix"]);

  savePanel = [SavePanel new];
  [savePanel setTitle:[translation valueForStringKey:"FIX Abspeichern"]];
  [savePanel setRequiredFileType:"fix"];
  tag = [savePanel runModalForDirectory:directory file:filename];
  [savePanel setTitle:[translation valueForStringKey:"Save"]];
  if (tag == NX_OKTAG) {
    free (directory);
    directory = NXCopyStringBuffer ([savePanel directory]);

    free (filename);
    filename = NXCopyStringBuffer (rindex ([savePanel filename], '/') + 1);

    stream = NXOpenMemory (NULL, 0, NX_WRITEONLY);

    NXPrintf (stream, "Programm: %d\n", [programText byteLength]);
    [programText writeText:stream];

    NXPrintf (stream, "\nFeste Daten: %d\n", [constantsText byteLength]);
    [constantsText writeText:stream];

    [self unselectNextInputLine];
    NXPrintf (stream, "\nEingaben: %d\n", [inputText byteLength]);
    [inputText writeText:stream];
    [self selectNextInputLine];

    if (NXSaveToFile (stream, [savePanel filename]) == -1)
      NXRunAlertPanel ([translation valueForStringKey:"Error"], 
         [translation valueForStringKey:"Kann die Datei %s leider nicht schreiben."],
                       NULL, NULL, NULL, [savePanel filename]);

    NXCloseMemory (stream, NX_FREEBUFFER);

    [[programText window] setTitleAsFilename:[savePanel filename]];
  }
  return self;	
}

- restore:sender
{
  id         openPanel;
  int        tag;
  NXStream * stream;
  static const char * fileType[2] = {"fix", NULL};
  char *     buffer;
  int        count;

  openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  if (directory == NULL)
    directory = NXCopyStringBuffer ("~/Library/FIX");

  if (filename == NULL)
    filename = NXCopyStringBuffer ("");

  tag = [openPanel runModalForDirectory:directory
                              file:filename types:fileType];
  if (tag == NX_OKTAG) {
    free (directory);
    directory = NXCopyStringBuffer ([openPanel directory]);

    free (filename);
    filename = NXCopyStringBuffer (rindex ([openPanel filename], '/') + 1);

    stream = NXMapFile ([openPanel filename], NX_READONLY);
    if (stream == NULL)
      return self;

    count = 0;
    NXScanf (stream, "Programm: %d", &count);
    if (count == 0)
      [programText setText:""];
    else {
      buffer = malloc (count + 2);
      if (NXRead (stream, buffer, count + 1) != count + 1)
        return self;
      buffer[count + 1] = '\0';
      [programText setText:buffer + 1];
      free (buffer);
    }

    count = 0;
    NXScanf (stream, "\nFeste Daten: %d", &count);
    if (count == 0)
      [constantsText setText:""];
    else {
      buffer = malloc (count + 2);
      if (NXRead (stream, buffer, count + 1) != count + 1)
        return self;
      buffer[count + 1] = '\0';
      [constantsText setText:buffer + 1];
      free (buffer);
    }

    count = 0;
    NXScanf (stream, "\nEingaben: %d", &count);
    if (count == 0)
      [inputText setText:""];
    else {
      buffer = malloc (count + 2);
      if (NXRead (stream, buffer, count + 1) != count + 1)
        return self;
      buffer[count + 1] = '\0';
      [inputText setText:buffer + 1];
      free (buffer);
    }

    NXCloseMemory (stream, NX_FREEBUFFER);
    [[programText window] setTitleAsFilename:[openPanel filename]];
  }
  return self;
}

@end
