/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

#import "GlassViewDelegate.h"

#import "common.h"
#import "com_sun_glass_events_ViewEvent.h"
#import "com_sun_glass_events_MouseEvent.h"
#import "com_sun_glass_events_TouchEvent.h"
#import "com_sun_glass_events_KeyEvent.h"
#import "com_sun_glass_events_DndEvent.h"
#import "com_sun_glass_events_SwipeGesture.h"
#import "com_sun_glass_ui_Clipboard.h"
#import "com_sun_glass_ui_mac_MacGestureSupport.h"

#import "GlassMacros.h"
#import "GlassKey.h"
#import "GlassScreen.h"
#import "GlassWindow.h"

//#define VERBOSE
#ifndef VERBOSE
    #define LOG(MSG, ...)
    #define CHECK
#else
    #define LOG(MSG, ...) GLASS_LOG(MSG, ## __VA_ARGS__);
    #define CHECK GLASS_CHECK_EXCEPTION
#endif

//#define DNDVERBOSE
#ifndef DNDVERBOSE
    #define DNDLOG(MSG, ...)
#else
    #define DNDLOG(MSG, ...) GLASS_LOG(MSG, ## __VA_ARGS__);
#endif

// used Safari as a reference while dragging large images
#define MAX_DRAG_SIZE 400

// explicitly set image size
#define DEFAULT_DRAG_SIZE 64

// Tracks pressed modifier keys
static NSUInteger s_modifierFlags = 0;

// The last processed key event
static NSEvent* s_lastKeyEvent = nil;

@interface GlassViewDelegate (hidden)

- (void)releaseTouches;
@end

static jint getTouchStateFromPhase(int phase)
{
    switch (phase) {
        case NSTouchPhaseBegan:
            return com_sun_glass_events_TouchEvent_TOUCH_PRESSED;
        case NSTouchPhaseMoved:
            return com_sun_glass_events_TouchEvent_TOUCH_MOVED;
        case NSTouchPhaseStationary:
            return com_sun_glass_events_TouchEvent_TOUCH_STILL;
        case NSTouchPhaseEnded:
        case NSTouchPhaseCancelled:
            return com_sun_glass_events_TouchEvent_TOUCH_RELEASED;
    }
    return 0;
}


static BOOL isTouchEnded(int phase)
{
    return phase == NSTouchPhaseEnded || phase == NSTouchPhaseCancelled;
}


static jint getSwipeDirFromEvent(NSEvent *theEvent)
{
    if ([theEvent deltaX] < 0) {
        return com_sun_glass_events_SwipeGesture_DIR_RIGHT;
    }
    if ([theEvent deltaX] > 0) {
        return com_sun_glass_events_SwipeGesture_DIR_LEFT;
    }
    if ([theEvent deltaY] > 0) {
        return com_sun_glass_events_SwipeGesture_DIR_UP;
    }
    if ([theEvent deltaY] < 0) {
        return com_sun_glass_events_SwipeGesture_DIR_DOWN;
    }
    return 0;
}


@implementation GlassViewDelegate

- (id)initWithView:(NSView*)view withJview:(jobject)jview
{
    self = [super init];
    if (self != nil)
    {
        GET_MAIN_JENV;
        
        self->nsView = view;
        self->jView = (*env)->NewGlobalRef(env, jview);
        self->mouseIsOver = NO;

        // Ensure JNI stuff related to gesture processing is ready
        if (NULL == jGestureSupportClass) {
            (*env)->FindClass(env, "com/sun/glass/ui/mac/MacGestureSupport");
        }
        
        self->touches = nil;
        self->lastTouchId = 0;

        // optimization
        [self->nsView allocateGState];
        
        // we want all touch events
        // (http://stackoverflow.com/questions/3573276/know-the-position-of-the-finger-in-the-trackpad-under-mac-os-x)
        [self->nsView setAcceptsTouchEvents:YES];
        [self->nsView setWantsRestingTouches:YES];
		
		// register for drag and drop
		[self->nsView registerForDraggedTypes:[NSArray arrayWithObjects:	NSPasteboardTypeString,
                                                                        	NSPasteboardTypeTIFF,
                                                                           	NSPasteboardTypeRTF,
                                                                           	NSPasteboardTypeTabularText,
                                                                           	NSPasteboardTypeFont,
                                                                           	NSPasteboardTypeRuler,
                                                                           	NSPasteboardTypeColor,
                                                                           	NSPasteboardTypeRTFD,
                                                                           	NSPasteboardTypeHTML,
                                                                           	NSPasteboardTypePDF,
                                                                           	NSPasteboardTypeMultipleTextSelection,
                                                                           	(NSString*)kUTTypeURL,
                                                                           	(NSString*)kUTTypeFileURL,
                                                                            nil]];
    }
    return self;
}

- (void)dealloc
{
    [self->lastEvent release];
    self->lastEvent = nil;
    
    [self->parentHost release];
    self->parentHost = nil;
    
    [self->parentWindow release];
    self->parentWindow = nil;
    
    [self->fullscreenWindow release];
    self->fullscreenWindow = nil;
    
    [self releaseTouches];
    
    GET_MAIN_JENV;
    if (env != NULL)
    {
    	(*env)->DeleteGlobalRef(env, self->jView);
    }
    self->jView = NULL;
	
    [super dealloc];
}

