//
//  ICeCoffEEWebKit.m
//  ICeCoffEE APE
//
//  Created by Nicholas Riley on Sun Jan 19 2003.
//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
//

#import "ICeCoffEEWebKit.h"
#import "ICeCoffEEWebPolicyDelegate.h"
#import "ICeCoffEEParser.h"
#import "ICeCoffEETrigger.h"
#import <WebKit/WebKit.h>
#import <unistd.h>

// Safari 3.1 and earlier (pre-r31014)
// eliminated in http://bugs.webkit.org/show_bug.cgi?id=17640
// but convertNSRangeToDOMRange: (temporarily) restored in r39145
@interface WebCoreFrameBridge : NSObject
- (DOMRange *)convertNSRangeToDOMRange:(NSRange)range;
- (NSString *)stringForRange:(DOMRange *)range;
@end

// r31014 and later; likely to move again
@interface WebFrame (WebFrameInternal)
- (DOMRange *)_convertNSRangeToDOMRange:(NSRange)range;
- (NSString *)_stringForRange:(DOMRange *)range;
@end

// XXX WebHTMLView is going away
@interface WebHTMLView : NSObject

- (NSRect)selectionRect;
- (DOMRange *)_documentRange;

- (WebView *)_webView;
- (WebCoreFrameBridge *)_bridge; // moved from WebNSViewExtras in r14032; removed in r31014 then (temporarily) restored in r39145
- (WebFrame *)_frame; // moved from WebNSViewExtras in r14032

@end

// from WebViewPrivate.h (will become public, part of WebViewEditing)
@interface WebView (webViewGrammarChecking)
- (BOOL)isGrammarCheckingEnabled;
- (void)setGrammarCheckingEnabled:(BOOL)flag;
@end

@implementation ICeCoffEEWebKit

- (NSMenu *)menuForEvent:(NSEvent *)e;
{
    NSMenu *myMenu = [super menuForEvent: e];
    return ICCF_MenuForEvent(self, myMenu, e);
}

static NSEvent *downEvent = nil;
static id /* (WebPolicyDelegate) */ policyDelegate;
static id /* DOMRange */ selectedRange;

static BOOL ICCF_DOMParentOfClass(DOMNode *node, Class cls) {
    do {
	if ([node isKindOfClass: cls])
	    return YES;
    } while ( (node = [node parentNode]) != nil);
    
    return NO;
}
#define ICCF_NodeIs(elt) [clickedNode isKindOfClass: NSClassFromString(@"DOMHTML"elt"Element")]
#define ICCF_DOMParentIs(elt) ICCF_DOMParentOfClass(clickedNode, NSClassFromString(@"DOMHTML"elt"Element"))

- (void)mouseDown:(NSEvent *)e;
{
    [downEvent release]; downEvent = nil;
    if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(e)) {
	if ([self respondsToSelector: @selector(_webView)]) {
	    WebView *webView = [(WebHTMLView *)self _webView];
	    
	    // check that the Command-click isn't being used for discontiguous selection
	    NSPoint viewClickPt = [webView convertPoint: [e locationInWindow] fromView: nil];
	    NSDictionary *elementDict = [webView elementAtPoint: viewClickPt];
	    DOMNode *clickedNode = [elementDict objectForKey: @"WebElementDOMNode"];
	    if (clickedNode != nil && ICCF_NodeIs("Select")) {
		[super mouseDown: e];
		return;
	    }
	    
	    // save selection: it may be deselected on super mouseDown
	    selectedRange = [[webView selectedDOMRange] retain];
	    
	    // stop any URL launching from happening
	    if ([webView isEditable]) {
		policyDelegate = [[webView policyDelegate] retain];
		[webView setPolicyDelegate: [ICeCoffEEWebPolicyDelegate sharedDelegate]];
	    }
	    
	    downEvent = [e retain];
	}
    }
    [super mouseDown: e];
}

