/*
HMWebHTMLView.m

Author: Makoto Kinoshita

Copyright 2004-2006 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import "HMWebKitDefaultKeys.h"
#import "HMWebHTMLView.h"

static IMP  _initWithFrame_ = NULL;
static IMP  _dealloc_ = NULL;
static IMP  _mouseDown_ = NULL;
static IMP  _mouseUp_ = NULL;
static IMP  _mouseDragged_ = NULL;
static IMP  _otherMouseDown_ = NULL;
static IMP  _otherMouseUp_ = NULL;
static IMP  _drawRect_ = NULL;

static NSMutableDictionary* _contextMenuTimerDict = nil;

static void _cancelContextMenuTimer(
        id self)
{
    // Cancel context menu timer
    NSTimer*    timer;
    timer = [_contextMenuTimerDict 
            objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
    if (timer) {
        [timer invalidate];
        [_contextMenuTimerDict 
                removeObjectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

static id initWithFrame_(
        id self, SEL _cmd, NSRect rect)
{
    self = _initWithFrame_(self, _cmd, rect);
    if (!self) {
        return nil;
    }
    
    // Initialize static variables
    if (!_contextMenuTimerDict) {
        _contextMenuTimerDict = [[NSMutableDictionary dictionary] retain];
    }
    
    // Register key value observation
    [[NSUserDefaults standardUserDefaults] addObserver:self 
            forKeyPath:HMWebKitAntiAliasing 
            options:NSKeyValueObservingOptionNew 
            context:NULL];
    
    return self;
}

static id dealloc_(
        id self, SEL _cmd)
{
    // Cancel context menu timer
    _cancelContextMenuTimer(self);
    
    // Remove observer
    [[NSUserDefaults standardUserDefaults] 
            removeObserver:self forKeyPath:HMWebKitAntiAliasing];
    
    _dealloc_(self, _cmd);
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Mouse handling --
//--------------------------------------------------------------//

static id mouseDown_(
        id self, SEL _cmd, NSEvent* event)
{
    // Start context menu timer
    if ([[NSUserDefaults standardUserDefaults] boolForKey:HMWebKitOneClickNavigation]) {
        if ([event clickCount] == 1) {
            // Check with subviews
            NSPoint         point;
            NSEnumerator*   enumerator;
            NSView*         subview;
            point = [self convertPoint:[event locationInWindow] fromView:nil];
            enumerator = [[self subviews] objectEnumerator];
            while (subview = [enumerator nextObject]) {
                if (NSPointInRect(point, [subview frame])) {
                    goto done;
                }
            }
            
            // Create event info
            NSDictionary*   eventInfo;
            eventInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                    [NSValue valueWithPoint:[event locationInWindow]], @"locationInWindow", 
                    [NSNumber numberWithUnsignedInt:[event modifierFlags]], @"modifierFlags", 
                    [NSNumber numberWithFloat:[event timestamp]], @"timestamp", 
                    [NSNumber numberWithInt:[event windowNumber]], @"windowNumber", 
                    [NSNumber numberWithInt:[event eventNumber]], @"eventNumber", 
                    [NSNumber numberWithFloat:[event pressure]], @"pressure", 
                    nil];
            
            // Create timer
            NSTimer*    timer;
            timer = [_contextMenuTimerDict 
                    objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
            if (timer) {
                [timer invalidate];
            }
            timer = [NSTimer scheduledTimerWithTimeInterval:0.3f 
                    target:self selector:@selector(contextMenuTimerFired:) userInfo:eventInfo repeats:NO];
            
            // Set timer
            [_contextMenuTimerDict 
                    setObject:timer forKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
        }
    }
    
done:
    return _mouseDown_(self, _cmd, event);
}

static id mouseUp_(
        id self, SEL _cmd, NSEvent* event)
{
    // Cancel context menu timer
    _cancelContextMenuTimer(self);
    
    return _mouseUp_(self, _cmd, event);
}

static id mouseDragged_(
        id self, SEL _cmd, NSEvent* event)
{
    // Cancel context menu timer
    _cancelContextMenuTimer(self);
    
    return _mouseDragged_(self, _cmd, event);
}

static id otherMouseDown_(
        id self, SEL _cmd, NSEvent* event)
{
    // Create left mouse down with command modifier event
    NSEvent*    leftDownEvent;
    leftDownEvent = [NSEvent mouseEventWithType:NSLeftMouseDown 
            location:[event locationInWindow] 
            modifierFlags:([event modifierFlags] | NSCommandKeyMask) 
            timestamp:[event timestamp] 
            windowNumber:[event windowNumber] 
            context:[event context] 
            eventNumber:[event eventNumber] 
            clickCount:[event clickCount] 
            pressure:[event pressure]];
    
    [self mouseDown:leftDownEvent];
    
    return nil;
}

static id otherMouseUp_(
        id self, SEL _cmd, NSEvent* event)
{
    // Create left mouse up with command modifier event
    NSEvent*    leftUpEvent;
    leftUpEvent = [NSEvent mouseEventWithType:NSLeftMouseUp 
            location:[event locationInWindow] 
            modifierFlags:([event modifierFlags] | NSCommandKeyMask) 
            timestamp:[event timestamp] 
            windowNumber:[event windowNumber] 
            context:[event context] 
            eventNumber:[event eventNumber] 
            clickCount:[event clickCount] 
            pressure:[event pressure]];
    
    [self mouseUp:leftUpEvent];
    
    return nil;
}

static id contextMenuTimerFired_(
        id self, SEL _cmd, NSTimer* timer)
{
    // Get event info
    NSDictionary*   eventInfo;
    eventInfo = [timer userInfo];
    
    // Make NSRightMouseDown event
    NSEvent*    rightMouseEvent;
    rightMouseEvent = [NSEvent mouseEventWithType:NSRightMouseDown 
            location:[[eventInfo objectForKey:@"locationInWindow"] pointValue] 
            modifierFlags:[[eventInfo objectForKey:@"modifierFlags"] unsignedIntValue] 
            timestamp:[[eventInfo objectForKey:@"timestamp"] floatValue] 
            windowNumber:[[eventInfo objectForKey:@"windowNumber"] intValue] 
            context:[NSGraphicsContext currentContext] 
            eventNumber:[[eventInfo objectForKey:@"eventNumber"] intValue] 
            clickCount:1 
            pressure:[[eventInfo objectForKey:@"pressure"] floatValue]];
    
    // Show context menu
    [self rightMouseDown:rightMouseEvent];
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Drawing --
//--------------------------------------------------------------//

static id drawRect_(
        id self, SEL _cmd, NSRect rect)
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get current context
    NSGraphicsContext*  context;
    context = [NSGraphicsContext currentContext];
    
    // Save context
    [context saveGraphicsState];
    
    // Anti-aliasing
    BOOL    antiAliasing;
    antiAliasing = [defaults boolForKey:HMWebKitAntiAliasing];
    if (antiAliasing != [context shouldAntialias]) {
        [context setShouldAntialias:antiAliasing];
    }
    
    // Invoke orignal method
    _drawRect_(self, _cmd, rect);
    
    // Restore context
    [context restoreGraphicsState];
    
    return nil;
}

static id observeValueForKeyPath_ofObject_change_context_(
        id self, SEL _cmd, NSString* keyPath, id object, NSDictionary* change, void* context)
{
    // For NSUserDefault
    if (object == [NSUserDefaults standardUserDefaults]) {
        // Universal access preferences
        if ([keyPath isEqualToString:HMWebKitAntiAliasing]) {
            [self setNeedsDisplay:YES];
        }
    }
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Posing --
//--------------------------------------------------------------//

void HMWebHTMLViewLoad()
{
    static BOOL _isInitialized = NO;
    if (_isInitialized) {
        return;
    }
    _isInitialized = YES;
    
    // Get WebHTMLView class
    Class   klass;
    klass = NSClassFromString(@"WebHTMLView");
    
    // Swap methods
    HMSwapMethod(klass, @selector(initWithFrame:), 
            (IMP)initWithFrame_, &_initWithFrame_);
    HMSwapMethod(klass, @selector(dealloc), 
            (IMP)dealloc_, &_dealloc_);
    HMSwapMethod(klass, @selector(mouseDown:), 
            (IMP)mouseDown_, &_mouseDown_);
    HMSwapMethod(klass, @selector(mouseUp:), 
            (IMP)mouseUp_, &_mouseUp_);
    HMSwapMethod(klass, @selector(mouseDragged:), 
            (IMP)mouseDragged_, &_mouseDragged_);
    HMSwapMethod(klass, @selector(otherMouseDown:), 
            (IMP)otherMouseDown_, &_otherMouseDown_);
    HMSwapMethod(klass, @selector(otherMouseUp:), 
            (IMP)otherMouseUp_, &_otherMouseUp_);
    HMSwapMethod(klass, @selector(drawRect:), 
            (IMP)drawRect_, &_drawRect_);
    
    // Add methods
    SEL     selectors[2];
    char*   types[2];
    IMP     imps[2];
    selectors[0] = @selector(observeValueForKeyPath:ofObject:change:context:);
    types[0] = "v24@0:4@8@12@16^v20";
    imps[0] = (IMP)observeValueForKeyPath_ofObject_change_context_;
    selectors[1] = @selector(contextMenuTimerFired:);
    types[1] = "v@:@";
    imps[1] = (IMP)contextMenuTimerFired_;
    
    HMAddMethods(klass, 2, selectors, types, imps);
}
