/**************************************************
 * SynthBuilder
 * Copyright 1993 Nick Porcaro All Rights Reserved
 **************************************************/

/*
 * Controller.m
 * Eric Jordan, independent work, Spring, 1992
 * Re-written by Nick Porcaro Summer 1993
 */
#import <stdlib.h>
#import <strings.h>
#import <appkit/Matrix.h>
#import <appkit/View.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>
#import <appkit/Button.h>
#import <appkit/OpenPanel.h>
#import <appkit/SavePanel.h>
#import <musickit/Midi.h>
#import "Controller.h"
#import "FakeSynthPatch.h"
#import "DragView.h"
#import "FakePatchPoint.h"
#import "MKCodeGen.h"
#import "ClmCodeGen.h"
#import "ElementController.h"
#import "Utilities.h"
#import "MainWindowDelegate.h"

extern char *getAppName();

static id openPanel = nil;

static char TempString[1024];

char *getOpenPath(char *banner)
{
  static const char dir[1024];

  if (!openPanel) 
    {
      openPanel = [OpenPanel new];
    }

  [openPanel setTitle:banner];
  [openPanel allowMultipleFiles:NO];
  if ([openPanel runModalForDirectory:dir file:NULL types:NULL])
    {
      return ((char *) [openPanel filename]);
    }
  else
    {
      return NULL;
    }
}

static id savePanel = nil;

char *getSavePath(char *banner)
/* Set up and run the Save panel for the given type.  */
{
  static const char dir[1024];
  static const char file[1024];

  if (!savePanel) 
    {
      savePanel = [SavePanel new];
    }

  [savePanel setTitle:banner];
  if ([savePanel runModalForDirectory:dir file:file])
    {
      return ((char *) [savePanel filename]);
    } 
  else
    {
      return NULL;
    }
}

@implementation Controller

static id theController = nil;

+ theController
{
    return theController;
}

- awakeFromNib
{
//  malloc_debug(0xFFFF);
  [dragView setPatch:theSP];
  return self;
}

- init
{
  theController = self;
  return self;
}

- displayMainWindow
/* Called after the ViewController gets its act together */
{
  [theWindow makeKeyAndOrderFront:self];
  return self;
}

- setHidingScene:sender
{
  if ([sender intValue])
      stopSoundOnHide = YES;
  else
      stopSoundOnHide = NO;
  
  return self;
}

- newSP:sender
/* This just erases what we already have.  The user
 * is asked to save the current SP 
 */
{
  int val;
  
  val = NXRunAlertPanel(getAppName(),
			"Do you want to save the current patch first?",
			"YES",   /* default */
			"NO",    /* alt */
			"Cancel"); /* other */
  switch (val)
    {
      case(NX_ALERTDEFAULT):
	{
	  [self save:self];
	  break;
	}
      case(NX_ALERTALTERNATE):
	{
	  break;
	}
    default:
	{
	  return self;
	}
    }
	 
  [theSP removeSelf];
  [theSP display];
  [dragView setPatch:theSP];
  [self setFileName:""];
  [[theSP window] makeKeyAndOrderFront:self];
   
  return self;
}

static BOOL didFirstOpen = NO;

- open:sender
{
  char *file_name;
  int val;

  if (didFirstOpen)
    {
      val = NXRunAlertPanel(getAppName(),
			    "Do you want to save the current patch first?",
			    "YES",   /* default */
			    "NO",    /* alt */
			    "Cancel"); /* other */
      switch (val)
	{
	  case(NX_ALERTDEFAULT):
	    {
	      [self save:self];
	      break;
	    }
	  case(NX_ALERTALTERNATE):
	    {
	      break;
	    }
	  default:
	    {
	      return self;
	    }
	}
    }
  else
    {
      didFirstOpen = YES;
    }

  sprintf(TempString, "%s Open Patch", getAppName());
  file_name = getOpenPath(TempString);
  if (! file_name)
    {
      return nil;
    }

  [self openFile:file_name];
  return self;
}

