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

/* #define DEBUG_ERASE 1 */
/*
 * FakePatchPoint.m
 * Eric Jordan, independent work, Spring, 1992
 */

#import <stdlib.h>
#import <stdio.h>
#import <appkit/Application.h>
#import <appkit/Control.h>
#import <appkit/Slider.h>
#import <musickit/musickit.h>
#import "FakePatchPoint.h"
#import "FakeSynthPatch.h"
#import "PatchParam.h"
#import "Clavier.h"
#import "FakeUG.h"
#import <dsp/DSPConversion.h>
#import "Utilities.h"

extern char *getAppName();

@implementation FakePatchPoint
  
static char TempString[1024];

+ initialize
 /* Set the version. This can be used in a later version to distinguish older
  * formats when unarchiving documents. 
  */
{
  [FakePatchPoint setVersion:3];
  return self;
}

- awakeFromNib
{
  clickCount = 0;
  midiConnected = NO;
  return self;
}

- init
{
  [super init];
  toNode = [[List alloc] init];
  allocOrder = 1;
  isAllocated = NO;
  strcpy(theName,"");
  clickCount = 0;
  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;
  midiConnected = NO;
  return self;

}

- setSP:aSP
{
  theSP = aSP;
  return self;
}

- closeInspector
{
  [inspector performClose:self];
  inspectorDisplayed = NO;
  return self;
}

- setName:(char *) aName
{
  if (! aName)
    {
      return nil;
    }

  if (! [[theSP utilities] checkName:aName])
    {
      if (inspector)
	{
	  [ppNameField setStringValue:theName];
	}
      return nil;
    }

  strcpy(theName, aName);
  return self;
}

- takeAllocOrderFrom:sender
{

  int button, order, ival, maxOrder;
  
  maxOrder = [[theSP getFakePatchPoints] count];
  order = [self getAllocOrder];

  if ([sender isKindOfClassNamed: "TextField"])
    {
      ival = [sender intValue];
      if ((ival <= maxOrder) && (ival >=1))
	{
	  [self setAllocOrder:order];
	  [theSP sortFakePatchPointsByAllocOrder];
	  [self updateAllocOrder];
	}
      else
	{
	  NXRunAlertPanel(getAppName(), "Invalid order: %d Must be between 1 and %d",
			  NULL, NULL, NULL, ival, maxOrder);
	  [sender setIntValue:order];
	  return self;
	}
    }
  else
    {
      /* Get order from switcher */

      /* button = 1 for top, -1 for botton */
      button = [[sender selectedCell] tag];
      order += button;
      
      /* Only set a new order if it's within range */
      if ((order <= maxOrder) && (order >= 1))
	{
	  [self setAllocOrder:order];
	  [theSP sortFakePatchPointsByAllocOrder];
	  [self updateAllocOrder];
	}
    }

  return self;
}


- setAllocOrder:(int) order
{
  allocOrder = order;
  return self;
}

- (int) getAllocOrder
{
  return allocOrder;
}

- updateAllocOrder
{
  [allocOrderField setIntValue:allocOrder];
  return self;
}

- displayInspector
{
  if (! inspector)
    {
      [NXApp loadNibSection:"FakePatchPoint.nib" owner:self withNames:NO];
    }
	
  [ppNameField setStringValue:theName];
  [self updateAllocOrder];
  [self setInspectorName: theName];
  [self updateInfo];

  if (inspectorRectJustLoaded)
    {
      if (useInspectorRect)
	{
	  [inspector placeWindowAndDisplay:&inspectorRect];
	  useInspectorRect = NO;
	}
      inspectorRectJustLoaded = NO;
    }
  
  /* doHighlight toggle prevents the instance
   * from being highlighted twice
   */
  doHighlight = NO;
  [inspector makeKeyAndOrderFront:self];
  doHighlight = YES;
  inspectorDisplayed = YES;

  return self;
}

- displayInspectorIfWasActive
{
  if (inspectorDisplayed)
    {
      [self displayInspector];
    }

  return self;
}

