#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <appkit/timer.h>
#import <appkit/Cell.h>
#import <appkit/Window.h>
#import <appkit/Application.h>
#import <appkit/publicWraps.h>

#import "OrderMatrix.h"


@implementation OrderMatrix


#define startTimer(timer)\
		if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);

#define stopTimer(timer) if (timer) 	{ \
		NXEndTimer(timer); \
		timer = NULL; \
		}

#define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK


/* instance methods */

- (BOOL) acceptsFirstResponder
{
	return YES;
}

- free
{
    [matrixCache free];
    [cellCache free];
    
    return [super free];
}

- delete:sender
{
	int    row = [self selectedRow];
	if (row >= 0)
	{
		[self removeRowAt:row andFree:YES];
		[self sizeToCells];
		[[self superview] display];
		if (![textDelegate textWillChange:self])
			[textDelegate textDidChange:self];
	}
	else
		NXBeep();
	return self;
}

- keyDown:(NXEvent *)theEvent
{
	if (theEvent->data.key.charCode == 127)
	{
		int    row = [self selectedRow];
		if (row >= 0)
		{
			[self removeRowAt:row andFree:YES];
			[self sizeToCells];
			[[self superview] display];
			if (![textDelegate textWillChange:self])
				[textDelegate textDidChange:self];
		}
		else
			NXBeep();
		return self;
	}
	else
		return [super keyDown:theEvent];
}

- mouseDown:(NXEvent *)theEvent
{
	NXPoint   mouseDownLocation, mouseUpLocation, mouseLocation;
	int       eventMask, row, column, newRow;
	NXRect    visibleRect, cellCacheBounds, cellFrame;
	id        matrixCacheContentView, cellCacheContentView;
	float     dy;
	NXEvent  *event, peek;
	NXTrackingTimer *timer = NULL;
	BOOL      scrolled = NO;

	theEvent->flags = 0;
/* peek on the next event. If it is a mouse up, let super do the work */  
	eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	if ([NXApp peekNextEvent:MOVE_MASK
			into:&peek waitFor:0.25 threshold:NX_BASETHRESHOLD])
	{
		if (peek.type == NX_MOUSEUP)
		{
			[window setEventMask:eventMask];
			return [super mouseDown:theEvent];
		}
	}
	
/* prepare the cell and matrix cache windows */
	[window disableFlushWindow];
	[self setupCacheWindows];

/* find the cell that got clicked on and select it */
	mouseDownLocation = theEvent->location;
	[self convertPoint:&mouseDownLocation fromView:nil];
	[self getRow:&row andCol:&column forPoint:&mouseDownLocation];
	activeCell = [self cellAt:row :column];
	[self selectCell:activeCell];
	[self getCellFrame:&cellFrame at:row :column];
    
/* do whatever's required for a single-click */
	[self sendAction];
    
/* draw a "well" in place of the selected cell (see drawSelf::) */
	[self display:&cellFrame :1];
    
/* copy what's currently visible into the matrix cache */
	matrixCacheContentView = [matrixCache contentView];
	[matrixCacheContentView lockFocus];
	[self getVisibleRect:&visibleRect];
	[self convertRect:&visibleRect toView:nil];
	PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
			NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
	[window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY);
	[matrixCacheContentView unlockFocus];

/* image the cell into its cache */
	cellCacheContentView = [cellCache contentView];
	[cellCacheContentView lockFocus];
	[cellCacheContentView getBounds:&cellCacheBounds];
	NXSetColor(NX_COLORLTGRAY);
	NXRectFill(&cellCacheBounds);
	[activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView];
	[cellCacheContentView unlockFocus];

/* save the mouse's location relative to the cell's origin */
	dy = mouseDownLocation.y - cellFrame.origin.y;
	[window reenableFlushWindow];
	
/* from now on we'll be drawing into ourself */
	[self lockFocus];
	
	event = theEvent;
	while (event->type != NX_MOUSEUP)
	{
/* erase the active cell using the image in the matrix cache */
		[self getVisibleRect:&visibleRect];
		PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
			NX_Y(&cellFrame) + NX_Y(&visibleRect) -
			NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
			NX_HEIGHT(&cellFrame), [matrixCache gState],
			NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
			NX_COPY);
	
/* move the active cell */
		mouseLocation = event->location;
		[self convertPoint:&mouseLocation fromView:nil];
		cellFrame.origin.y = mouseLocation.y - dy;
	
/* constrain the cell's location to our bounds */
		if (NX_Y(&cellFrame) < NX_X(&bounds) )
		{
			cellFrame.origin.y = NX_X(&bounds);
		}
		else
			if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds))
			{
				cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
			}