- openFile:(char *) fileName
{
  NXTypedStream *ts;
  id theSuperview = [theSP superview];
  id theOldSP = theSP;
  id aSP;

  ts = NXOpenTypedStreamForFile(fileName, NX_READONLY);
  if (ts) 
    {
      [theWindow setTitle: "Loading ..."];
      aSP = NXReadObject(ts);
      NXCloseTypedStream(ts);
    }
  else
    {
      NXRunAlertPanel("Open", "Cannot open file: %s", NULL, NULL, NULL, fileName);
      return nil;
    }

  /* Make sure all the interface controls that
   * theSP are responsible for are wired up properly
   */
  [mainWindowDelegate setOwner:aSP];
  [theSP deallocatePatch:self];
  [aSP setController: self];
  [aSP setWindowDelegate: [theSP windowDelegate]];
  [aSP setMemory: [theSP memory]];
  [aSP setUtilities: [theSP utilities]];
  [aSP setMidiPortSwitch: [theSP midiPortSwitch]];
  [aSP setSoundOutDeviceSwitch: [theSP soundOutDeviceSwitch]];
  [aSP setSamplingRateSwitch: [theSP samplingRateSwitch]];
  [aSP setDefaultArgValSwitch: [theSP defaultArgValSwitch]];
  [aSP setAllocOnOpenSwitch: [theSP allocOnOpenSwitch]];
  [aSP setAlwaysUpdateSwitch: [theSP alwaysUpdateSwitch]];
  [aSP setNoteOnButton: [theSP noteOnButton]];
  [aSP setNoteOffButton: [theSP noteOffButton]];
  [aSP setStopButton: [theSP stopButton]];
  [aSP setResetControl: [theSP resetControl]];
  [aSP setCloseAllControl: [theSP closeAllControl]];
  [aSP setDisplayAllControl: [theSP displayAllControl]];
  [aSP setPrintSelfControl: [theSP printSelfControl]];
  [aSP setOrch: [theSP getOrch]];
  [theOldSP removeSelf];
  theSP = aSP;
  [theSuperview addSubview:theSP];
  [dragView setPatch:theSP];

  [theSP updateElements];
  if ([theSP allocOnOpen])
    {
      [theSP allocatePatch];
    }

  [theSP display];
  [theSP makeSelected:nil];

  if ([renamePPSwitch intValue])
    {
      [self fixPatchPointNames];
    }

  [theSP sortFakePatchPointsByAllocOrder];
  [theSP sortFakeUGsByAllocOrder];
  [[theSP window] orderFront:self];
  [self setFileName:fileName];
  [theOldSP free];
  return self;
}

- fixPatchPointNames
{
  int i, len;
  id fakePatchPoints, thePatchPoint;
  char pp_name[256], *format_str;
  static char patch_name[256];

/* Make sure all the patch points have the
 * correct (based on feeder pin) name
 *
 * Here's the sense to this naming:
 *
 * In the feedback example, constantpp should be a patchpoint, not a synthData.
 * That is, you should say:
 *     constantpp13Out = [template addPatchpoint:MK_xPatch];  
 * rather than:
 *     constantpp13Out = [template addSynthData:MK_xData length:16];
 *
 * What you did is ok, but not as clear.
 * 
 * Also, I wouldn't use the name "Out" when something is a constantpp.  
 * Instead just use the name, i.e. "constantpp13".
 * I also wouldn't use the name "Out" when something is a synthdata.  
 * The only time you should use that is when you have
 * something that's an "implicit" patchpoint, i.e. something that was 
 * created by this program, not the user, in order to connect two unit generators. 
 */

  fakePatchPoints = [theSP getFakePatchPoints];
  for (i=0; i<[fakePatchPoints count]; i++)
    {
      thePatchPoint = [[[fakePatchPoints objectAt:i] getFromNode] superview];
      if (([thePatchPoint hasConstant]) || ([thePatchPoint isData]))
	{     
	  format_str = "%s";
	}
      else
	{
	  format_str = "%sOut";
	}
      
      sprintf(pp_name, format_str,
	      [[[[fakePatchPoints objectAt:i] getFromNode] superview] getName]);
      len = strlen(pp_name);
      strcpy(patch_name, pp_name);
      [ (FakePatchPoint *) [fakePatchPoints objectAt:i] setName:patch_name];
    }

  return self;
}


- save:sender
{
  if (currentFileName)
    {
      [self doSave:currentFileName];
    }
  else
    {
      [self saveAs:self];
    }

  return self;
}


- saveAs:sender
{
  char *fileName;

  sprintf(TempString, "%s Save Patch", getAppName());
  if (! (fileName = getSavePath(TempString)))
    {
      return self;
    }

  [self doSave:fileName]; /* fileName should have the full path */
  return self;
}

- doSave:(char *)fileName
{
  char *str;
  int len;

  NXTypedStream *ts;

  ts = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY);
  if (ts)
    {
      [theWindow setTitle: "Saving ..."];
      NXWriteRootObject(ts, theSP);
      NXCloseTypedStream(ts);

      /* Sorry this is needed because setFileName
       * and the caller of this function can
       * interfere with each other
       */
      len = strlen(fileName) + 1;
      str = (char *) NXZoneCalloc([self zone], len, sizeof(char));
      strcpy(str, fileName);
      [self setFileName:str];
      NXZoneFree([self zone], str);
    } 
  else
    {
      NXRunAlertPanel(getAppName(), "Can't create file: %s",
		      NULL, NULL, NULL, fileName);
    }

  return self;
}

