#import "Fiend.h"
#import "Dock.h"
#import "IconDragView.h"
#import "ShelfView.h"
#import "DockMgrView.h"
#import "Controller.h"
#import "FiendWraps.h"

#import <appkit/appkit.h>
#import <mach/mach.h>
#import <bsd/sys/file.h>


int mouseMoved(NXPoint *o, int n, int mask);

typedef enum { Unknown,
			   SameDeviceOperation,
	           WriteNotAllowed,
	           CrossDeviceOperation,
	           ApplicationDirectory } DragStatus;


@interface Application(DavesMethod)
- (int)contextNum;
@end

@implementation Application(DavesMethod)
- (int)contextNum
{
	return contextNum;
}
@end

@implementation IconDragView : IconView

- initFrame:(const NXRect *) newFrame
		image:anImage
		data:(const void *) someData
		andLength:(unsigned int) newLength
		useSize:(BOOL)sizeValid onDock:(BOOL)dockFlag
{
    const char *const	types[1] = {NXFilenamePboardType};

    [self registerForDraggedTypes:types count:1];
    [super initFrame:newFrame image:anImage data:someData andLength:newLength
	 useSize:sizeValid onDock:dockFlag];

    altImage = nil;
    selected = NO;
	dockDrag = NO;
	hideIcons = !strcmp(NXGetDefaultValue([NXApp appName], HIDE_ICONS), "YES");

    return self;
}


- free
{
    [altImage free];
    return [super free];
}

- sizeTo:(NXCoord) width :(NXCoord) height
{
	NXSize	aSize;

    [super sizeTo:width :height];
	if (altImage)	{
		[image getSize:&aSize];
		[altImage setSize:&aSize];
	}
	return self;
}

- drawSelf:(const NXRect *) rects :(int) rectCount
{
    id	tempImage = image;

	if (altImage)
		image = altImage;
    [super drawSelf:rects :rectCount];
    image = tempImage;

    return self;
}


- image
{
    return image;
}


- getData:(void **) aPtr andLength:(unsigned int *) aLength
{
    *aPtr = data;
    *aLength = length;
    return self;
}

- handleMouseMoved:(NXEvent *)saveEvent imagePt:(NXPoint *)imgPt offsetPt:(NXPoint *)offPt
{
	NXPoint		imageLoc;
	NXImage		*dragImage;
	NXPoint		mousePoint = saveEvent->location;
	Pasteboard 	*pboard = [Pasteboard newName:NXDragPboard];

	if (onDock)
		[[NXApp delegate] deselectShelf];
	else
		[[NXApp delegate] deselectDock];

	if (!onDock)	{
		[superview setDragView:self onEvent:saveEvent withOffset:offPt
		            atLocation:imgPt];
	}
	else	{
		dragImage = [[NXImage alloc] initSize:&frame.size];

		[pboard declareTypes:&NXFilenamePboardType num:1 owner:nil];
		[pboard writeType:NXFilenamePboardType data:data length:strlen(data)];

		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		[dragImage lockFocus];
		PScomposite(0.0, 0.0, NX_WIDTH(&frame), NX_HEIGHT(&frame),
					[window gState], 0.0, 0.0, NX_COPY);
		[dragImage unlockFocus];

		[dockMgrView setDragView:self andOffset:&mousePoint];
		[[self setGhost:YES] display];
		NXPing();

		dockDrag = YES;
		[self dragImage:dragImage at:&frame.origin offset:offPt
		          event:saveEvent pasteboard:pboard source:dockMgrView slideBack:NO];
		dockDrag = NO;

		[[self setGhost:NO] display];
		NXPing();
		[dragImage free];
	}
	return self;
}