- (jobject)jView
{
    return self->jView;
}

- (void)viewDidMoveToWindow
{
    //	NSLog(@"viewDidMoveToWindow");
    //	NSLog(@"	self: %@", self);
    //	NSLog(@"	[self superview]: %@", [self superview]);
    GET_MAIN_JENV;
    if ([self->nsView window] != nil)
    {
        if (self->parentHost == nil)
        {
            self->parentHost = (GlassHostView*)[[self->nsView superview] retain];
        }
        if (self->parentWindow == nil)
        {
            self->parentWindow = [[self->nsView window] retain];
        }
        
        [[self->nsView window] setAcceptsMouseMovedEvents:YES];
        (*env)->CallVoidMethod(env, self->jView, jHandleViewEvent, com_sun_glass_events_ViewEvent_ADD);
    }
    else
    {
        if (self->parentWindow != nil)
        {
            [self->parentWindow release];
            self->parentWindow = nil;
        }
        (*env)->CallVoidMethod(env, self->jView, jHandleViewEvent, com_sun_glass_events_ViewEvent_REMOVE);
    }
}

- (void)setFrameOrigin:(NSPoint)newOrigin
{
    
}

- (void)setFrameSize:(NSSize)newSize
{
    //NSLog(@"GlassViewDelegate setFrameSize: %dx%d", (int)newSize.width, (int)newSize.height);
    // TODO: listen for resize view's notifications
    GET_MAIN_JENV;
    (*env)->CallVoidMethod(env, self->jView, jViewNotifyResize, (int)newSize.width, (int)newSize.height);
    GLASS_CHECK_EXCEPTION(env);
    
    [self->nsView removeTrackingRect:self->trackingRect];
    self->trackingRect = [self->nsView addTrackingRect:[self->nsView bounds] owner:self->nsView userData:nil assumeInside:NO];
}

- (void)setFrame:(NSRect)frameRect
{
    //NSLog(@"GlassViewDelegate setFrame: %d,%d %dx%d", (int)frameRect.origin.x, (int)frameRect.origin.y, (int)frameRect.size.width, (int)frameRect.size.height);
    // TODO: listen for resize view's notifications
    GET_MAIN_JENV;
    (*env)->CallVoidMethod(env, self->jView, jViewNotifyResize, (int)frameRect.size.width, (int)frameRect.size.height);
    GLASS_CHECK_EXCEPTION(env);
    
    [self->nsView removeTrackingRect:self->trackingRect];
    self->trackingRect = [self->nsView addTrackingRect:[self->nsView bounds] owner:self->nsView userData:nil assumeInside:NO];
}

- (void)updateTrackingAreas
{
    [self->nsView removeTrackingRect:self->trackingRect];
    self->trackingRect = [self->nsView addTrackingRect:[self->nsView bounds] owner:self->nsView userData:nil assumeInside:NO];
}

- (void)drawRect:(NSRect)dirtyRect
{
    //NSLog(@"BEGIN View:drawRect %@: ", self);
    //NSLog(@"	[self frame]: %f,%f %fx%f", [self->nsView frame].origin.x, [self->nsView frame].origin.y, [self->nsView frame].size.width, [self->nsView frame].size.height);
    GET_MAIN_JENV;
    jint x = [self->nsView frame].origin.x;
    jint y = [self->nsView frame].origin.y;
    jint w = [self->nsView frame].size.width;
    jint h = [self->nsView frame].size.height;
    (*env)->CallVoidMethod(env, self->jView, jViewNotifyRepaint, x, y, w, h);
    GLASS_CHECK_EXCEPTION(env);
    //NSLog(@"END drawRect");
}

- (void)sendJavaMenuEvent:(NSEvent *)theEvent
{
//    NSLog(@"sendJavaMenuEvent");
    NSWindow * nswindow = [nsView window];
    if (nswindow && [nswindow isKindOfClass: [GlassWindow class]]) {
        GlassWindow *window = (GlassWindow*)nswindow;
        if (!window->isEnabled) {
            return;
        }
    }
    NSPoint viewPoint = [nsView convertPoint:[theEvent locationInWindow] fromView:nil]; // convert from window coordinates to view coordinates
    CGPoint basePoint = CGEventGetLocation([theEvent CGEvent]);

    GET_MAIN_JENV;
    jboolean isKeyboardTrigger = JNI_FALSE;
    (*env)->CallVoidMethod(env, self->jView, jViewNotifyMenu, 
                            (jint)viewPoint.x, (jint)viewPoint.y, (jint)basePoint.x, (jint)basePoint.y, isKeyboardTrigger);
    GLASS_CHECK_EXCEPTION(env);
}

