/*
 *    Copyright, 1991, The Regents of the University of
 *    California.  This software was produced under a U. S.
 *    Government contract (W-7405-ENG-36) by the Los Alamos
 *    National Laboratory, which is operated by the
 *    University of California for the U. S. Department of
 *    Energy.  The U. S. Government is licensed to use,
 *    reproduce, and distribute this software.  Permission
 *    is granted to the public to copy and use this software
 *    without charge, provided that this Notice and any statement
 *    of authorship are reproduced on all copies.  Neither the
 *    Government nor the University makes any warranty, express
 *    or implied, or assumes any liability or responsibility for
 *    the use of this software.
 */

/* 
 * ConnectorView.m
 * By Bill Edney, Los Alamos National Laboratory
 */

#import <math.h>
#import <objc/List.h>
#import <objc/Storage.h>
#import <stdlib.h>
#import<dpsclient/psops.h>
#import<dpsclient/wraps.h>
#import <appkit/Window.h>
#import <appkit/Panel.h>
#import <appkit/Application.h>
#import <appkit/publicWraps.h>
#import <appkit/nextstd.h>

#import "BlackenView.h"

#import "ConnectorView.h"

@implementation ConnectorView

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];

	connectionWidth = 2.0;

	return self;
}


- awake
{
	/* Allow our superclass to awake */
	[super awake];
	
	/* Create a list to hold our local list of rectangles */
	viewRectList = [[Storage alloc] initCount:0
				    elementSize:sizeof(ViewRectPair)
				    description:"{@ffff}"];

	/* THIS IS FOR USING THIS CODE IN THE IB PALETTE ONLY */
	/* Take this out for real applications where you are registering meaningful rects */
	[self registerRect:&bounds forView:self];

	return self;
}

/* Add the view-rect pair to our own, local, list */
/* NOTE: Rects MUST be registered in their own local view's coordinates */
- registerRect:(NXRect *)rect forView:view
{
	ViewRectPair    *newViewRect;
    
	newViewRect = (ViewRectPair *)malloc(sizeof(ViewRectPair));
	newViewRect->view = view;
	newViewRect->rect = *rect;
    
	[viewRectList addElement:newViewRect];
    
	return self;
}

- buildGlobalRectList
{
	ViewRectPair		*oldPair;
	ViewRectPair		*newPair;

	id	winList, theWindow;
	int	winNum;
	id	viewList, theView;
	int	viewNum;
	id	hitList;
	int	hitNum;

	int	i,j,k;

	i = j = k = 0;
	winNum = viewNum = hitNum = 0;

	winList = [NXApp windowList];
	winNum = [winList count];
	if (winNum > 0)
	{
		for (i = 0;i <= winNum-1;i++)
		{
			theWindow = [winList objectAt:i];
			viewList = [[theWindow contentView] subviews];
			viewNum = [viewList count];
			if (viewNum > 0)
			{
				for (j = 0;j <= viewNum-1;j++)
				{
					theView = [viewList objectAt:j];
					if ([theView respondsTo:@selector(viewRectList)])
					{
						hitList = [theView viewRectList];
						hitNum = [hitList count];
						if (hitNum > 0)
						{
							for (k = 0; k <= hitNum-1;k++)
							{
								oldPair = (ViewRectPair *)[hitList elementAt:k];
								newPair = (ViewRectPair *)malloc(sizeof(ViewRectPair));
								newPair->view = theView;
								newPair->rect = oldPair->rect;
    								[globRectList addElement:newPair];
							}
							oldPair = newPair = NULL;
						}
					}
				}
			}
		}
	}

	return self;	
}


- (ViewRectPair *)checkForAcceptRect:(NXPoint)thePoint
{
	int				numViewRects;
	ViewRectPair		*vrPair;
	int				i;
	NXPoint			globPoint;

	/* Get the number of elements in our globRectList */
	numViewRects = [globRectList count];

	globPoint = thePoint;
	for (i = 0; i<= numViewRects-1;i++)
	{
		/* Get the element and convert the global point into a local view one */
		vrPair = (ViewRectPair *)[globRectList elementAt:i];
		[[(vrPair->view) window] convertScreenToBase:&thePoint];
		[(vrPair->view) convertPoint:&thePoint fromView:nil];
		if (NXPointInRect(&thePoint, &(vrPair->rect)))
			return (vrPair);
		
		/* Reset the point to the global point for the next test */
		thePoint = globPoint;
	}
	
	return NULL;
}