/*
 * make sure the cell will be entirely visible in its new location (if
 * we're in a scrollView, it may not be)
 */
		if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll)
		{	
/*
 * the cell won't be entirely visible, so scroll, dood, scroll, but
 * don't display on-screen yet
 */
			[window disableFlushWindow];
			[self scrollRectToVisible:&cellFrame];
			[window reenableFlushWindow];
	    
/* copy the new image to the matrix cache */
	    [matrixCacheContentView lockFocus];
	    [self getVisibleRect:&visibleRect];
	    [self convertRectFromSuperview:&visibleRect];
	    [self convertRect:&visibleRect toView:nil];
	    PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
			NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
			[window gState], 0.0, NX_HEIGHT(&visibleRect),
			NX_COPY);
	    [matrixCacheContentView unlockFocus];
	    
/*
 * note that we scrolled and start generating timer events for
 * autoscrolling
 */
	    scrolled = YES;
	    startTimer(timer);
		}
		else
		{
/* no scrolling, so stop any timer */
			stopTimer(timer);
		}
      
/* composite the active cell's image on top of ourself */
		PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
			[cellCache gState], NX_X(&cellFrame),
			NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
	
/* now show what we've done */
		[window flushWindow];
	
/*
 * if we autoscrolled, flush any lingering window server events to make
 * the scrolling smooth
 */
		if (scrolled)
		{
			NXPing();
			scrolled = NO;
		}
	
/* save the current mouse location, just in case we need it again */
		mouseLocation = event->location;
	
		if (![NXApp peekNextEvent:MOVE_MASK into:&peek])
		{
			event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
		}
		else
		{
			event = [NXApp getNextEvent:MOVE_MASK];
		}
	
/* if a timer event, mouse location isn't valid, so we'll set it */
		if (event->type == NX_TIMER)
		{
			event->location = mouseLocation;
		}
	}
    
/* mouseUp, so stop any timer and unlock focus */
	stopTimer(timer);
	[self unlockFocus];
    
/* find the cell under the mouse's location */
	mouseUpLocation = event->location;
	[self convertPoint:&mouseUpLocation fromView:nil];
	if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation])
	{
/* mouse is out of bounds, so find the cell the active cell covers */
		[self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)];
	}
    
/* we need to shuffle cells if the active cell's going to a new location */
	if (newRow != row)
	{
		[self setAutodisplay:NO];
		if (newRow > row)
		{
			if (selectedRow <= newRow)
				selectedRow--;
	
			while (row++ < newRow)
			{
				cell = [self cellAt:row :0];
				[self putCell:cell at:(row - 1) :0];
			}
	    [self putCell:activeCell at:newRow :0];
		}
		else
			if (newRow < row)
			{
	    	if (selectedRow >= newRow)
					selectedRow++;
				while (row-- > newRow)
				{
					cell = [self cellAt:row :0];
					[self putCell:cell at:(row + 1) :0];
				}
				[self putCell:activeCell at:newRow :0];
			}
      
		if ([activeCell state])
		{
			selectedRow = newRow;
		}
      
/* make sure the active cell's visible if we're autoscrolling */
		if (mFlags.autoscroll)
		{
			[self scrollCellToVisible:newRow :0];
		}
				
		activeCell = 0;
		[[self sizeToCells] setAutodisplay:YES];
	}
	else
	{
		activeCell = 0;
	}
    
	[self display];
	[window setEventMask:eventMask];
	if (![textDelegate textWillChange:self])
		[textDelegate textDidChange:self];
	return self;
}

- drawSelf:(NXRect *)rects :(int)count
{
	int		row, col;
	NXRect	cellBorder;
			   
	[super drawSelf:rects :count];
    
	if (activeCell)
	{
		[self getRow:&row andCol:&col ofCell:activeCell];
		[self getCellFrame:&cellBorder at:row :col];
      
		if (NXIntersectsRect(&cellBorder, &(rects[0])))
		{
	    PSsetgray(0.5);
	    NXRectFill(&cellBorder);
		}
	}
	return self;
}

- setupCacheWindows
{
	NXRect	visibleRect;

/* create the matrix cache window */
	[self getVisibleRect:&visibleRect];
	matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)];
    
/* create the cell cache window */
	cellCache = [self sizeCacheWindow:cellCache to:&cellSize];
	return self;
}

- sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
{
	NXRect	cacheFrame;
    
	if (!cacheWindow)
	{
		cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
		cacheFrame.size = *windowSize;
		cacheWindow = [[[Window alloc] initContent:&cacheFrame
			style:NX_PLAINSTYLE
			backing:NX_RETAINED
			buttonMask:0
			defer:NO] reenableDisplay];
		[[cacheWindow contentView] setFlipped:YES];
	}
	else
	{
		[cacheWindow getFrame:&cacheFrame];
		if (cacheFrame.size.width != windowSize->width ||
				cacheFrame.size.height != windowSize->height)
		{
			[cacheWindow sizeWindow:windowSize->width :windowSize->height];
		}
	}
	return cacheWindow;
}

@end