- (void)sendJavaMouseEvent:(NSEvent *)theEvent
{
    NSWindow * nswindow = [nsView window];
    if (nswindow && [nswindow isKindOfClass: [GlassWindow class]]) {
        GlassWindow *window = (GlassWindow*)nswindow;
        if (!window->isEnabled) {
            return;
        }
    }

    int type = 0;
    int button = com_sun_glass_events_MouseEvent_BUTTON_NONE;
    switch ([theEvent type])
    {
        case NSLeftMouseDown:
            type = com_sun_glass_events_MouseEvent_DOWN;
            button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
            break;
        case NSRightMouseDown:
            type = com_sun_glass_events_MouseEvent_DOWN;
            button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
            break;
        case NSOtherMouseDown:
            type = com_sun_glass_events_MouseEvent_DOWN;
            button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
            break;
            
        case NSLeftMouseUp:
            type = com_sun_glass_events_MouseEvent_UP;
            button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
            break;
        case NSRightMouseUp:
            type = com_sun_glass_events_MouseEvent_UP;
            button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
            break;
        case NSOtherMouseUp:
            type = com_sun_glass_events_MouseEvent_UP;
            button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
            break;
            
        case NSLeftMouseDragged:
            type = com_sun_glass_events_MouseEvent_DRAG;
            button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
            break;
        case NSRightMouseDragged:
            type = com_sun_glass_events_MouseEvent_DRAG;
            button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
            break;
        case NSOtherMouseDragged:
            type = com_sun_glass_events_MouseEvent_DRAG;
            button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
            break;
            
        case NSMouseMoved:
            type = com_sun_glass_events_MouseEvent_MOVE;
            break;
            
        case NSMouseEntered:
            type = com_sun_glass_events_MouseEvent_ENTER;
            self->lastTrackingNumber = [theEvent trackingNumber];
            break;
            
        case NSMouseExited:
            type = com_sun_glass_events_MouseEvent_EXIT;
            self->lastTrackingNumber = [theEvent trackingNumber];
            break;
            
        case NSScrollWheel:
            type = com_sun_glass_events_MouseEvent_WHEEL;
            break;
    }
    
    NSPoint viewPoint = [nsView convertPoint:[theEvent locationInWindow] fromView:nil]; // convert from window coordinates to view coordinates
    CGPoint basePoint = CGEventGetLocation([theEvent CGEvent]);

    if (type == com_sun_glass_events_MouseEvent_MOVE)
    {
        NSRect frame = [nsView frame];

        if (viewPoint.x < 0 || viewPoint.y < 0 ||
                viewPoint.x >= frame.size.width ||
                viewPoint.y >= frame.size.height)
        {
            // The MOVE events happening outside of the view must be ignored
            return;
        }

        // Check if the event is a duplicate
        if (self->lastEvent)
        {
            CGPoint oldBasePoint = CGEventGetLocation([self->lastEvent CGEvent]);

            if (basePoint.x == oldBasePoint.x && basePoint.y == oldBasePoint.y)
            {
                return;
            }
        }
    }
    
	//    NSLog(@"Event location: in window %@, in view %@, in base coordinates %d,%d",
	//          NSStringFromPoint([theEvent locationInWindow]),
	//          NSStringFromPoint(viewPoint),
	//          (jint)basePoint.x, (jint)basePoint.y);
	
    jdouble rotationX = 0.0;
    jdouble rotationY = 0.0;
    if (type == com_sun_glass_events_MouseEvent_WHEEL)
    {
        rotationX = (jdouble)[theEvent deltaX];
        rotationY = (jdouble)[theEvent deltaY];

        //XXX: check for equality for doubles???
        if (rotationX == 0.0 && rotationY == 0.0)
        {
            return;
        }

        // The rotation values start from 0.1 because by default Mac divides
        // the values to a constant value of 10 (see CGEventSource.h and
        // CGEventSourceGetPixelsPerLine). So we multiply them to get scroll
        // amounts in pixels.
        rotationX *= 10.0;
        rotationY *= 10.0;
    }
    
    BOOL block = NO;
    {
        // RT-5892
        if ((type == com_sun_glass_events_MouseEvent_ENTER) || (type == com_sun_glass_events_MouseEvent_EXIT))
        {
            // userData indicates if this is a synthesized EXIT event that MUST pass through
            // Note: userData is only valid for ENTER/EXIT events!
            if (self->mouseIsDown == YES && [theEvent userData] != self)
            {
                block = [self suppressMouseEnterExitOnMouseDown];
            }
        }
        else
        {
            // for the mouse supression we can not look at the mouse down state during ENTER/EXIT events
            // as they always report mouse up regardless of the actual state, so we need to store it
            // based on the events other than ENTER/EXIT
            self->mouseIsDown = (button != com_sun_glass_events_MouseEvent_BUTTON_NONE);
        }
    }
    if (block == NO)
    {
        if (!self->mouseIsOver &&
                type != com_sun_glass_events_MouseEvent_ENTER &&
                type != com_sun_glass_events_MouseEvent_EXIT)
        {
            // OS X didn't send mouseEnter. Synthesize it here.
            NSEvent *eeEvent = [NSEvent enterExitEventWithType:NSMouseEntered location:[theEvent locationInWindow] modifierFlags:[theEvent modifierFlags] timestamp:[theEvent timestamp] windowNumber:[theEvent windowNumber] context:[theEvent context] eventNumber:0 trackingNumber:self->lastTrackingNumber userData:self];
            [self sendJavaMouseEvent:eeEvent];
        }

        jint modifiers = GetJavaModifiers(theEvent);
        if (type != com_sun_glass_events_MouseEvent_UP)
        {
            switch (button)
            {
                case com_sun_glass_events_MouseEvent_BUTTON_LEFT:
                    modifiers |= com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_PRIMARY;
                    break;
                case com_sun_glass_events_MouseEvent_BUTTON_RIGHT:
                    modifiers |= com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_SECONDARY;
                    break;
                case com_sun_glass_events_MouseEvent_BUTTON_OTHER:
                    modifiers |= com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_MIDDLE;
                    break;
            }
        }
        
        jboolean isSynthesized = JNI_FALSE;
        
        jboolean isPopupTrigger = JNI_FALSE;
        if (type == com_sun_glass_events_MouseEvent_DOWN) {
            if (button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT) {
                isPopupTrigger = JNI_TRUE;
            }
            if (button == com_sun_glass_events_MouseEvent_BUTTON_LEFT &&
                (modifiers & com_sun_glass_events_KeyEvent_MODIFIER_CONTROL))
            {
                isPopupTrigger = JNI_TRUE;
            }
        }
        
        [self->lastEvent release];
        self->lastEvent = nil;
        switch (type) {
            // prepare GlassDragSource for possible drag,
            case com_sun_glass_events_MouseEvent_DOWN:
            case com_sun_glass_events_MouseEvent_DRAG:
                [GlassDragSource setDelegate:self];
                // fall through to save the lastEvent
            // or for filtering out duplicate MOVE events
            case com_sun_glass_events_MouseEvent_MOVE:
                self->lastEvent = [theEvent retain];
                break;


            // Track whether the mouse is over the view
            case com_sun_glass_events_MouseEvent_ENTER:
                self->mouseIsOver = YES;
                break;
            case com_sun_glass_events_MouseEvent_EXIT:
                self->mouseIsOver = NO;
                break;
        }
        
        GET_MAIN_JENV;
        if (type == com_sun_glass_events_MouseEvent_WHEEL) {
            (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                        jGestureSupportScrollGesturePerformed,
                                        self->jView, modifiers, 
                                        (jint)viewPoint.x, (jint)viewPoint.y, 
                                        (jint)basePoint.x, (jint)basePoint.y,
                                        rotationX, rotationY);
                                        
        } else {
            (*env)->CallVoidMethod(env, self->jView, jViewNotifyMouse, type, button, 
                    (jint)viewPoint.x, (jint)viewPoint.y, (jint)basePoint.x, (jint)basePoint.y, 
                    modifiers, isPopupTrigger, isSynthesized);
        }
        GLASS_CHECK_EXCEPTION(env);
    }
}