- drawSelf:(NXRect *)r :(int)count
{
	PSsetgray(NX_WHITE);
	NXRectFill(&bounds);
	PSsetgray(NX_BLACK);
	NXFrameRect(&bounds);

	return self;
}

- (float)connectionWidth
{
	return connectionWidth;
}

- setConnectionWidth:(float)theWidth
{
	connectionWidth = theWidth;
	
	return self;
}

- (id)viewRectList
{
	return viewRectList;
}

- (BOOL)acceptsFirstMouse	/* We want events that caused our window to become key window */
{
	return YES;
}

- mouseDown:(NXEvent *)theEvent
{
	NXRect		tempContRect;
	id			vertView, horizView,mDownView;
	NXPoint		mouseDownPoint;
	float			mDownOffset = 0.0;

	/* Allocate and build the global rectangle list for all the hit rectangles
	    in all the views in all the windows of the application */
	globRectList = [[Storage alloc] initCount:10
				    elementSize:sizeof(ViewRectPair)
				    description:"{@ffff}"];
	[self buildGlobalRectList];

	/* Get the point at which the mouse went down */
	mouseDownPoint = theEvent->location;
	
	/* Convert the point from window's base coord system to screen coord system */
	[[self window] convertBaseToScreen:&mouseDownPoint];

	/* Check our GLOBAL rectangle list and set our startAcceptor */
	startAcceptor = [self checkForAcceptRect:mouseDownPoint];

	/* If there was a "hit rect" beneath our mouseDown point AND
	    the user was holding down the control key, go into our drag loop */
	if ((startAcceptor) && (theEvent->flags & NX_CONTROLMASK))
	{
		/* Highlight the rect we went down in */
		[self mouseWentDown:startAcceptor];

		/* Set up the vertical window */
		NXSetRect(&vertWinRect,mouseDownPoint.x,mouseDownPoint.y, 								connectionWidth,0.0);
		[Window getContentRect:&tempContRect
				forFrameRect:&vertWinRect
				style:NX_PLAINSTYLE];
		vertWindow = [[Window alloc] initContent:&tempContRect
							style:NX_PLAINSTYLE  /* We don't want a title bar */
							backing:NX_BUFFERED
							buttonMask:0
							defer:NO];
		vertView = [[BlackenView alloc] initFrame:&vertWinRect];
		[vertWindow setContentView:vertView];
		/* We don't have any overlapping subviews in the window, so we can use
		optimized drawing */
		[vertWindow useOptimizedDrawing:YES];

		/* Set up the horizontal window - same initialization as vertical */
		NXSetRect(&horizWinRect,mouseDownPoint.x,mouseDownPoint.y,
								0.0, connectionWidth);
		[Window getContentRect:&tempContRect
				forFrameRect:&horizWinRect
				style:NX_PLAINSTYLE];
		horizWindow = [[Window alloc] initContent:&tempContRect
							style:NX_PLAINSTYLE
							backing:NX_BUFFERED
							buttonMask:0
							defer:NO];
		horizView = [[BlackenView alloc] initFrame:&horizWinRect];
		[horizWindow setContentView:horizView];
		[horizWindow useOptimizedDrawing:YES];

		/* Calculate mDownOffset to handle centering the box around the point clicked */
		mDownOffset = connectionWidth * 3.0;

		/* Set up the box window */
		NXSetRect(&mDownRect,mouseDownPoint.x - connectionWidth,
								mouseDownPoint.y - connectionWidth,
								mDownOffset, mDownOffset);
		[Window getContentRect:&tempContRect
				forFrameRect:&mDownRect
				style:NX_PLAINSTYLE];
		mDownWindow = [[Window alloc] initContent:&mDownRect
							style:NX_PLAINSTYLE
							backing:NX_RETAINED
							buttonMask:0
							defer:NO];
		mDownView = [[BlackenView alloc] initFrame:&mDownRect];
		[mDownWindow setContentView:mDownView];

		/* Now, order the windows in the front of every other window */
		[vertWindow orderFront:NULL];
		[horizWindow orderFront:NULL];
		[mDownWindow orderFront:NULL];
		
		/* Display them */
		[vertWindow display];
		[horizWindow display];
		[mDownWindow display];
	
		/* Start dragging 'em around! */
		[self dragConnectionUsingStartPoint:mouseDownPoint];
	}
	else
		/* Otherwise, pass on the event to the next responder */
		[nextResponder mouseDown:theEvent];

	return self;
}

