// Copyright (94) by melonSoft Ralf Suckow Berlin, All Rights Reserved

// Note: sometimes we use the Class * type instead of id since
// the appkit includes the soundkit, which has different definitions
// for the activate and info methods.

#import "MLMKVMaster.h"
#import "MLParameters.h"
#import <musickit/musickit.h>
#import <dsp/dsp.h>

@implementation MLMKVMaster

- appDidInit:sender
{
  static char name[80];

  sprintf (name, VOICE_PRODUCER_NAME, "score-tmpl");
  [[NXConnection registerRoot:self withName:name] runFromAppKit];

  // initialize self
  [Orchestra setSamplingRate:44100.0];
  [Conductor setClocked:NO];
  [Conductor setThreadPriority:1.0]; 
  [PartPerformer setFastActivation:YES];  

  orchestra = [Orchestra new];
  if (![orchestra open]) {
    NXRunAlertPanel ("MusicKit Voice Interpreter",
"Can't open Digital Signal Processor. Do you have one? Maybe another application is using it. Also, the MusicKit needs to be installed on your computer!",
                     NULL, NULL, NULL);
  }
  [orchestra close];
  return self;
}

- showError:(const char *)text with:(const char *)parameter
{
  NXRunAlertPanel ("MusicKit Voice Interpreter",
                    text, NULL, NULL, NULL, parameter);
  return self;
}

- (NXStream *)expandScore:(NXStream *)inputStream fromParameters:params
{
  NXStream *   outputStream;
  char         parameterName[1024];
  int          c;
  int          count;
  const char * value;
  
  if (!inputStream)
    return NULL;
    
  // replacing all patterns like $parameter$ by their value from params
  
  if (!(outputStream = NXOpenMemory (NULL, 0, NX_READWRITE)))
    return NULL;
    
  while ((c = NXGetc (inputStream)) != EOF)
    if (c != '$')
      NXPutc (outputStream, c);
    else {

      count = 0;
      while ((c = NXGetc (inputStream)) != EOF && count < 1023)
        if (c != '$')
          parameterName[count++] = c;
        else
          break;

      if (!count)
        NXPutc (outputStream, '$'); // two $$s are a single $

      else if (count == 1023) {
        [self showError:"Parameter name too long" with:""];
        NXClose (outputStream);
        return NULL;
      }

      else {
        parameterName[count] = '\0';
        value = [params valueFor:NXUniqueString(parameterName)];
        if (!value) {
          [self showError:"Parameter `%s' not found" with:parameterName];
          NXClose (outputStream);
          return NULL;
        }
        NXPrintf (outputStream, "%s", value);
      }
    }
  
  NXSeek (outputStream, 0, NX_FROMSTART);
  return outputStream;
}

- (BOOL)makeFile:(const char *)outputFile 
  fromVoice:(const char *)inputFile
  parameters:params
{
  NXStream * scoreTemplateStream;
  NXStream * expandedScoreStream;
  BOOL       ok;
  id         score;
  
  ok = YES;
   
  NX_DURING
    scoreTemplateStream = NXMapFile (inputFile,NX_READONLY);
  NX_HANDLER
    [self showError:"I/O error -- Can't open file `%s'" with:inputFile];
    ok = NO;
  NX_ENDHANDLER

  if (ok) {
    expandedScoreStream = [self expandScore:scoreTemplateStream 
                          fromParameters:params];
    NXClose (scoreTemplateStream);

    if (expandedScoreStream) {  

      score = [[Score alloc] init];
      if (![score readScorefileStream:expandedScoreStream]) {
        [self showError:"bad syntax of scorefile `%s'" with:inputFile];
        ok = NO;
      }
      else 
        ok = [self makeFile:outputFile fromScore:score];
        
      [score free];
      NXClose (expandedScoreStream);
    }
    else
      ok = NO;
  }
  
  return ok;
}
  
- (BOOL)makeFile:(const char *)outputFile fromScore:(Score *)score
{
  ScorePerformer * scorePerformer;
  id               partPerformers;
  id               instruments;
  BOOL             ok;
  int              i;
  id               partPerformer;
  Part           * part;
  id               partInfo;
  char           * className;
  id               synthPatchClass;
  id               instrument;

  scorePerformer = [[ScorePerformer alloc] init];
  [scorePerformer setScore:score];
  [scorePerformer activate];

  if ([[score info] isParPresent:MK_tempo])
    [[Conductor defaultConductor] 
                setTempo:[[score info] parAsDouble:MK_tempo]];
                
  partPerformers = [scorePerformer partPerformers];
  instruments = [[List alloc] init];
  ok = YES;
  
  for (i = 0; partPerformer = [partPerformers objectAt:i]; i++) {

    part = [partPerformer part];
    
    if (!(partInfo = [part info])) {
      [self showError: "missing info for part `%s'" 
            with:MKGetObjectName(part)];
      ok = NO;
      break;
    }

    if (![partInfo isParPresent:MK_synthPatch]) {
      [self showError: "Part `%s' without synthPatch info"
            with:MKGetObjectName(part)];
      ok = NO;
      break;
    }
    
    className = [partInfo parAsStringNoCopy:MK_synthPatch];
    if (!*className || 
        !(synthPatchClass = [SynthPatch findSynthPatchClass:className])) {
      [self showError: "SynthPatch class `%s' not found" with:className];
      ok = NO;
      break;
    }

    instrument = [[SynthInstrument alloc] init];
    [instruments addObject:instrument]; 
    [[partPerformer noteSender] connect:[instrument noteReceiver]];

    [instrument setSynthPatchClass:synthPatchClass];

    if ([partInfo isParPresent:MK_synthPatchCount])
      [instrument setSynthPatchCount:[partInfo parAsInt:MK_synthPatchCount]
                  patchTemplate:[synthPatchClass patchTemplateFor:partInfo]];
  }

  if (ok) {
    [orchestra setOutputSoundfile:(char *)outputFile];
    [orchestra run];
    [Conductor startPerformance];
    [orchestra close];
  }
  [scorePerformer free];
  [[instruments freeObjects] free];
  return ok;
}

// <MLVoiceProduction> protocol

- (oneway void)voice:(const char *)documentFile
               makeFile:(const char *)outputFile 
               from:(const char *)parameters
               forClient:(id <MLProductionClient>)sender
               request:(int)number;
{
  BOOL ok;
  id   params;
  BOOL flag;
  
  params = [[MLParameters alloc] init];
  
  if (![params readFromMathString:parameters syntaxOK:&flag])
    ok = NO;
  else 
    ok = [self makeFile:outputFile
               fromVoice:documentFile
               parameters:params];        
  
  [sender server:self didRequest:number success:(int)ok];
  [params free];
  
  // trying to free the strings -- does this always work ?
  if (documentFile)
    free ((char *)documentFile);
  if (outputFile)
    free ((char *)outputFile);
  if (parameters)
    free ((char *)parameters);
}

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

- (int)app:sender openFile:(const char *)path type:(const char *)type
{
  // It is a better idea to forward it to Edit even if somebody is
  // using emacs or any other editor
  
  [self perform:@selector(doOpen:) with:(id)NXUniqueString (path) 
        afterDelay:0 cancelPrevious:NO];
  return YES;
}

- doOpen:path
{
  [[Application workspace] openFile:(const char *)path
                           withApplication:"Edit"];
  return self;
}

@end