- (void)resetMouseTracking
{
    if (self->mouseIsOver) {
        // Nothing of the parameters really matters for the EXIT event, except userData
        NSEvent* theEvent = [NSEvent
            enterExitEventWithType:NSMouseExited
                          location:[NSEvent mouseLocation]
                     modifierFlags:0
                         timestamp:[NSDate timeIntervalSinceReferenceDate]
                      windowNumber:[[self->nsView window] windowNumber]
                           context:[NSGraphicsContext currentContext]
                       eventNumber:0
                    trackingNumber:self->lastTrackingNumber
                          userData:self]; // indicates that this is a synthesized event

        [self sendJavaMouseEvent:theEvent];
    }
}

// RT-11707: zero out the keycode for TYPED events
#define SEND_KEY_EVENT(type) \
    (*env)->CallVoidMethod(env, self->jView, jViewNotifyKey, (type), \
            (type) == com_sun_glass_events_KeyEvent_TYPED ? 0 : jKeyCode, \
            jKeyChars, jModifiers); \
    GLASS_CHECK_EXCEPTION(env);

- (void)sendJavaKeyEvent:(NSEvent *)theEvent isDown:(BOOL)isDown
{
    if (theEvent == s_lastKeyEvent) {
        // this must be a keyDown: generated by performKeyEquivalent: which returns NO by design
        return;
    }
    [s_lastKeyEvent release];
    s_lastKeyEvent = [theEvent retain];

    GET_MAIN_JENV;
    
    jint jKeyCode = GetJavaKeyCode(theEvent);
    jcharArray jKeyChars = GetJavaKeyChars(env, theEvent);
    jint jModifiers = GetJavaModifiers(theEvent);
	
    if (!isDown)
    {
        SEND_KEY_EVENT(com_sun_glass_events_KeyEvent_RELEASE);
    }
    else
    {
        SEND_KEY_EVENT(com_sun_glass_events_KeyEvent_PRESS);
    
        // TYPED events should only be sent for printable characters. Thus we avoid
        // sending them for navigation keys. Perhaps this logic could be enhanced.
        if (jKeyCode < com_sun_glass_events_KeyEvent_VK_PAGE_UP ||
                jKeyCode > com_sun_glass_events_KeyEvent_VK_DOWN)
        {
            SEND_KEY_EVENT(com_sun_glass_events_KeyEvent_TYPED);
        }

        // Mac doesn't send keyUp for Cmd+<> key combinations (including Shift+Cmd+<>, etc.)
        // So we synthesize the event
        if (jModifiers & com_sun_glass_events_KeyEvent_MODIFIER_COMMAND)
        {
            SEND_KEY_EVENT(com_sun_glass_events_KeyEvent_RELEASE);
        }
    }
    
    (*env)->DeleteLocalRef(env, jKeyChars);
    GLASS_CHECK_EXCEPTION(env);
}