- dragConnectionUsingStartPoint:(NXPoint)startPoint
{
	int			mask;
	NXEvent		*event;
	BOOL		moveHoriz = NO,moveVert = NO;
	NXPoint		currentPoint;
	ViewRectPair	*newAcceptor = NULL;
	ViewRectPair	*oldAcceptor = NULL;

	/* get mouse-dragged events also (and save original mask for later) */
	mask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];

	/* Make the current point equal to the starting point */
	currentPoint = startPoint;

	do	/* While we're dragging the mouse */
	{
		/* Check to see if the mouse point is in the "start box" */
		if (NXMouseInRect(&currentPoint,&mDownRect,NO))
		{
			for (;;)	/* Cycle through until the user comes out */
			{
				if (!NXMouseInRect(&currentPoint,&mDownRect,NO))
				{
					if ((currentPoint.y > (mDownRect.size.height + mDownRect.origin.y))
						|| (currentPoint.y < mDownRect.origin.y))
					{
						/* She came out vertically, move the horizontal */
							moveVert = NO;
							moveHoriz = YES;
							break;	/* Break out of this bloody loop */
					}
					else 	if ((currentPoint.x > (mDownRect.size.width + mDownRect.origin.x)) 						|| (currentPoint.x < mDownRect.origin.x))
					{
						/* She came out horizontally, move the vertical */
						moveVert = YES;
						moveHoriz = NO;
						break;	/* Break out of this bloody loop */
					}
				}
				
				/* Get the next event */
				event = [NXApp
						getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
				currentPoint = event->location;
				[[self window] convertBaseToScreen:&currentPoint];
			}
		}

		if (moveVert == YES)	/* We're moving the vertical, calc both windows origins */
		{
			vertWinRect.origin.x = currentPoint.x;
			horizWinRect.origin.y = startPoint.y;
		}
	
		if (moveHoriz == YES)	/* We're moving the horizontal, calc both windows origins */
		{
			vertWinRect.origin.x = startPoint.x;
			horizWinRect.origin.y = currentPoint.y;
		}
	
		/* Now, calculate the other origins and the extents of the windows */
		if (currentPoint.y > startPoint.y)
		{
			vertWinRect.origin.y = startPoint.y;
			vertWinRect.size.height = currentPoint.y - startPoint.y + connectionWidth;
		}
		else if (currentPoint.y < startPoint.y) 
		{
			vertWinRect.origin.y = currentPoint.y;
			vertWinRect.size.height = startPoint.y - currentPoint.y;
		}
			
		if (currentPoint.x > startPoint.x)
		{
			horizWinRect.origin.x = startPoint.x;
			horizWinRect.size.width = currentPoint.x - startPoint.x + connectionWidth;
		}
		else if (currentPoint.x < startPoint.x)
		{
			horizWinRect.origin.x = currentPoint.x;
			horizWinRect.size.width = startPoint.x - currentPoint.x;
		}

		[vertWindow disableFlushWindow];	/* Does this stuff really improve performance? */
		[horizWindow disableFlushWindow];	/* I don't know */

		/* Place and display our windows!!! */
 		[vertWindow placeWindowAndDisplay:&vertWinRect];
		[horizWindow placeWindowAndDisplay:&horizWinRect];
		
		[vertWindow reenableFlushWindow];	/* More supposedly performance improving stuff */
		[horizWindow reenableFlushWindow];	/* Hard to tell with an '040 board */

		[vertWindow flushWindow];
		[horizWindow flushWindow];

		/* See if the mouse point is in another acceptor */
		newAcceptor = [self checkForAcceptRect:currentPoint];

		/* If there isn't a newAcceptor, but there was an old one, tell the old one
		    we left it */
		if ((newAcceptor == NULL) && (oldAcceptor))
				[self mouseWentOut:oldAcceptor];
		
		if (newAcceptor)	/* If there is a newAcceptor, tell it we came into it */
			[self mouseCameIn:newAcceptor];

		oldAcceptor = newAcceptor;		/* Make the old acceptor equal to the new one */

		/* Get the next event to process */
		event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
		currentPoint = event->location;
		[[self window] convertBaseToScreen:&currentPoint];
	}
	while (event->type == NX_MOUSEDRAGGED);
    
	/* No longer need mouse dragged events, so reset the event mask */
	[window setEventMask:mask];
    
	/* If oldAcceptor exists, tell ourself that the user let go of the mouse */
	if (oldAcceptor)
	{
		/* Tell ourselves that the mouse was dropped in an acceptor */
		[self mouseDropped:startAcceptor :oldAcceptor];
		
		/* Hide and free the connection windows */
		[[vertWindow orderOut:self] free];
		[[horizWindow orderOut:self] free];
		[[mDownWindow orderOut:self] free];
		
		[oldAcceptor->view display];		/* Call the acceptor's "drawSelf" to paint over its boxes */

		/* Free our GLOBAL rectangle list */
		[globRectList free];
	}
	else
	{
		/* Hide and free the connection windows */
		[[vertWindow orderOut:self] free];
		[[horizWindow orderOut:self] free];
		[[mDownWindow orderOut:self] free];
		
		/* Free our GLOBAL rectangle list */
		[globRectList free];
	}
  
	[self display];				/* Call our "drawSelf" to paint over our boxes */

	startAcceptor = NULL;		/* We're done with the startAcceptor, so set it to null */

	return self;
}