- updateInfo
{
  int i;
  id aFakeUG, aToNode;
  static char fromStr[256], toStr[256];
  char *ptr;
  int count;
  aFakeUG = [fromNode superview];
  ptr = [aFakeUG getMethod:[fromNode getTag]];
  if (ptr)
    {
      sprintf(fromStr, "%s %s", [aFakeUG getName], ptr);
    }
  else
    {
      sprintf(fromStr, "%s", [aFakeUG getName]);
    }
  [fromInfo setStringValue:fromStr];

  strcpy(toStr, "");
  count = [toNode count];
  for (i=0; i<count; i++)
    {
      aToNode = [toNode objectAt:i];
      aFakeUG = [aToNode superview];
      ptr = [aFakeUG getMethod:[aToNode getTag]];
      if (ptr)
	{
	  sprintf(TempString, "%s %s", [aFakeUG getName], ptr);
	}
      else
	{
	  sprintf(TempString, "%s", [aFakeUG getName]);
	}

      strcat(toStr, TempString);
      if (i < (count - 1))
	{
	  strcat(toStr, ", ");
	}
    }

  [toInfo setStringValue:toStr];

  return self;
}

- inspectorClosed
/* Called by the inspector's delegate */
{
  inspectorDisplayed = NO;
  return self;
}

- setInspectorName:(char *) aName
{
  [inspector setTitle: aName];
  return self;
}

- validate
{
  int i;
  id aToNode;

  
  [fromNode setPatch: theSP];
  for (i=0; i<[toNode count]; i++)
    {
      aToNode = [toNode objectAt:i];
      [aToNode setPatch: theSP];
      [self checkMidiFrom:[fromNode superview] To:[aToNode superview]];
    }

  return self;
}

-connect:newFromNode to:newToNode xmemory:(BOOL)mem
{
  id fromFakeUG, toFakeUG;

  fromFakeUG = [newFromNode superview];
  toFakeUG = [newToNode superview];

  if ( ([fromFakeUG isClavier] && [toFakeUG isPatchParameter]))
    {
      id nr;
      [[fromFakeUG clavier] connectNoteSenderTo: 
         [[toFakeUG patchParam] noteReceiver]];
      
	// This bologna is because we have to make sure noteOnHandler is last thing
      nr = [[[fromFakeUG clavier] noteOnHandler] noteReceiver];
      [[[fromFakeUG clavier] noteSender] disconnect:nr];
      [[[fromFakeUG clavier] noteSender] connect:nr];
      
    }

  /* Make sure we don't connect event UGs to non-event UGs */
  if ( ([fromFakeUG isClavier] && ! [toFakeUG isPatchParameter]) ||
       ([fromFakeUG isMidi] && ! [toFakeUG isPatchParameter]) )
    {
      return self;
    }

  if (! [self checkMidiFrom:fromFakeUG To:toFakeUG])
    {
      return nil;
    }

  if (!fromNode)
    {
      fromNode = newFromNode;
      xmemory = mem;
    }

  [toNode addObject:newToNode];
  
  return self;
}
  
- checkMidiFrom:aFromFakeUG To:aToFakeUG
{
  id midi;

//  if ([aFromFakeUG isMidi] && [aToFakeUG isPatchParameter] && ! midiConnected)
  if ([aFromFakeUG isMidi] && [aToFakeUG isPatchParameter])
    {
      id nr;
      if (! [theSP initMidi])
	{
	  return nil;
	}

      midi = [theSP midi];
      [midi setMergeInput:YES];
//    or [midi channelNoteSender:1] and no setMergeInput
      [[midi noteSender] connect:[[aToFakeUG patchParam] noteReceiver]];

      nr = [[theSP midiNoteOnHandler] noteReceiver];
      [[midi noteSender] disconnect:nr];
      [[midi noteSender] connect:nr];
      midiConnected = YES;
    }

  return self;
}

-(FakeUG *)getFromFakeUG
{
  return [fromNode superview];
}

-(int)getFromConnectionNumber
{
  return [fromNode getTag];
}

-getFromNode
{
  return fromNode;
}

-getToNodes
{
  return toNode;
}

-(BOOL)getXMemory
{
  return xmemory;
}