#define SEND_MODIFIER_KEY_EVENT_WITH_TYPE(type, vkCode) \
        (*env)->CallVoidMethod(env, self->jView, jViewNotifyKey, \
                (type), \
                (vkCode), \
                jKeyChars, jModifiers);

#define SEND_MODIFIER_KEY_EVENT(mask, vkCode) \
    if (changedFlags & (mask)) { \
        SEND_MODIFIER_KEY_EVENT_WITH_TYPE(currentFlags & (mask) ? com_sun_glass_events_KeyEvent_PRESS : com_sun_glass_events_KeyEvent_RELEASE, vkCode); \
        GLASS_CHECK_EXCEPTION(env); \
    }

- (void)sendJavaModifierKeyEvent:(NSEvent *)theEvent
{
    NSUInteger currentFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
    NSUInteger changedFlags = currentFlags ^ s_modifierFlags;

    jint jModifiers = GetJavaModifiers(theEvent);

    GET_MAIN_JENV;
    jcharArray jKeyChars = (*env)->NewCharArray(env, 0);

    SEND_MODIFIER_KEY_EVENT(NSShiftKeyMask,       com_sun_glass_events_KeyEvent_VK_SHIFT);
    SEND_MODIFIER_KEY_EVENT(NSControlKeyMask,     com_sun_glass_events_KeyEvent_VK_CONTROL);
    SEND_MODIFIER_KEY_EVENT(NSAlternateKeyMask,   com_sun_glass_events_KeyEvent_VK_ALT);
    SEND_MODIFIER_KEY_EVENT(NSCommandKeyMask,     com_sun_glass_events_KeyEvent_VK_COMMAND);

    // For CapsLock both PRESS and RELEASE should be synthesized each time
    if (changedFlags & NSAlphaShiftKeyMask) {
        SEND_MODIFIER_KEY_EVENT_WITH_TYPE(com_sun_glass_events_KeyEvent_PRESS, com_sun_glass_events_KeyEvent_VK_CAPS_LOCK);
        SEND_MODIFIER_KEY_EVENT_WITH_TYPE(com_sun_glass_events_KeyEvent_RELEASE, com_sun_glass_events_KeyEvent_VK_CAPS_LOCK);
    }

    (*env)->DeleteLocalRef(env, jKeyChars);
    GLASS_CHECK_EXCEPTION(env);

    s_modifierFlags = currentFlags;
}

- (void)sendJavaGestureEvent:(NSEvent *)theEvent type:(int)type
{
    NSPoint viewPoint = [nsView convertPoint:[theEvent locationInWindow] fromView:nil]; // convert from window coordinates to view coordinates
    CGPoint basePoint = CGEventGetLocation([theEvent CGEvent]);

    jint modifiers = GetJavaModifiers(theEvent);

    GET_MAIN_JENV;

    switch (type)
    {
        case com_sun_glass_ui_mac_MacGestureSupport_GESTURE_ROTATE:
            (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                        jGestureSupportRotateGesturePerformed,
                                        self->jView, modifiers, 
                                        (jint)viewPoint.x, (jint)viewPoint.y, 
                                        (jint)basePoint.x, (jint)basePoint.y,
                                        (jfloat)[theEvent rotation]);
            break;
        case com_sun_glass_ui_mac_MacGestureSupport_GESTURE_SWIPE:
            (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                        jGestureSupportSwipeGesturePerformed,
                                        self->jView, modifiers, 
                                        getSwipeDirFromEvent(theEvent),
                                        (jint)viewPoint.x, (jint)viewPoint.y, 
                                        (jint)basePoint.x, (jint)basePoint.y);
            break;
        case com_sun_glass_ui_mac_MacGestureSupport_GESTURE_MAGNIFY:
            (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                        jGestureSupportMagnifyGesturePerformed,
                                        self->jView, modifiers, 
                                        (jint)viewPoint.x, (jint)viewPoint.y, 
                                        (jint)basePoint.x, (jint)basePoint.y,
                                        (jfloat)[theEvent magnification]);
            break;
    }
    GLASS_CHECK_EXCEPTION(env);
}