- (BOOL)handleSingleClick:(NXEvent *)e
{
	NXPoint		imageLoc;
	NXPoint		upperRight;
	BOOL		alt = (e->flags & NX_ALTERNATEMASK) ? YES : NO;
    BOOL		shift = (e->flags & NX_SHIFTMASK) ? YES : NO;
	BOOL		control = (e->flags & NX_CONTROLMASK) ? YES : NO;
	BOOL		command = (e->flags & NX_COMMANDMASK) ? YES : NO;
	BOOL		delivered = NO;
	const char	*path = (const char *)data;

	if (onDock)
		[[NXApp delegate] deselectShelf];
	else
		[[NXApp delegate] deselectDock];

	if (!command && !shift && !alt && !control)	{
		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		[self convertPoint:&imageLoc toView:nil];
		[window convertBaseToScreen:&imageLoc];

		upperRight.x = screenSize.width - 32;
		upperRight.y = screenSize.height - 32;

		if (onDock)
			[[dockMgrView currentDock] select:NO all:self];
		else
			[superview deselectAll:self];

		[self setState:YES];
		[[Application workspace] slideImage:image from:&imageLoc to:&upperRight];
		delivered = [[Application workspace] selectFile:path inFileViewerRootedAt:""];
		[self setState:NO];
	}
	else if (alt && !onDock)	{
		[superview perform:@selector(deleteView:)
	                  with:self afterDelay:0.0 cancelPrevious:YES];
		[superview perform:@selector(writeShelf)
	                  with:nil afterDelay:0.0 cancelPrevious:YES];
		delivered = YES;
	}
	else if (control && onDock && isApp)	{
		autoLaunch = !autoLaunch;
		[self display];
		delivered = YES;
	}
	else if (alt && onDock)	{
		[dockMgrView perform:@selector(deleteView:)
	                    with:self afterDelay:0.0 cancelPrevious:YES];
		delivered = YES;
	}
	else if (shift)	{
		[NXApp unhide:self];
		[self setState:!selected];
		delivered = YES;
	}
	else	{
		if (onDock)
			[[dockMgrView currentDock] select:NO all:self];
		else
			[superview deselectAll:self];
		delivered = YES;
	}

	return delivered;
}

- hideSomeApps
{
	PSWHideOtherApps();
	return self;
}

- (BOOL)handleDoubleClick:(NXEvent *)e
{
	NXPoint		imageLoc;
	NXPoint		upperRight;
	BOOL		delivered = NO;
	BOOL		alt = (e->flags & NX_ALTERNATEMASK) ? YES : NO;
	BOOL		cmd = (e->flags & NX_COMMANDMASK) ? YES : NO;
	const char	*path = (const char *)data;

	if (!isApp)	{
		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		[self convertPoint:&imageLoc toView:nil];
		[window convertBaseToScreen:&imageLoc];

		upperRight.x = screenSize.width - 32;
		upperRight.y = screenSize.height - 32;

		[[Application workspace] slideImage:image from:&imageLoc to:&upperRight];
		delivered = [[Application workspace] openFile:path];
	}
	else if (cmd && isLaunched)	{
		[self perform:@selector(hideSomeApps) with:nil afterDelay:1.0
		 cancelPrevious:YES];
		delivered = [[Application workspace] launchApplication:path];
	}
	else if (!alt)	{
		if (!isLaunched)	{
			[self setGhost:YES];
			[self display];
			NXPing();
			isLaunched = [[Application workspace] launchApplication:path
						  showTile:(!onDock || !hideIcons)
						  autolaunch:NO];
		}
		delivered = [[Application workspace] launchApplication:path];
	}
	else if (!onDock && alt)
		delivered = [[Application workspace] openFile:path];

	return delivered;
}

