/*
WebKitEx.h

Author: Makoto Kinoshita

Copyright 2004 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 <objc/objc-runtime.h>

#import "WebKitEx.h"

@implementation WebPreferences (WebPreferencesKVCSupport)

- (BOOL)isPlugInsEnabled
{
    return [self arePlugInsEnabled];
}

@end

#if 0

@implementation WebViewEx

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    [self poseAsClass:[WebView class]];
    [pool release];
}

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (!self) {
        return self;
    }
    
    // Register bookmark type for dragging
    NSArray*    types;
    types = [NSArray arrayWithObjects:SRBookmarkPboardType, nil];
    [self registerForDraggedTypes:types];
    
    return self;
}

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    
    // For bookmark type
    if ([types containsObject:SRBookmarkPboardType]) {
        return NSDragOperationCopy;
    }
    
    return [super draggingEntered:info];
}

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    
    // For bookmark type
    if ([types containsObject:SRBookmarkPboardType]) {
        return NSDragOperationCopy;
    }
    
    return [super draggingUpdated:info];
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    
    // For bookmark type
    if ([types containsObject:SRBookmarkPboardType]) {
        // Read bookmakrs
        NSArray*    bookmarks;
        bookmarks = SRReadBookmarksFromPasteboard(pboard);
        if (bookmarks) {
            // Get all bookmarks in folder
            NSMutableArray* extractedBookmarks;
            extractedBookmarks = [NSMutableArray array];
            
            NSEnumerator*   enumerator;
            SRBookmark*     bookmark;
            enumerator = [bookmarks objectEnumerator];
            while (bookmark = [enumerator nextObject]) {
                if ([[bookmark isFolder] boolValue]) {
                    [extractedBookmarks addObjectsFromArray:[bookmark children]];
                }
                else {
                    [extractedBookmarks addObject:bookmark];
                }
            }
            
            // Open bookmarks in tabs
            if ([extractedBookmarks count] > 1) {
                id  windowController;
                windowController = [[self window] windowController];
                if ([windowController isKindOfClass:[SRMainWindowController class]]) {
                    [windowController openInTabsBookmarks:extractedBookmarks];
                }
                
                return YES;
            }
            
            // Open bookmark
            if ([extractedBookmarks count] == 1) {
                id  windowController;
                windowController = [[self window] windowController];
                if ([windowController isKindOfClass:[SRMainWindowController class]]) {
                    [windowController openBookmark:[extractedBookmarks objectAtIndex:0]];
                }
                
                return YES;
            }
        }
    }
    
    return [super performDragOperation:info];
}

- (void)concludeDragOperation:(id<NSDraggingInfo>)info
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    
    // For bookmark type
    if ([types containsObject:SRBookmarkPboardType]) {
        // Do nothing
        // To avoid WebView default performance
        return;
    }
    
    [super concludeDragOperation:info];
}

@end

#pragma mark -
//--------------------------------------------------------------//
// WebClipViewEx
//--------------------------------------------------------------//

#if 0
@implementation WebClipViewEx

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    [self poseAsClass:[WebClipView class]];
    [pool release];
}

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    // Get UI delegate of web view
    NSView*     view = self;
    WebView*    webView = nil;
    id          delegate = nil;
    while (view) {
        view = [view superview];
        if ([view isKindOfClass:[WebView class]]) {
            webView = (WebView*)view;
            delegate = [webView UIDelegate];
            break;
        }
    }
    if (delegate) {
        NSArray*    items;
        items = [delegate webView:webView 
                contextMenuItemsForElement:[NSDictionary dictionary] 
                defaultMenuItems:[NSArray array]];
        if (!items || [items count] == 0) {
            return nil;
        }
        
        // Create menu
        NSMenu* menu;
        menu = [[NSMenu alloc] initWithTitle:@""];
        [menu autorelease];
        
        NSEnumerator*   enumerator;
        NSMenuItem*     item;
        enumerator = [items objectEnumerator];
        while (item = [enumerator nextObject]) {
            [menu addItem:item];
        }
        
        return menu;
    }
    
    return [super menuForEvent:event];
}

@end
#endif

#pragma mark -
//--------------------------------------------------------------//
// WebImageViewEx_pseudo
//--------------------------------------------------------------//

NSString*    SRWebImageSizeChangeNotification = @"SRWebImageSizeChangeNotification";

@implementation WebImageViewEx_pseudo

static NSMutableDictionary* _webImageViewAutoResizeModeDict = nil;

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    
    struct objc_class*  newClass;
    struct objc_class*  rootClass;
    struct objc_class*  superClass;
    struct objc_class*  metaClass;
    
    // Get super class WebImageView
    superClass = (struct objc_class*)objc_lookUpClass("WebImageView");
    if (superClass && objc_lookUpClass("WebImageViewEx") == NULL) {
        // Get root class
        rootClass = superClass;
        while (rootClass->super_class != nil) {
            rootClass = rootClass->super_class;
        }
        
        // Allocate memory for new class
        newClass = calloc(2, sizeof(struct objc_class));
        metaClass = &newClass[1];
        
        // Setup class
        newClass->isa = metaClass;
        newClass->info = CLS_CLASS;
        metaClass->info = CLS_META;
        newClass->name = "WebImageViewEx";
        metaClass->name = newClass->name;
        
        // Setup methods
        newClass->methodLists = calloc(1, sizeof(struct objc_method_list*));
        metaClass->methodLists = calloc(1, sizeof(struct objc_method_list*));
        
        void*   iterator = 0;
        struct objc_method_list*    methodList;
        
        // Add instance methods
        methodList = class_nextMethodList(self, &iterator);
        while (methodList != NULL) {
            class_addMethods(newClass, methodList);
            methodList = class_nextMethodList(self, &iterator);
        }
        
        // Add class methods
        methodList = class_nextMethodList([self class]->isa, &iterator);
        while (methodList != NULL) {
            class_addMethods(metaClass, methodList);
            methodList = class_nextMethodList(self, &iterator);
        }
        
        // Setup super class
        newClass->super_class = superClass;
        metaClass->super_class = superClass->isa;
        metaClass->isa = (void*)rootClass->isa;
        
        newClass->instance_size = superClass->instance_size;
        newClass->ivars = NULL;
        metaClass->instance_size = metaClass->instance_size;
        metaClass->ivars = NULL;
        
        // Register new class
        objc_addClass(newClass);
        
        // Pose as WebViewImage
        class_poseAs(newClass, superClass);
    }
    
    [pool release];
}

+ (void)initialize
{
    if (!_webImageViewAutoResizeModeDict) {
        _webImageViewAutoResizeModeDict = [[NSMutableDictionary dictionary] retain];
    }
}

- (WebView*)_webView
{
    return [self performSelector:@selector(webView)];
}

+ (NSArray*)_supportedImageMIMETypes
{
    return [self performSelector:@selector(supportedImageMIMETypes)];
}

- (WebImageRepresentation*)_rep
{
    id  value;
    object_getInstanceVariable(self, "rep", (void**)&value);
    return value;
}

- (BOOL)isViewingImageFile
{
    // Get MIME type from response
    WebDataSource*  mainDataSource;
    NSString*       MIMEType;
    mainDataSource = [[[self _webView] mainFrame] dataSource];
    if (!mainDataSource) {
        return NO;
    }
    MIMEType = [[mainDataSource response] MIMEType];
    if (!MIMEType) {
        return NO;
    }
    
    // Check MIME type
    return [[[self class] _supportedImageMIMETypes] containsObject:MIMEType];
}

- (NSSize)_imageSize
{
    // Get original image size
    WebImageRenderer*   webImageRenderer;
    webImageRenderer = [[self _rep] image];
    
    // Get WebImageData for Tiger
    WebImageData*   webImageData;
    object_getInstanceVariable(webImageRenderer, "imageData", (void**)&webImageData);
    if (webImageData) {
        CGSize  size;
        size = [webImageData size];
        return NSMakeSize(size.width, size.height);
    }
    
    // Get WebInternalImage for Panther
    id  webInternalImage;
    webInternalImage = [webImageRenderer image];
    if (webInternalImage) {
        NSImageRep* imageRep;
        imageRep = [webInternalImage bestRepresentationForDevice:nil];
        if (imageRep) {
            return NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
        }
        //return [webInternalImage size];
    }
    
    return NSZeroSize;
}

- (BOOL)_isImageSizeLargerThanVisibleSize
{
    NSSize      imageSize;
    NSSize      visibleSize;
    imageSize = [self _imageSize];
    visibleSize = [[self _webView] visibleRect].size;
    
    return imageSize.width > visibleSize.width || 
        imageSize.height > visibleSize.height;
}

- (void)_setFrameSizeToWindow
{
    // Get original image size
    NSSize  imageSize;
    imageSize = [self _imageSize];
    
    // Get web view visible rect size
    NSSize  visibleSize;
    visibleSize = [[self _webView] visibleRect].size;
    
    // Calculate scaled size
    NSSize  scaledSize = imageSize;
    if (imageSize.width > visibleSize.width || 
        imageSize.height > visibleSize.height)
    {
        float   ratioW, ratioH;
        ratioW = visibleSize.width / imageSize.width;
        ratioH = visibleSize.height / imageSize.height;
        if (ratioW < ratioH) {
            scaledSize.width = visibleSize.width;
            scaledSize.height = imageSize.height * ratioW;
        }
        else {
            scaledSize.width = imageSize.width * ratioH;
            scaledSize.height = visibleSize.height;
        }
    }
    
    // Set Size
    NSSize  frameSize;
    frameSize = [self frame].size;
    if (!NSEqualSizes(scaledSize, imageSize) || 
        !NSEqualSizes(scaledSize, frameSize))
    {
        id  webImageRenderer;
        webImageRenderer = [[self _rep] image];
        [webImageRenderer resize:scaledSize];
        [self setFrameSize:scaledSize];
        [self setNeedsDisplay:YES];
        
        // Post notification
        [[NSNotificationCenter defaultCenter] 
                postNotificationName:SRWebImageSizeChangeNotification object:self];
    }
}

- (void)_resetFrameSize
{
    // Get original image size
    NSSize  imageSize;
    imageSize = [self _imageSize];
    
    // Reset size
    id  webImageRenderer;
    webImageRenderer = [[self _rep] image];
    [webImageRenderer resize:imageSize];
    [self setFrameSize:imageSize];
    [self setNeedsDisplay:YES];
    
    // Post notification
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRWebImageSizeChangeNotification object:self];
}

- (BOOL)autoResizeMode
{
    NSNumber*   mode;
    mode = [_webImageViewAutoResizeModeDict 
            objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
    if (!mode) {
        // Default is YES
        return YES;
    }
    
    return [mode boolValue];
}

- (void)setAutoResizeMode:(BOOL)flag
{
    [_webImageViewAutoResizeModeDict setObject:[NSNumber numberWithBool:flag] 
            forKey:[NSNumber numberWithUnsignedInt:(unsigned int)self]];
    
    // Set frame size
    if (flag) {
        [self _setFrameSizeToWindow];
    }
    else {
        [self _resetFrameSize];
    }
}

- (void)mouseDown:(NSEvent*)event
{
    // Toggle auto resize mode
    BOOL    mode;
    mode = [self autoResizeMode];
    mode = !mode;
    [self setAutoResizeMode:mode];
    
    // Get super method
    struct  objc_method*    superMethod;
    superMethod = class_getInstanceMethod([self superclass], _cmd);
    
    // Instaed of [super mouseDown:event], use IMP directly
    superMethod->method_imp(self, _cmd, event);
}

- (void)drawRect:(NSRect)rect
{
    // Get super method
    struct  objc_method*    superMethod;
    superMethod = class_getInstanceMethod([self superclass], _cmd);
    
    // Check auto resize mode
    BOOL    mode;
    mode = [self autoResizeMode];
    if (!mode || ![self _isImageSizeLargerThanVisibleSize]) {
        // Instaed of [super drawRect:rect], use IMP directly
        superMethod->method_imp(self, _cmd, rect);
        
        return;
    }
    
    // Resize itself
    [self _setFrameSizeToWindow];
    
    // Instaed of [super drawRect:rect], use IMP directly
    superMethod->method_imp(self, _cmd, rect);
}

@end

#pragma mark -

@implementation WebFrameEx

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    
    [self poseAsClass:[WebFrame class]];
    
    [pool release];
}

- (void)_transitionToCommitted:(NSDictionary*)pageCache
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    if (![defaults boolForKey:SRUsePageTransitionEffect]) {
        [super _transitionToCommitted:pageCache];
        return;
    }
    
    id  webView;
    webView = [self webView];
    
    if ([webView mainFrame] == self) {
        if ([self _state] == WebFrameStateProvisional) {
            if ([webView respondsToSelector:@selector(prepareForTransition)]) {
                [webView prepareForTransition];
            }
        }
    }
    
    [super _transitionToCommitted:pageCache];
}

- (void)_goToItem:(WebHistoryItem*)item withLoadType:(WebFrameLoadType)type
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    if (![defaults boolForKey:SRUsePageTransitionEffect]) {
        [super _goToItem:item withLoadType:type];
        return;
    }
    
    id  webView;
    webView = [self webView];
    
    if ([webView respondsToSelector:@selector(setBackOrForward:) ]) {
        // Find item in the back and forward list
        WebBackForwardList* list;
        NSArray*            items;
        int                 backOrForward = 0;
        list = [[self webView] backForwardList];
        items = [list backListWithLimit:[list backListCount]];
        if ([items containsObject:item]) {
            // This is back
            backOrForward = -1;
        }
        else {
            items = [list forwardListWithLimit:[list forwardListCount]];
            if ([items containsObject:item]) {
                // This is forward
                backOrForward = 1;
            }
        }
        [webView setBackOrForward:backOrForward];
    }
    
    [super _goToItem:item withLoadType:type];
}

- (void)_transitionToLayoutAcceptable
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    if (![defaults boolForKey:SRUsePageTransitionEffect]) {
        [super _transitionToLayoutAcceptable];
        return;
    }
    
    id  webView;
    webView = [self webView];
    
    if ([webView mainFrame] == self) {
        if ([self _state] == WebFrameStateCommittedPage) {
            int transitionType = SRWebViewNoneTransition;
            WebFrameLoadType    loadType;
            loadType = (WebFrameLoadType)[self _loadType];
            switch (loadType) {
            case WebFrameLoadTypeStandard:
            case WebFrameLoadTypeForward: {
                transitionType = SRWebViewPageCurlTransition;
                break;
            }
            case WebFrameLoadTypeBack: {
                transitionType = SRWebViewPareCurlBackTransition;
                break;
            }
            case WebFrameLoadTypeIndexedBackForward: {
                if ([webView respondsToSelector:@selector(backOrForward)]) {
                    int backOrForward;
                    backOrForward = [webView backOrForward];
                    if (backOrForward == -1) {
                        transitionType = SRWebViewPareCurlBackTransition;
                    }
                    else {
                        transitionType = SRWebViewPageCurlTransition;
                    }
                }
                break;
            }
            case WebFrameLoadTypeReload: {
                transitionType = SRWebViewCopyMachineTransition;
                break;
            }
            }
            [super _transitionToLayoutAcceptable];
            
            if ([webView respondsToSelector:@selector(startTransitionOfType:)]) {
                [webView startTransitionOfType:transitionType];
            }
            return;
        }
    }
    [super _transitionToLayoutAcceptable];
}

@end

#pragma mark -

@implementation WebFrame (FindFrame)

- (WebFrame*)srFindFrameNamed:(NSString*)frameName
{
    // Get name
    NSString*   name;
    name = [self name];
    
    // Compare name
    if ([name isEqualToString:frameName]) {
        return self;
    }
    
    // Find in children
    NSArray*        children;
    NSEnumerator*   enumerator;
    WebFrame*       frame;
    children = [self childFrames];
    enumerator = [children objectEnumerator];
    while (frame = [enumerator nextObject]) {
        WebFrame*   childFrame;
        childFrame = [frame srFindFrameNamed:frameName];
        if (childFrame) {
            return childFrame;
        }
    }
    
    return nil;
}

@end
#endif

#if 0
#pragma mark -

@implementation WebPreferences (WebPreferencesKVCSupport)

- (BOOL)isPlugInsEnabled
{
    return [self arePlugInsEnabled];
}

@end

#pragma mark -

@implementation DOMNode (SRExtension)

- (DOMNode*)firstChildOfType:(unsigned int)type
{
    return nil;
}

- (DOMNode*)firstChildOfNodeName:(NSString*)nodeName
{
    DOMNodeList*    children;
    children = [self childNodes];
    if (!children) {
        return nil;
    }
    
    unsigned long   i;
    for (i = 0; i < [children length]; i++) {
        DOMNode*    child;
        child = [children item:i];
        if (child && [[child nodeName] isEqualToString:nodeName]) {
            return child;
        }
    }
    
    return nil;
}

- (DOMNode*)firstChildOfClass:(Class)nodeClass
{
    DOMNodeList*    children;
    children = [self childNodes];
    if (!children) {
        return nil;
    }
    
    unsigned long   i;
    for (i = 0; i < [children length]; i++) {
        DOMNode*    child;
        child = [children item:i];
        if (child && [child isKindOfClass:nodeClass]) {
            return child;
        }
    }
    
    return nil;
}

- (BOOL)isDescendant:(DOMNode*)node
{
    while (node = [node parentNode]) {
        if (node == self) {
            return YES;
        }
    }
    
    return NO;
}

@end

#pragma mark - 

@implementation WebBasePluginPackage_pseudo

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    
    // Pose as WebBasePluginPackage class
    SRPoseAsClass(self, @"WebBasePluginPackage", @"WebBasePluginPackageEx");
    
    [pool release];
}

+ (id)pluginWithPath:(NSString*)pluginPath
{
    // Disable Adobe reader
    if ([pluginPath isEqualToString:@"/Library/Internet Plug-Ins/AdobePDFViewer.plugin"]) {
        return nil;
    }
    
    // Instaed of super, use IMP directly
    struct  objc_method*    superMethod;
    superMethod = class_getClassMethod([self superclass], _cmd);
    return superMethod->method_imp(self, _cmd, pluginPath);
}

@end
#endif
