#import <math.h>
#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>


static id	destroySound;
int mouseMoved(NXPoint *o, int n, int mask);

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


@implementation IconDragView : IconView

+ initialize
{
	destroySound = [Sound findSoundFor:"Destroy"];
	return self;
}

- 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;

    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	tImage = image;

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

    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;
	NXPoint		mousePoint = saveEvent->location;
	Pasteboard 	*pboard = [Pasteboard newName:NXDragPboard];

	if (onDock)
		[dockMgrView stopScanTimer];

	if (!onDock)
		[superview setDragView:self onEvent:saveEvent withOffset:offPt atLocation:imgPt];
	else if (saveEvent->flags & NX_ALTERNATEMASK)	{
		NXImage		*dragImage = [IconView getImageForPath:data fileIcon:YES zone:[self zone]];

		[pboard declareTypes:&NXFilenamePboardType num:1 owner:nil];
		[pboard writeType:NXFilenamePboardType data:data length:strlen(data)];
		[self dragImage:dragImage at:&frame.origin offset:offPt
			  event:saveEvent pasteboard:pboard source:dockMgrView slideBack:NO];
		[dragImage free];
	}
	else if (1 /*!sticky*/)	{
		const char	*types[] = {FIEND_PBTYPE};
		NXImage 	*dragImage = [[NXImage allocFromZone:[self zone]] initSize:&frame.size];

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

		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		if ([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];
		[dragImage free];
		dockDrag = NO;

		[[self setGhost:NO] display];
		NXPing();
	}
	else if (sticky)	{
		NXRunAlertPanel([NXApp appName],
						"Can't delete sticky icons!",
						NULL, NULL, NULL);
		NXBeep();
	}

	if (onDock)
		[dockMgrView startScanTimer];

	return self;
}

static void
getDockLoc(NXPoint *p, NXSize *size)
{
	char	string[100];
	const char	*ret;

	p->x = p->y = -1.0;
	if (size->height != ORIG_SCREEN_HEIGHT)	{
		sprintf(string, "DockOriginYForHt%d", (int)size->height);
		ret = NXGetDefaultValue("Workspace", string);
		if (ret)
			p->y = atoi(ret);
		sprintf(string, "DockOriginXForHt%d", (int)size->height);
		ret = NXGetDefaultValue("Workspace", string);
		if (ret)
			p->x = atoi(ret);
	}
	else	{
		ret = NXGetDefaultValue("Workspace", "DockOriginY");
		if (ret)
			p->y = atoi(ret);
		ret = NXGetDefaultValue("Workspace", "DockOriginX");
		if (ret)
			p->x = atoi(ret);
	}
	if (p->x < 0.0)
		p->x = size->width - 67.0;
	if (p->y < 0.0)
		p->y = size->height - 64.0;
}

- (BOOL)handleSingleClick:(NXEvent *)e
{
	int			newStat;
	NXPoint		imageLoc;
	NXPoint		upperRight;
	BOOL		delivered = NO;
	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		noFlags = (!command && !shift && !control && !alt);
	BOOL		justCommand = (command && !alt && !shift && !control);
	BOOL		useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");
	BOOL		demandDockCommand = !strcmp(NXGetDefaultValue([NXApp appName], ENAB_DCLCK), "NO");
	BOOL		demandShelfCommand = !strcmp(NXGetDefaultValue([NXApp appName], ENAB_SCLCK), "NO");
	struct stat	buf;
	const char	*path = (const char *)data;

	if (onDock)	{
		if (ghost)	{
			ghost = NO;
			[self display];
		}
		[[NXApp delegate] deselectShelf];
	}
	else
		[[NXApp delegate] deselectDock];

	if ((onDock && ((noFlags && !demandDockCommand) || (justCommand && demandDockCommand))) ||
		(!onDock && ((noFlags && !demandShelfCommand) || (justCommand && demandShelfCommand))))	{
		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		[self convertPoint:&imageLoc toView:nil];
		[window convertBaseToScreen:&imageLoc];

		getDockLoc(&upperRight, &screenSize);
		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:""];
		if (delivered)
			[[Application workspace] launchApplication:"WM"];
		[self setState:NO];

		if (!(autoScan && isLaunched) && ((newStat = stat(path, &buf)) != statReturn))	{
			statReturn = newStat;
			[self restoreIconImage];
		}
	}
	else if (control)	{
		if (!onDock)	{
			[superview perform:@selector(deleteView:)
					   with:self afterDelay:0.0 cancelPrevious:YES];
			[superview perform:@selector(writeShelf)
					   with:nil afterDelay:1000.0 cancelPrevious:YES];
			[[NXApp delegate] perform:@selector(updateMenus)
			           with:nil afterDelay:1000.0 cancelPrevious:NO];
			delivered = YES;
			if (useSound)
				[destroySound play:self];
		}
		else	{
			if (!sticky)	{
				[dockMgrView deleteView:self];
				delivered = YES;
			}
			else	{
				NXLogError("%s is flagged as persistent - can't delete", path);
				NXBeep();
			}
		}
	}
	else if (shift && command && onDock)	{
		[dockMgrView inspectIcon:self];
		delivered = YES;
	}
	else if (shift)	{
		[NXApp unhide:self];
		[self setState:!selected];
		delivered = YES;
	}
	else if (alt && onDock)	{
		if (!(autoScan && isLaunched) && ((newStat = stat(path, &buf)) != statReturn))	{
			statReturn = newStat;
			[self restoreIconImage];
		}
		[dockMgrView app:dockMgrView applicationDidLaunch:path];
		delivered = YES;
	}
	else	{
		if (onDock)
			[[dockMgrView currentDock] select:NO all:self];
		else
			[superview deselectAll:self];
		delivered = YES;
	}

	return delivered;
}