- mouseWentDown:(ViewRectPair *)acceptor
{
	/* The mouse went down, highlight the starting acceptor */
	[acceptor->view lockFocus];
	PSsetgray(NX_BLACK);
	NXFrameRectWithWidth(&(acceptor->rect),connectionWidth);
	[[acceptor->view window] flushWindow];
	[acceptor->view unlockFocus];
	
	return self;
}

- mouseCameIn:(ViewRectPair *)acceptor
{
	/* We don't the starting ViewRectPair to highlight */
	if ((acceptor == startAcceptor) &&
	(NXEqualRect(&(acceptor->rect),&(startAcceptor->rect))))
		return self;

	/* Lock focus on the view and highlight it */
	[acceptor->view lockFocus];
	PSsetgray(NX_BLACK);
	NXFrameRectWithWidth(&(acceptor->rect),connectionWidth);
	[[acceptor->view window] flushWindow];
	[acceptor->view unlockFocus];
	
	return self;
}

- mouseWentOut:(ViewRectPair *)acceptor
{
	/* We don't the starting ViewRectPair to unhighlight */
	if ((acceptor == startAcceptor) &&
	(NXEqualRect(&(acceptor->rect),&(startAcceptor->rect))))
		return self;

	/* Unhighlight the view */
	[acceptor->view display];
	[[acceptor->view window] flushWindow];

	return self;
 }

- mouseDropped:(ViewRectPair *)fromAcceptor :(ViewRectPair *)toAcceptor
{
	int	result = 0;

	/* We don't the starting ViewRectPair to get its own connection */
	if ((fromAcceptor == toAcceptor) &&
	(NXEqualRect(&(fromAcceptor->rect),&(toAcceptor->rect))))
		return self;

	/* Bring up an alert panel */
	result = NXRunAlertPanel(NULL,"You just connected something","Yi doggie!",NULL,NULL);

	return 0;
}

- (const char *)inspectorName
{
	/* Return the name for the IB inspector */
	return "ConnectorViewInspector";
}

/* We only read and write "connectionWidth" here. You might want to do others */
- read:(NXTypedStream*)stream
{
	[super read:stream];
	NXReadTypes(stream,"f", &connectionWidth);

	return self;
}

- write:(NXTypedStream*)stream
{
	[super write:stream];
	NXWriteTypes(stream,"f", &connectionWidth);

	return self;
}

@end