- (void)sendJavaGestureEndEvent:(NSEvent *)theEvent
{
    NSPoint viewPoint = [nsView convertPoint:[theEvent locationInWindow] fromView:nil]; // convert from window coordinates to view coordinates
    CGPoint basePoint = CGEventGetLocation([theEvent CGEvent]);

    jint modifiers = GetJavaModifiers(theEvent);

    GET_MAIN_JENV;
    
    (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                jGestureSupportGestureFinished,
                                self->jView, modifiers, 
                                (jint)viewPoint.x, (jint)viewPoint.y, 
                                (jint)basePoint.x, (jint)basePoint.y);
    GLASS_CHECK_EXCEPTION(env);
}

- (void)sendJavaTouchEvent:(NSEvent *)theEvent
{
    jint modifiers = GetJavaModifiers(theEvent);
    
    NSSet* touchPoints = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:self->nsView];
    
    jint touchPointCount = touchPoints.count;
    
    // Adjust 'touchPointCount'.
    for (NSTouch* touch in touchPoints)
    {
        int phase = touch.phase;
        BOOL isPhaseEnded = isTouchEnded(phase);

        if (!isPhaseEnded) 
        {
            continue;
        }
        
        if (self->touches == nil || [self->touches objectForKey:touch.identity] == nil)
        {
            --touchPointCount;
        }
    }

    if (!touchPointCount)
    {
        return;
    }

    GET_MAIN_JENV;

    (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                jGestureSupportNotifyBeginTouchEvent,
                                self->jView, modifiers, 
                                touchPointCount);
    GLASS_CHECK_EXCEPTION(env);

    BOOL endAllTouches = YES;

    for (NSTouch* touch in touchPoints)
    {
        int phase = touch.phase;
        NSPoint pos = touch.normalizedPosition;

        BOOL isPhaseEnded = isTouchEnded(phase);

        if (!isPhaseEnded)
        {
            endAllTouches = NO;
        }

        if (self->touches == nil) 
        {
            self->touches = [[NSMutableDictionary alloc] init];
        }

        id identity = touch.identity;
        id touchId = [self->touches objectForKey:identity];
        if (touchId == nil)
        {
            if (isPhaseEnded)
            {
                continue;
            }
            
            touchId = [NSNumber numberWithInt:++(self->lastTouchId)];
            [self->touches setObject:touchId forKey:identity];
        }
        else if (phase == NSTouchPhaseBegan)
        {   // Adjust 'phase'. This is needed as OS X sometimes sends
            // multiple 'NSTouchPhaseBegan' for the same touch point.
            phase = NSTouchPhaseStationary;
        }

        if (isPhaseEnded)
        {
            [self->touches removeObjectForKey:identity];
        }

        (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                    jGestureSupportNotifyNextTouchEvent, 
                                    self->jView, getTouchStateFromPhase(phase),
                                    (jlong)[touchId intValue], 
                                    (jfloat)pos.x, (jfloat)pos.y);
        GLASS_CHECK_EXCEPTION(env);
    }

    (*env)->CallStaticVoidMethod(env, jGestureSupportClass, 
                                jGestureSupportNotifyEndTouchEvent, self->jView);
    GLASS_CHECK_EXCEPTION(env);

    if (endAllTouches)
    {
        [self releaseTouches];
        self->lastTouchId = 0;
    }
}

- (NSDragOperation)sendJavaDndEvent:(id <NSDraggingInfo>)info type:(jint)type
{
    GET_MAIN_JENV;
    
    NSPoint draggingLocation = [nsView convertPoint:[info draggingLocation] fromView:nil];
    int x = draggingLocation.x;
    int y = draggingLocation.y;
    int xAbs = x + [self->nsView window].frame.origin.x + [self->nsView frame].origin.x;
    int yAbs = y + [self->nsView window].frame.origin.y + [self->nsView frame].origin.y;
    int mask;
    
    NSDragOperation operation = [[info draggingSource] draggingSourceOperationMaskForLocal:NO];
    jint recommendedActions = [GlassDragSource mapNsOperationToJavaMask:operation];
    [GlassDragSource setMask:recommendedActions];
    switch (type)
    {
        case com_sun_glass_events_DndEvent_ENTER:
            DNDLOG("com_sun_glass_events_DndEvent_ENTER");
            mask = (*env)->CallIntMethod(env, self->jView, jViewNotifyDragEnter, x, y, xAbs, yAbs, recommendedActions);
            [GlassDragSource setMask:mask];
            break;
        case com_sun_glass_events_DndEvent_UPDATE:
            DNDLOG("com_sun_glass_events_DndEvent_UPDATE");
            mask = (*env)->CallIntMethod(env, self->jView, jViewNotifyDragOver, x, y, xAbs, yAbs, recommendedActions);
            [GlassDragSource setMask:mask];
            break;
        case com_sun_glass_events_DndEvent_PERFORM:
            DNDLOG("com_sun_glass_events_DndEvent_PERFORM");
            mask = (*env)->CallIntMethod(env, self->jView, jViewNotifyDragDrop, x, y, xAbs, yAbs, recommendedActions);
            [GlassDragSource setMask:mask];
            break;
        case com_sun_glass_events_DndEvent_END:
            DNDLOG("com_sun_glass_events_DndEvent_END");
            (*env)->CallVoidMethod(env, self->jView, jViewNotifyDragEnd, recommendedActions);
            [GlassDragSource setMask:com_sun_glass_ui_Clipboard_ACTION_NONE];
            break;
        case com_sun_glass_events_DndEvent_EXIT:
            DNDLOG("com_sun_glass_events_DndEvent_EXIT");
            (*env)->CallVoidMethod(env, self->jView, jViewNotifyDragLeave);
            [GlassDragSource setMask:com_sun_glass_ui_Clipboard_ACTION_NONE];
            break;
        default:
            [GlassDragSource setMask:com_sun_glass_ui_Clipboard_ACTION_NONE];
            break;
    }
    
    GLASS_CHECK_EXCEPTION(env);
    
    return [GlassDragSource mapJavaMaskToNsOperation:[GlassDragSource getMask]];
}

- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
    return self->dragOperation;
}

// called from Java layer drag handler, triggered by DnD Pasteboard flush
- (void)startDrag:(NSDragOperation)operation
{
    DNDLOG("startDrag");
    self->dragOperation = operation;
    {
        NSPoint dragPoint = [self->nsView convertPoint:[self->lastEvent locationInWindow] fromView:nil];
        NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
        NSImage *image = nil;
        if ([NSImage canInitWithPasteboard:pasteboard] == YES)
        {
            // ask the Pasteboard for ist own image representation of its contents
            image = [[NSImage alloc] initWithPasteboard:pasteboard];
            
            // check the drag image size and scale it down as needed using Safari behavior (sizes) as reference
            CGFloat width = [image size].width;
            CGFloat height = [image size].height;
            if ((width > MAX_DRAG_SIZE) || (height > MAX_DRAG_SIZE))
            {
                if (width >= height)
                {
                    CGFloat ratio = height/width;
                    width = MIN(width, MAX_DRAG_SIZE);
                    height = ratio * width;
                    [image setSize:NSMakeSize(width, height)];
                }
                else
                {
                    CGFloat ratio = width/height;
                    height = MIN(height, MAX_DRAG_SIZE);
                    width = ratio * height;
                    [image setSize:NSMakeSize(width, height)];
                }
            }
        }
        else
        {
            NSArray *items = [pasteboard pasteboardItems];
            if ([items count] == 1)
            {
                image = [[NSImage alloc] initWithContentsOfFile:@"/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns"];
            }
            
            if (image == nil)
            {
                image = [[NSImage imageNamed:NSImageNameMultipleDocuments] retain];
            }
            
            [image setSize:NSMakeSize(DEFAULT_DRAG_SIZE, DEFAULT_DRAG_SIZE)];
        }
        
        if (image != nil)
        {
            // select the center of the image as the drag origin
            // TODO http://javafx-jira.kenai.com/browse/RT-17629
            // would be nice to get this info from the Java layer,
            // so that we could adjust the drag image origin based on where in the src it was clicked on
            dragPoint.x -= ([image size].width/2.0f);
            dragPoint.y += ([image size].height/2.0f);
        }
        else
        {
            // last resource: "empty" image
            image = [[NSImage alloc] initWithSize:NSMakeSize(1.0f, 1.0f)];
        }
        [self->nsView dragImage:image at:dragPoint offset:NSZeroSize event:self->lastEvent pasteboard:pasteboard source:self->nsView slideBack:YES];
        
        // main thread blocked here until drag completes
        
        [GlassDragSource setDelegate:nil];
        
        [image release];
    }
    self->dragOperation = NSDragOperationNone;
}

- (BOOL)suppressMouseEnterExitOnMouseDown
{
    return YES;
}

- (void)sendJavaLockedEvent
{
    if ([NSThread isMainThread] == YES)
    {
        GET_MAIN_JENV;
        (*env)->CallVoidMethod(env, self->jView, jHandleViewEvent, com_sun_glass_events_ViewEvent_LOCKED);
        GLASS_CHECK_EXCEPTION(env);
    }
}

- (void)sendJavaUnlockedEvent
{
    if ([NSThread isMainThread] == YES)
    {
        GET_MAIN_JENV;
        (*env)->CallVoidMethod(env, self->jView, jHandleViewEvent, com_sun_glass_events_ViewEvent_UNLOCKED);
        GLASS_CHECK_EXCEPTION(env);
    }
}

/*
 The hierarchy for our view is view -> superview (host) -> window
 
 1. create superview (new host) for our view
 2. create fullscreen window with thew new superview
 3. create the background window (for fading out the desktop)
 4. remove our view from the window superview and insert it into the fullscreen window superview
 5. show our fullscreen window (and hide the original window)
 6. attach to it our background window (which will show it as well)
 7. zoom out our fullscreen window and at the same time animate the background window transparency
 8. enter fullscreen
 */