- mouseDown:(NXEvent *) e
{
	int			savedMask;
    NXPoint		mousePoint;
	NXPoint		offset;
	NXEvent		newEvent;
    NXEvent		savedEvent;
	BOOL		delivered = NO;
    int			mask = NX_LMOUSEDRAGGEDMASK|NX_LMOUSEUPMASK;

    savedEvent = *e;
    mousePoint = e->location;
    savedMask = [window addToEventMask:mask];

    if (mouseMoved(&mousePoint, 2, mask)) {
		offset.x = mousePoint.x - e->location.x;
		offset.y = mousePoint.y - e->location.y;
		[self handleMouseMoved:&savedEvent imagePt:&imagePoint offsetPt:&offset];
    }
    else	{
		if (![NXApp peekNextEvent:NX_LMOUSEDOWNMASK into:&newEvent
			              waitFor:0.2 threshold:NX_MODALRESPTHRESHOLD])	{
			delivered = [self handleSingleClick:&savedEvent];
		}
		else	{
			(void)[NXApp getNextEvent:NX_LMOUSEDOWNMASK];
			delivered = [self handleDoubleClick:&savedEvent];
		}
		if (!delivered)
			NXBeep();
    }

	[window setEventMask:savedMask];
    return self;
}


- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
	return NX_DragOperationAll;
}

- (BOOL) acceptsFirstMouse
{
	return YES;
}


- (BOOL) acceptsFirstResponder
{
	return NO;
}


#define OPEN_DIR_ICON_FILE      ".opendir.tiff"
#define	DEFAULT_OPEN_DIR_FILE	"/usr/lib/NextStep/Workspace.app/WM.app/openFolder.tiff"
#define OPEN_HIGHLIGHT		"OpenHighlight.tiff"

- getOpenImageForDirectory:(const char *) dirname
{
    struct stat	st;
    char	buf[MAXPATHLEN];
    id		newImage = nil;

    /*
     *  See if there's a .opendir.tiff
     */
    strncpy(buf, dirname, sizeof(buf));
    strncat(buf, "/", sizeof(buf));
    strncat(buf, OPEN_DIR_ICON_FILE, sizeof(buf));
    if (access(buf, R_OK) == 0) {
		newImage = [[NXImage allocFromZone:[self zone]] initFromFile:buf];
		if (newImage)
			return newImage;
    }

    /*
     *  Before we use the default image, see if this thing is the
     *  root of a mounted file system, and use the appropriate icon
     *  if it is.
     */
    if (stat(dirname, &st) >= 0 && st.st_ino == 2) {
        NXSize size;
		[image getSize:&size];
    	newImage = [[NXImage allocFromZone:[self zone]] initSize:&size];
		if ([newImage lockFocus]) {
			NXPoint	zeroPoint = {0,0};
			id		highlight = [NXImage allocFromZone:[self zone]];
			char        buf[MAXPATHLEN];
			NXBundle   *bundle;

			bundle = [NXBundle bundleForClass:[self class]];
			if ([bundle getPath:buf forResource:OPEN_HIGHLIGHT ofType:"tiff"]) {
				[highlight initFromFile:buf];
				[image composite:NX_SOVER toPoint:&zeroPoint];
				[highlight composite:NX_SOVER toPoint:&zeroPoint];
				[newImage unlockFocus];
				return newImage;
			}
		}
    }

    if (access(DEFAULT_OPEN_DIR_FILE, R_OK) == 0)
		newImage = [[NXImage allocFromZone:[self zone]] initFromFile:DEFAULT_OPEN_DIR_FILE];

    if (newImage != nil)
		return newImage;
    else
		return image;
}


/*
 *  Figure out the various parameters of the drag.
 */