-(char *)getName
{
  return theName;
}

-getpp
{
  return pp;
}

- takeNameFromInspector:sender
{
  [self setName:(char *) [sender stringValue]];
  [ppNameField setStringValue: [sender stringValue]];
  [self setInspectorName: theName];
  return self;
}

-(char *)getTypeString
{
  if ([[fromNode superview] isData])
    if (xmemory)
      return "xData";
    else
      return "yData";
  if (xmemory)
    return "xPatch";
  else
    return "yPatch";
}

-(BOOL)isAFakeUG
{
  return NO;
}

-(BOOL)isAFakePatchPoint
{
  return YES;
}

-(BOOL)isPatchParameter
{
  return NO;
}

-(BOOL)isClavier
{
  return NO;
}

-(BOOL)isMidi
{
  return NO;
}

-allocatePPMK
{
  int i;
  id toFakeUG;
  id fromFakeUG;
  id midi;

  if (isAllocated)
    {
      return self;
    }

  /* Nasty hack for putting Claviers/Midi/PatchParameter back together again */
  fromFakeUG = [fromNode superview];
  if ( [fromFakeUG isClavier] || [fromFakeUG isMidi])
    {
      for (i=0; i<[toNode count]; i++)
	{
	  toFakeUG = [[toNode objectAt:i] superview];
	  if ([ toFakeUG isPatchParameter])
	    {
	      [[fromFakeUG clavier] connectNoteSenderTo: 
	       [[toFakeUG patchParam] noteReceiver]];
	    }
	  else
	    {
	      midi = [fromFakeUG midi];
	      [midi connectNoteSenderTo:
	       [[toFakeUG patchParam] noteReceiver]];
	    }
	}
      isAllocated = YES;
    }

  if (xmemory)
    {
      if ([[fromNode superview] isData])
	{
	  pp = [[superview getOrch] allocSynthData:MK_xData 
	      length:[[fromNode superview] getLength]];
	  isAllocated = YES;
	}
      else
	{
	  pp = [[superview getOrch] allocPatchpoint:MK_xPatch];
	  isAllocated = YES;
	}
    }
  else
    {
      if ([[fromNode superview] isData])
	{
	  if (!([[fromNode superview] isSineROM]))
	    {
	      pp = [[superview getOrch] allocSynthData:MK_yData 
		  length:[[fromNode superview] getLength]];
	      isAllocated = YES;
	    }
	}
      else
	{
	  pp = [[superview getOrch] allocPatchpoint:MK_yPatch];
	  isAllocated = YES;
	}
    }

  if (! pp & ![[fromNode superview] isSineROM])
    {
      NXRunAlertPanel(getAppName(), 
		      "Is DSP being used by another app? Could not allocate %s", 
		      NULL, NULL, NULL,
		      [self getName]);
      isAllocated = NO;
      return nil;
    }

  if ([[fromNode superview] hasConstant])
    {
      [pp setToConstant:	
       DSPDoubleToFix24(atod([[fromNode superview] getConstant]))];
    }

  return self;
}

-deallocPPMK
{
  [pp finish];
  [pp dealloc];
  pp = nil;
  isAllocated = NO;
  return self;
}

-makeConnectionsMK
{
  int i;
  if (!([[fromNode superview] isSineROM]))
    {
      [[[fromNode superview] getUG] 
     perform:sel_getUid([[fromNode superview]
		       getConnectionName:[fromNode getTag]]) with:pp];
      for (i=0; i<[toNode count]; i++)
	{
	  id node = [toNode objectAt:i];
	  [[[node superview] getUG] 
	 perform:sel_getUid([[node superview]
			   getConnectionName:[node getTag]])
	 with:pp];
	}
    }
  return self;
}