- (void)enterFullscreenWithAnimate:(BOOL)animate withKeepRatio:(BOOL)keepRatio withHideCursor:(BOOL)hideCursor
{
    NSScreen *screen = [[self->nsView window] screen];
    
    NSRect frame = [self->nsView bounds];
    NSPoint pointInWindowCoordinates = [self->nsView convertPoint:frame.origin toView:nil];
    // ensure that view's bounds is in unflipped coordinates
    pointInWindowCoordinates.y -= frame.size.height;
    NSPoint pointInScreenCoords = [self->parentWindow convertBaseToScreen:pointInWindowCoordinates];
    frame.origin = pointInScreenCoords;
    //NSLog(@"pointInScreenCoords: %.2f,%.2f", pointInScreenCoords.x, pointInScreenCoords.y);
    
    @try
    {
        // 1.
        self->fullscreenHost = [[GlassHostView alloc] initWithFrame:[self->nsView bounds]];
        [self->fullscreenHost setAutoresizesSubviews:YES];
        
        // 2.
        self->fullscreenWindow = [[GlassFullscreenWindow alloc] initWithContentRect:frame withHostView:self->fullscreenHost withView:self->nsView withScreen:screen withPoint:pointInScreenCoords];
        
        // 3.
        self->backgroundWindow = [[GlassBackgroundWindow alloc] initWithWindow:self->fullscreenWindow];
        
        [self->parentWindow disableFlushWindow];
        {
            NSWindow * nswindow = self->parentWindow;

            // 4.
            [self->nsView retain];
            [self->nsView removeFromSuperviewWithoutNeedingDisplay];
            [self->fullscreenHost addSubview:self->nsView];
            [self->nsView release];
            //[self->nsView destroyContext];
            if (nswindow && [nswindow isKindOfClass: [GlassWindow class]]) {
                GlassWindow * window = (GlassWindow*)nswindow;
                [window setFullscreenWindow: self->fullscreenWindow];
            }
            
            // 5.
            [self->fullscreenWindow makeKeyAndOrderFront:self->nsView];
            [self->fullscreenWindow makeMainWindow];
        }
        
        // 6.
        [self->fullscreenWindow addChildWindow:self->backgroundWindow ordered:NSWindowBelow];
        
        NSRect screenFrame = [screen frame];
        NSRect fullscreenFrame = [screen frame];
        if (keepRatio == YES)
        {
            float ratioWidth = (frame.size.width/screenFrame.size.width);
            float ratioHeight = (frame.size.height/screenFrame.size.height);
            if (ratioWidth > ratioHeight)
            {
                float ratio = (frame.size.width/frame.size.height);
                fullscreenFrame.size.height = fullscreenFrame.size.width / ratio;
                fullscreenFrame.origin.y = (screenFrame.size.height - fullscreenFrame.size.height) / 2.0f;
            }
            else
            {
                float ratio = (frame.size.height/frame.size.width);
                fullscreenFrame.size.width = fullscreenFrame.size.height / ratio;
                fullscreenFrame.origin.x = (screenFrame.size.width - fullscreenFrame.size.width) / 2.0f;
            }
        }
        
        // 7.
        [self->fullscreenWindow setFrame:fullscreenFrame display:YES animate:animate];
        
        // 8.
        //[self enterFullScreenMode:[self->fullscreenWindow screen] withOptions:nil];
        self->fullscreenWindow->displayID = [screen enterFullscreenAndHideCursor:hideCursor];
    }
    @catch (NSException *e)
    {
        NSLog(@"enterFullscreenWithAnimate caught exception: %@", e);
    }
}

- (void)exitFullscreenWithAnimate:(BOOL)animate
{
    @try
    {
        //[self exitFullScreenModeWithOptions:nil];
        [[self->fullscreenWindow screen] exitFullscreen:self->fullscreenWindow->displayID];
        
        NSRect frame = [self->parentHost bounds];
        frame.origin = [self->fullscreenWindow point];
        [self->fullscreenWindow setFrame:frame display:YES animate:animate];
        
        [self->fullscreenWindow disableFlushWindow];
        {
            [self->nsView retain];
            [self->nsView removeFromSuperviewWithoutNeedingDisplay];
            [self->parentHost addSubview:self->nsView];
            [self->nsView release];
            //[self->nsView destroyContext];
            
            [self->parentWindow setInitialFirstResponder:self->nsView];
            [self->parentWindow makeFirstResponder:self->nsView];

            if ([self->parentWindow isKindOfClass:[GlassWindow class]]) {
                GlassWindow * window = (GlassWindow*)self->parentWindow;
                [window setFullscreenWindow: nil];
            }
        }
        [self->fullscreenWindow enableFlushWindow];
        [self->parentWindow enableFlushWindow];
        
        [self->fullscreenWindow orderOut:nil];
        [self->fullscreenWindow close];
        self->fullscreenWindow = nil;
        [self->backgroundWindow orderOut:nil];
        [self->backgroundWindow close];
        self->backgroundWindow = nil;
    }
    @catch (NSException *e)
    {
        NSLog(@"exitFullscreenWithAnimate caught exception: %@", e);
    }
}

@end


@implementation GlassViewDelegate (hidden)
- (void)releaseTouches
{
    if (self->touches != nil)
    {
        [self->touches release];
        self->touches = nil;
    }
}
@end