- (void)mouseUp:(NSEvent *)e;
{
    [super mouseUp: e];

    if (downEvent == nil)
	return;
    
    WebView *webView = [(WebHTMLView *)self _webView];
    BOOL webViewIsEditable = [webView isEditable];
    BOOL elementIsEditable = YES; // don't restore
    BOOL isContinuousSpellCheckingEnabled = NO, isGrammarCheckingEnabled = NO;

    if (webViewIsEditable) {
	[webView setPolicyDelegate: policyDelegate];
	[policyDelegate release]; policyDelegate = nil;
    }

    NSPoint downPt = [downEvent locationInWindow];
    NSPoint upPt = [e locationInWindow];
    if (abs(downPt.x - upPt.x) > kICHysteresisPixels && abs(downPt.y - upPt.y) > kICHysteresisPixels)
	return;

    @try {
	NSPoint viewClickPt = [webView convertPoint: downPt fromView: nil];
	NSDictionary *elementDict = [webView elementAtPoint: viewClickPt];
	ICLog(@"elementDict: %@", elementDict);

	if ([elementDict count] == 0)
	    return;

	elementIsEditable = [[elementDict objectForKey: @"WebElementIsContentEditableKey"] boolValue];
	if (!elementIsEditable) {
	    NSAssert(!webViewIsEditable, @"Internal error: uneditable element inside editable WebView");
	    [webView setEditable: YES];
	
	    // don't want spelling/grammar marks to persist when view is made uneditable again
	    if ([webView respondsToSelector: @selector(isContinuousSpellCheckingEnabled)] &&
		(isContinuousSpellCheckingEnabled = [webView isContinuousSpellCheckingEnabled])) {
		if ([webView respondsToSelector: @selector(setContinuousSpellCheckingEnabled:)])
		    [webView setContinuousSpellCheckingEnabled: NO];
		else
		    isContinuousSpellCheckingEnabled = NO; // don't restore
	    }

	    if ([webView respondsToSelector: @selector(isGrammarCheckingEnabled)] &&
		(isGrammarCheckingEnabled = [webView isGrammarCheckingEnabled])) {
		if ([webView respondsToSelector: @selector(setGrammarCheckingEnabled:)])
		    [webView setGrammarCheckingEnabled: NO];
		else
		    isGrammarCheckingEnabled = NO; // don't restore
	    }
	}

	DOMNode *clickedNode = [elementDict objectForKey: @"WebElementDOMNode"];
	BOOL elementIsHTMLInput = ICCF_NodeIs("Input");
	
	if (!elementIsEditable && (elementIsHTMLInput || ICCF_DOMParentIs("Button") || ICCF_NodeIs("FieldSet"))) {
	    ICLog(@"got an uneditable form field (e.g., button)");
	    return;
	}

	id link = [elementDict objectForKey: @"WebElementLinkURL"];
	NSString *url = [link isKindOfClass: [NSURL class]] ? [link absoluteString] : nil;
	
	ICCF_StartIC();
	
	id /* DOMRange */ domRange = nil;
	
	if (url != nil) {
	    ICLog(@"got a link");
	    if (!elementIsEditable) {
		return;
	    }
	    // XXX handle existing selection
	    domRange = [webView selectedDOMRange];
	    [domRange selectNode: clickedNode];
	    [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
	} else {
	    if (!elementIsEditable) // may have become deselected in mouseDown
		[webView setSelectedDOMRange: selectedRange affinity: NSSelectionAffinityDownstream];
	    
	    NSRange range = [ICeCoffEETrigger rangeForEvent: downEvent onTarget: (NSView<NSTextInput> *)self];
	    if (range.location == NSNotFound)
		return;

	    WebFrame *frame = [(WebHTMLView *)self _frame];
	    WebCoreFrameBridge *bridge = nil;
	    if ([self respondsToSelector: @selector(_bridge)])
		bridge = [(WebHTMLView *)self _bridge];

	    // XXX limit to a reasonable size
	    NSString *s;
	    if (elementIsHTMLInput || ICCF_NodeIs("TextArea") || (elementIsEditable && !webViewIsEditable)) {
		// for form fields, range will be field-relative rather than document-relative
		ICLog(@"Using attributedSubstringFromRange:");
		s = [[(NSView<NSTextInput> *)self attributedSubstringFromRange: NSMakeRange(0, UINT_MAX)] string];
	    } else { // sometimes attributedSubstringFromRange: returns nil
		ICLog(@"Using stringForRange:");
		domRange = [(WebHTMLView *)self _documentRange];
		if ([frame respondsToSelector: @selector(_stringForRange:)])
		    s = [frame _stringForRange: domRange];
		else if (bridge != nil && [bridge respondsToSelector: @selector(stringForRange:)])
		    s = [bridge stringForRange: domRange];
		else {
		    ICLog(@"WebKit has neither -[WebFrame _stringForRange:] nor -[WebCoreFrameBridge stringForRange:]");
		    return; // WebKit too new?
		}
	    }
	    
	    if (bridge == nil && ![frame respondsToSelector: @selector(_convertNSRangeToDOMRange:)]) {
		ICLog(@"WebKit has no bridge and no -[WebFrame _convertNSRangeToDOMRange:]");
		return; // WebKit too new?
	    }
		
	    if (range.length == 0) {
		range.length = 1;
		range = ICCF_URLEnclosingRange(s, range);
		domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
				    [frame _convertNSRangeToDOMRange: range];
		[webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
	    } else {
		domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
				    [frame _convertNSRangeToDOMRange: range];
	    }
	    
	    url = [s substringWithRange: range];
	}	
	
	if (ICCF_LaunchURL(url, ICCF_KeyboardAction(downEvent)) && ICCF_prefs.textBlinkEnabled && domRange != nil) {
	    NSRect selectionRect = [(WebHTMLView *)self selectionRect];
	    for (int i = 0 ; i < ICCF_prefs.textBlinkCount ; i++) {
		[webView setSelectedDOMRange: nil affinity: NSSelectionAffinityDownstream];
		[self setNeedsDisplayInRect: selectionRect];
		[self display];
		usleep(kICBlinkDelayUsecs);
		[webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
		[self setNeedsDisplayInRect: selectionRect];
		[self display];
		usleep(kICBlinkDelayUsecs);
	    }
	}
    } @catch (NSException *e) {
	ICCF_HandleException(e, downEvent);
    } @finally {
	[selectedRange release]; selectedRange = nil;
	[downEvent release]; downEvent = nil;
	if (!elementIsEditable && !webViewIsEditable) {
	    [webView setEditable: NO];
	    if (isContinuousSpellCheckingEnabled)
		[webView setContinuousSpellCheckingEnabled: YES];
	    if (isGrammarCheckingEnabled)
		[webView setGrammarCheckingEnabled: YES];
	}
	ICCF_StopIC();
    }
}

@end