- delayedActivate
{
	if (appCtxt > 0)
		fiendUnhideContext(appCtxt, 1, 1, 0);

	return self;
}

- (BOOL)handleDoubleClick:(NXEvent *)e
{
	int			newStat;
	NXPoint		imageLoc;
	NXPoint		upperRight;
	struct stat	buf;
	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;
	float		delay = 1000.0 * atof(NXGetDefaultValue([NXApp appName], ACTIVE_DELAY));
	BOOL		closePostLaunch = !strcmp(NXGetDefaultValue([NXApp appName], CLOSE_PSTLNCH), "YES");

	if (!autoScan && ((newStat = stat(path, &buf)) != statReturn))	{
		statReturn = newStat;
		[self restoreIconImage];
	}
	if (!isApp)	{
		[self getImagePoint:&imageLoc andHilitePoint:NULL];
		[self convertPoint:&imageLoc toView:nil];
		[window convertBaseToScreen:&imageLoc];

		getDockLoc(&upperRight, &screenSize);
		[[Application workspace] slideImage:image from:&imageLoc to:&upperRight];
		delivered = [[Application workspace] openFile:path];
	}
	else if (cmd && isLaunched)	{
		fiendUnhideContext(appCtxt, 1, 1, 1);
		delivered = YES;
	}
	else if (!alt)	{
		if (!isLaunched)	{
			if (!onDock)
				[self setState:YES];
			else
				[self setGhost:YES];

			[self display];
			NXPing();

			if (onDock)	{
				[self setFiendLaunch:YES];
				[dockMgrView launch:path autolaunch:NO];
			}
			else
				[[Application workspace] launchApplication:path];

			if (!onDock)	{
				[self setState:NO];
				[self display];
			}
		}
		else
			fiendUnhideContext(appCtxt, 1, 1, 0);
		delivered = YES;
	}
	else if (!onDock && alt)	{
		delivered = [[Application workspace] openFile:path];
		[self perform:@selector(delayedActivate) with:nil afterDelay:delay cancelPrevious:YES];
	}
	if (onDock && closePostLaunch && delivered)
		[dockMgrView setShowDock:NO];

	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();
    }

	[[NXApp delegate] updateMenus];
	[window setEventMask:savedMask];
    return self;
}


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

- (BOOL) acceptsFirstMouse
{
	return YES;
}


- (BOOL) acceptsFirstResponder
{
	return NO;
}

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

    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;
    }

    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+1];
			NXBundle   *bundle;

			bundle = [NXBundle bundleForClass:[self class]];
			if ([bundle getPath:buf forResource:OPEN_HIGHLIGHT ofType:"tiff"]) {
				[[[highlight initFromFile:buf] setScalable:YES] setSize:&size];
				[image composite:NX_COPY 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 (IS_APP_PATH(destinationPath))	{
		dragDestType = NXApplicationFileType;
		return ApplicationDirectory;
	}

    if (stat(destinationPath, &st) < 0 || (st.st_mode & S_IFMT) != S_IFDIR)
		return WriteNotAllowed;
    if (access(destinationPath, W_OK|X_OK) < 0)
		return WriteNotAllowed;

	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;
    }

    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;
}


- (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 types];
    [pb readType:NXFilenamePboardType data:&dragPath length:&junk];

    nextPath = dragPath;
    do {
    	char		path[MAXPATHLEN+1];
		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;
			}

		case SameDeviceOperation:
			if (maskOp & NX_DragOperationGeneric) {
				thisOp = NX_DragOperationGeneric;
				break;
			}

		default:
			break;
		}

		if (first) {
			op = thisOp;
			first = NO;
		}
		else	{
			if (op != thisOp)	{
				[pb deallocatePasteboardData:dragPath length:junk];
				return NX_DragOperationNone;
			}
		}

		nextPath = index(nextPath, '\t');
		if (nextPath == NULL)
			break;
		else
			nextPath++;

    } while (1);

	[pb deallocatePasteboardData:dragPath length:junk];

    return op;
}


- (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
{
    [altImage free];
    altImage = nil;
    [self display];
    [window flushWindow];
    return self;
}


- (BOOL) prepareForDragOperation:sender
{
    return YES;
}

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


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

	[pb types];
	[pb readType:NXFilenamePboardType data:&dragPath length:&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:
			[pb deallocatePasteboardData:dragPath length:pbLength];
			return NO;
		}
		[workspace performFileOperation:wsmOp source:"/" destination:data
				   files:dragPath options:NULL];
	}
	else if (dragDestType == NXApplicationFileType && lastDragOp == NX_DragOperationGeneric)	{
		path = strtok(dragPath, "\t");
		while(path && strlen(path))	{
			[workspace openFile:path withApplication:data andDeactivate:NO];
			path = strtok(NULL, "\t");
		}
	}
	[pb deallocatePasteboardData:dragPath length:pbLength];

    return [self draggingExited:sender];
}

@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;
}