- setFileName:(char *) file
{
  int len;

  if (currentFileName)
    {
      NXZoneFree([self zone], currentFileName);
    }
  currentFileName = NULL;

  len = strlen(file);
  if ( (! file) || (len <= 0) || (strcmp(file, "") == 0) )
    {
      [theWindow setTitle: "Untitled"];
      return self;
    }

  len++;
  currentFileName = (char *) NXZoneCalloc([self zone], len, sizeof(char));
  if (! currentFileName)
    {
      NXRunAlertPanel(getAppName(), "Out of Memory", NULL, NULL, NULL);
      return self;
    }

  strcpy(currentFileName, file);

  [theWindow setTitle: [utilities getPathLeaf:currentFileName]];
  return self;
}

- removeSelected:sender
{
  [theSP removeSelected:sender];
  return self;
}

- setNameOfSelectedObject:sender
{
  [theSP setNameOfSelectedObject:sender];
  return self;
}

- getHelp:sender
{

   [NXApp showHelpPanel:self];

/*************

  if (!helpWindow) 
    {
      [NXApp loadNibSection:"Help.nib" owner:self];
    }

  [helpWindow makeKeyAndOrderFront:self];
****************/

  return self;
}

- getInfo:sender
{
  if (!infoPanel) 
    {
      [NXApp loadNibSection:"Info.nib" owner:self];
    }

  [infoPanel makeKeyAndOrderFront:self];
  return self;
}

- theSP
{
    return theSP;
}

- elementController
{
    return theElementController;
}

- displayAdminPanel:sender
{
  int val;

  val = NXRunAlertPanel(getAppName(),
			"DANGER: Only for administrators. Are you sure?",
			"NO",    /* default */
			"YES",   /* alt */
			"Cancel"); /* other */
  switch (val)
    {
      case(NX_ALERTDEFAULT):
	{
	  break;
	}
      case(NX_ALERTALTERNATE):
	{
	  [adminPanel makeKeyAndOrderFront:self];
	  break;
	}
    default:
	{
	  break;
	}
    }
	 
  return self;
}

- changeMidiPort:sender
/* this is a temporary hack to get around a problem where
 * when you load a file with a midi object, SynthBuilder will
 * crash when you attempt to change the midi port.
 */
{
  [theSP restartMidi:sender];
  return self;
}

- showLegal:sender
{
  if (!legalPanel) 
    {
      [NXApp loadNibSection:"Legal.nib" owner:self];
    }

  [legalPanel makeKeyAndOrderFront:self];
  return self;
}

@end

@implementation Controller(ApplicationDelegate)

/* 
 * We have set up the Controller to be
 * the delegate of the Application object
 * This is done in the Interface Builder
 * by connnecting the File's Owner delegate
 * outlet to the ControllerInstance.
 * Since Controller is a delegate of Application,
 * forwards the appDid* and appWill* methods
 * to Controller.  This is a good thing to do, as 
 * opposed to making a subclass of Application
 * and overriding it's appDid* appWill methods
 */

/*
 * Set some defaults when the app starts
 */

- appDidInit:sender
{
  if ([soundHidingPref intValue])
      stopSoundOnHide = YES;
  else 
      stopSoundOnHide = NO;

  return self;
}

/* If hidden, kill the orchestra if the user
 * has set this as a preference
 */

- appDidResignActive:sender
{

  if (stopSoundOnHide)
    {
       [theSP releaseDSP:self];
    }
   
  return self;

}

- appWillTerminate:sender

/* This is what gets called when the terminate message
 * is sent to the app.  We can clean up the midi mess here
 * If this method were to return nil, the app would not terminate.
 * For example you could add code in here to check for unsaved documents
 */
{
  int val;

  val = NXRunAlertPanel(getAppName(),
			"Do you want to save the current patch first?",
			"YES",   /* default */
			"NO",    /* alt */
			NULL); /* other */
  switch (val)
    {
      case(NX_ALERTDEFAULT):
	{
	  [self save:self];
	  break;
	}
    }

  return self;
}

#ifdef HELP_PANEL_TRICK
> - app:sender willShowHelpPanel:panel
> /* This method does nothing non-standard.
>  * I was trying to figure out how to 
>  * get the help panel to resize.  I ran out of time. (Nick)
>  */
> {
>     char path[MAXPATHLEN + 1];
> 
>     sprintf (path, "%s/%s", [panel helpDirectory],
>       "TableOfContents.rtf");
> 
>     [panel showFile:path atMarker:NULL];
>     return self;
> }
> 
#endif HELP_PANEL_TRICK

@end