-eraseSelf
{
  NXRect fromFrame, toFrame;
  int i;
  id fromFakeUG;
  double width;

  fromFakeUG = [fromNode superview];

#ifdef DEBUG_ERASE
  printf("(FakePatchPoint %s -eraseSelf)\n", [self getName], [fromFakeUG getName]);
#endif DEBUG_ERASE

  for (i=0; i<[toNode count]; i++)
    {
      fromFrame = [fromNode frame];
      toFrame = [[toNode objectAt:i] frame];
      [[fromNode superview] convertRect:&fromFrame toView:self];
      [[[toNode objectAt:i] superview] convertRect:&toFrame toView:self];
      PSmoveto(fromFrame.origin.x+fromFrame.size.width/2,
	       fromFrame.origin.y+fromFrame.size.height/2);
      PSlineto(toFrame.origin.x+toFrame.size.width/2,
	       toFrame.origin.y+toFrame.size.height/2);
      PSsetgray(NX_LTGRAY);
      if ([fromFakeUG isClavier] || [fromFakeUG isMidi])
	{
	  width = 2*LINEWIDTH;
	}
      else
	{
	  width = LINEWIDTH;
	}

      PSsetlinewidth(width);
      PSstroke();
      NXPing();
    }

  return self;
}

-removeSelf
{
  int i;
  [self lockFocus];
  [self eraseSelf];
  [self unlockFocus];

  [self closeInspector];
#ifdef DEBUG_ERASE
  printf("(FakePatchPoint %s - removeSelf) Attempting to remove connection %d on fromfakeUG: %s\n",
	 [self getName], [fromNode getTag], [[fromNode superview] getName]);
#endif DEBUG_ERASE

/**** Old Way
  [[fromNode superview] remConnection:[fromNode getTag]];
*****************/
  [[fromNode superview] nilConnectionsWithId:self];

  for (i=0; i<[toNode count]; i++)
    {
#ifdef DEBUG_ERASE
      printf("(FakePatchPoint %s - removeSelf) Attempting to remove connection %d on fromfakeUG: %s\n",
	     [self getName], 
	     [[toNode objectAt:i] getTag], 
	     [[[toNode objectAt:i] superview] getName]);
#endif DEBUG_ERASE

/****  Old way
      [[[toNode objectAt:i] superview] remConnection:
       [[toNode objectAt:i] getTag]];
*****************/
      [[[toNode objectAt:i] superview] nilConnectionsWithId:self];
    }

#ifdef DEBUG_ERASE
      printf("(FakePatchPoint %s - removeSelf) Attempting to remove 0x%x from list in %s\n",
	     [self getName], 
             self,
	     [superview getName]);
#endif DEBUG_ERASE

  [superview remFakePatchPointFromList:self];
  return self;
}

-drawSelf:(const NXRect *)rects :(int)rectCount
{
  int i;
  double gray, width;
  int x1, y1, x2, y2;
  NXRect fromFrame, toFrame;
  id fromFakeUG, toFakeUG;

  fromFakeUG = [fromNode superview];

  for (i=0; i<[toNode count]; i++)
    {
      toFakeUG = [[toNode objectAt:i] superview];

      if ((![fromFakeUG getErasing]) && (![toFakeUG getErasing]))
	{

	  fromFrame = [fromNode frame];
	  toFrame = [[toNode objectAt:i] frame];
	  [fromFakeUG convertRect:&fromFrame toView:self];
	  [toFakeUG convertRect:&toFrame toView:self];
	  x1 = fromFrame.origin.x+fromFrame.size.width/2;
	  y1 = fromFrame.origin.y+fromFrame.size.height/2;
	  x2 = toFrame.origin.x+toFrame.size.width/2;
	  y2 = toFrame.origin.y+toFrame.size.height/2;
	  PSmoveto(x1, y1);
	  PSlineto(x2, y2);

	  if ([superview getSelected]==self)
	    {
	      gray = NX_WHITE;
	      width = LINEWIDTH;
	    }
	  else
	    {
	      if ( ([fromFakeUG isClavier] && [toFakeUG isPatchParameter]) || 
		  ([fromFakeUG isMidi] && [toFakeUG isPatchParameter]))
		{
		  gray = 0.25;
		  width = 2*LINEWIDTH;
		}
	      else
		{
		  if (xmemory)
		    {
		      gray = NX_DKGRAY;
		      width = LINEWIDTH;
		    }
		  else
		    {
		      gray = NX_BLACK;
		      width = LINEWIDTH;
		    }
		}
	    }

	  PSsetgray(gray);
	  PSsetlinewidth(width);
	  PSstroke();
	  NXPing();
	}
    }

  return self;
}