- (DragStatus) dragStatusForPath:(char *)dragPath
{
    char		*destinationPath;
    unsigned int	junk;
    struct stat		st;

    [self getData:(void **) &destinationPath andLength:&junk];

	if (!strcmp(destinationPath, dragPath))
		return WriteNotAllowed;

    if (!strcmp(&destinationPath[strlen(destinationPath)-4], ".app"))	{
		dragDestType = NXApplicationFileType;
		return ApplicationDirectory;
	}

    /*
     *  See if we should be a dragging destination.  We need some way to
     *  make this extendible.  One way would be to associate a remote
     *  object with the node, and ask it what it means to drag the
     *  incoming object over the one on the screen...
     *
     *  For now, interpret the data as a string.  Stat it to see if it's
     *  a directory, and if it is, say yes subject to access constraints.
     */
    if (stat(destinationPath, &st) < 0 || (st.st_mode & S_IFMT) != S_IFDIR)
		return WriteNotAllowed;
    if (access(destinationPath, W_OK|X_OK) < 0)
		return WriteNotAllowed;

    /*
     *  Cool!  We're a directory.  Check the directory from which we're
     *  dragging, and see it's the same as this one, or if it's contained
     *  in this one.
     */
	dragDestType = NXDirectoryFileType;
    if (stat(destinationPath, &st) < 0)
		return Unknown;
    else {
		dev_t	destDev = st.st_dev;
		ino_t	destIno = st.st_ino;

		if (lstat(dragPath, &st) >= 0 &&
			st.st_dev == destDev && destIno == st.st_ino)
			return WriteNotAllowed;
    }

    /*
     *  Handle the case where the dragging destination and the dragging
     *  source are on different file systems.  In this case, we want to
     *  copy by default, not move.
     */
    if (stat(destinationPath, &st) >= 0) {
		dev_t	destinationDevice = st.st_dev;
		if (lstat(dragPath, &st) >= 0)
			if (st.st_dev != destinationDevice)
				return CrossDeviceOperation;
			else
				return SameDeviceOperation;
    }

    return Unknown;
}


/*
 *  Figure out what the drag operation should be for dragging a view onto
 *  another view.
 */
- (NXDragOperation) dragOperationForContext:(id <NXDraggingInfo>) dragInfo
{
    unsigned int	junk;
    char			*dragPath;
	char			*nextPath;
    BOOL			first = YES;
    NXDragOperation	op = NX_DragOperationNone;
    id				pb = [Pasteboard newName:NXDragPboard];
    NXDragOperation	maskOp = [dragInfo draggingSourceOperationMask];

    [pb readType:NXFilenamePboardType data:&dragPath length:&junk];

    /*
     *  We might have been asked to drag multiple files.  Loop over all of
     *  them to see if they all have the same drag op.  Fail if they don't!
     */
    nextPath = dragPath;
    do {
    	char		path[MAXPATHLEN];
		NXDragOperation	thisOp = maskOp;

		strncpy(path, nextPath, MAXPATHLEN);
		if (index(path, '\t'))
			*index(path, '\t') = '\0';

		switch ([self dragStatusForPath:path]) {
		  case WriteNotAllowed:
		  case Unknown:
			thisOp = NX_DragOperationNone;
			break;

		  case ApplicationDirectory:
			if (maskOp == NX_DragOperationGeneric)
				thisOp = NX_DragOperationGeneric;
			else
				thisOp = NX_DragOperationNone;
			break;

		  case CrossDeviceOperation:
			if (maskOp & NX_DragOperationCopy) {
				thisOp = NX_DragOperationCopy;
				break;
			}
			/* fall through */
		  case SameDeviceOperation:
			if (maskOp & NX_DragOperationGeneric) {
				thisOp = NX_DragOperationGeneric;
				break;
			}
			/* fall through */
			default:
			break;
		}

		/*
		 *  Bail out of this operation isn't the same as the last one!
		 */
		if (first) {
			op = thisOp;
			first = NO;
		}
		else	{
			if (op != thisOp)
				return NX_DragOperationNone;
		}

		/*
		 *  Advance to next file
		 */
		nextPath = index(nextPath, '\t');
		if (nextPath == NULL)
			break;
		else
			nextPath++;

    } while (1);

    return op;
}


/*
 *  Be a dragging destination...
 */
- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
    lastDragOp = [self dragOperationForContext:sender];
	if (lastDragOp == NX_DragOperationPrivate || lastDragOp == NX_DragOperationNone)
		return NX_DragOperationNone;
    else if (dragDestType == NXDirectoryFileType) {
    	NXSize	imageSize;

		altImage = [self getOpenImageForDirectory:data];

		[image getSize:&imageSize];
		[altImage setScalable:YES];
		[altImage setSize:&imageSize];

		[self display];
    }

    return lastDragOp;
}


- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
    lastDragOp = [self dragOperationForContext:sender];
    return lastDragOp;
}


- draggingExited:(id <NXDraggingInfo>)sender
{
    /*
     *  Reset image to original image.
     */
    [altImage free];
    altImage = nil;
    [self display];
    [window flushWindow];
    return self;
}


/*
 *  Eat the result...
 */
- (BOOL) prepareForDragOperation:sender
{
    return YES;
}

- (BOOL) performDragOperation:(id <NXDraggingInfo>)sender
{
    return YES;
}


- concludeDragOperation:(id <NXDraggingInfo>)sender
{
	char			*path;
    char			*pbData;
	char			*dragPath;
    const char		*wsmOp;
    unsigned int	pbLength;
    id				workspace = [Application workspace];
    id				pb = [Pasteboard newName:NXDragPboard];

	[pb readType:NXFilenamePboardType data:&pbData length:&pbLength];
	dragPath = malloc(pbLength);
	bcopy(pbData, dragPath, pbLength);

	if (dragDestType == NXDirectoryFileType)	{
		switch (lastDragOp) {
		  case NX_DragOperationGeneric:
			wsmOp = WSM_MOVE_OPERATION;
			break;
		  case NX_DragOperationLink:
			wsmOp = WSM_LINK_OPERATION;
			break;
		  case NX_DragOperationCopy:
			wsmOp = WSM_COPY_OPERATION;
			break;
		  default:
			return NO;
		}
		[workspace performFileOperation:wsmOp source:"/" destination:data
		 files:pbData options:NULL];
	}
	else if (dragDestType == NXApplicationFileType && lastDragOp == NX_DragOperationGeneric)	{
		if (!isLaunched)	{
			[[self setGhost:YES] display];
			NXPing();
			[workspace launchApplication:data showTile:(!onDock || !hideIcons)
			 autolaunch:NO];
		}

		path = strtok(dragPath, "\t");
		while(path && strlen(path))	{
			[workspace openFile:path withApplication:data andDeactivate:YES];
			path = strtok(NULL, "\t");
		}
	}

    return [self draggingExited:sender];
}


- (BOOL) isOnRemovableMedia
{
    struct stat	st;
    char	*mountedList = NULL;
    id		workspace = [Application workspace];

    if (stat(data, &st) < 0)
    	device = 0;
    else
		device = st.st_dev;

    removable = NO;
    if ([workspace respondsTo:@selector(getMountedRemovableMedia:)] &&
        [workspace getMountedRemovableMedia:&mountedList])
		[self perform:@selector(isOnRemovableDevice:) withPaths:mountedList];

    if (mountedList)
		free(mountedList);

    return removable;
}


- isOnRemovableDevice:(const char *)path
{
    struct stat	st;

    if (path && stat(path, &st) >= 0)
		removable |= (device == st.st_dev);

    return self;
}

@end


/*
 * Returns true if mouse is dragged more than n pixels from 'o',
 * false if the mouse button is lifted.  If the mouse button is
 * lifted, the mouseUp event is not consumed.
 */
int
mouseMoved(NXPoint *o, int n, int mask)
{
    NXEvent e;
    NXPoint p;
    float   dx, dy;

    do {
		[NXApp peekNextEvent:mask into:&e waitFor:0x7fffffff
		 threshold:NX_MODALRESPTHRESHOLD];
		p = e.location;
		if (e.type == NX_LMOUSEUP)
			break;

		(void) [NXApp getNextEvent:mask]; /* throw it away */
		dx = abs(p.x - o->x);
		dy = abs(p.y - o->y);

    } while ((dy < (float) n) && (dx < (float) n));
    *o = p;

    return e.type != NX_LMOUSEUP;
}