- write:(NXTypedStream *) stream
{
  char *aName;

  aName = theName;
  [super write:stream];
  if (inspector)
    {
      useInspectorRect = YES;
      [inspector getFrame:&inspectorRect];
    }
  else
    {
      useInspectorRect = NO;
      inspectorRect.origin.x = -1;
      inspectorRect.origin.y = -1;
      inspectorRect.size.width = -1;
      inspectorRect.size.height = -1;
    }

  NXWriteTypes(stream, "*", &aName);
  NXWriteTypes(stream, "@", &fromNode);
  NXWriteTypes(stream, "@", &toNode);
  NXWriteTypes(stream, "c", &xmemory);
  NXWriteTypes(stream, "i", &allocOrder);
  NXWriteTypes(stream, "d", &inspectorRect.origin.x);
  NXWriteTypes(stream, "d", &inspectorRect.origin.y);
  NXWriteTypes(stream, "d", &inspectorRect.size.width);
  NXWriteTypes(stream, "d", &inspectorRect.size.height);
  NXWriteTypes(stream, "c", &useInspectorRect);
  NXWriteTypes(stream, "c", &inspectorDisplayed);

  return self;
}

- read:(NXTypedStream *) stream
{
  int version;
  id aPP;
  char *old_name;

  isAllocated = NO;
  [super read:stream];
  allocOrder = 1;
  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;

  version = NXTypedStreamClassVersion(stream, "FakePatchPoint");
  switch (version)
    {
      case(3):
	{
	  NXReadTypes(stream, "*", &old_name);
	  NXReadTypes(stream, "@", &fromNode);
	  NXReadTypes(stream, "@", &toNode);
	  NXReadTypes(stream, "c", &xmemory);
	  NXReadTypes(stream, "i", &allocOrder);
	  NXReadTypes(stream, "d", &inspectorRect.origin.x);
	  NXReadTypes(stream, "d", &inspectorRect.origin.y);
	  NXReadTypes(stream, "d", &inspectorRect.size.width);
	  NXReadTypes(stream, "d", &inspectorRect.size.height);
	  NXReadTypes(stream, "c", &useInspectorRect);
	  NXReadTypes(stream, "c", &inspectorDisplayed);
	  inspectorRectJustLoaded = YES;
	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }

	  break;
	}
      case(2):
	{
	  NXReadTypes(stream, "*@@ci",
		      &old_name, &fromNode, &toNode, &xmemory, &allocOrder);
	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }

	  break;
	}
      default:
        {
	  /* Older versions */

	  /*ME* Observed malloc error on an initial attempt to load an old file */
	  NXReadTypes(stream, "*@@c@",
		      &old_name, &fromNode, &toNode, &xmemory, &aPP);
	  allocOrder = 1;
	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }

	}
    }

  return self;
}

- printSelf
{
  int i;

  printf("\n===============================================\n");
  printf("- (FakePatchPoint %s) %d toNodes\n", 
	 [self getName],
	 (int) [toNode count]);

  printf("    > fromNode: goes to tag %d on fakeUG %s\n", 
	 [fromNode getTag],
	 [[fromNode superview] getName]);
  
  for (i=0; i<[toNode count]; i++)
    {
      printf("    > toNode %d: goes to tag %d on fakeUG: %s\n",
	     i,
	     [[toNode objectAt:i] getTag],
	     [[[toNode objectAt:i] superview] getName]);
    }

  return self;
}

- displayIfDoubleClick
{
  clickCount++;
  if (clickCount >= 2)
    {
      clickCount = 0;
      [self displayInspector];
    }

  return self;
}

- highlightSelf
{
  if ( (![theSP windowMiniaturized]) && doHighlight)
    {
      [superview setSelected:self];
      [window removeFromEventMask:NX_LMOUSEDRAGGEDMASK];
      [window flushWindow];
      [superview display];
    }

  return self;
}


@end

