/*
SRBookmarkController.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 "SRBookmark.h"
#import "SRBookmarkController.h"

#import "SRAppController.h"
#import "SRBookmarkController.h"
#import "SRBrowserController.h"

#import "SRBookmarkContextMenu.h"

#import "SRConstants.h"
#import "SRPrefDefaultKeys.h"

// Frame auto save name
NSString*   SRBookmarkPanelFrameAutoSaveName = @"SRBookmarkPanelFrameAutoSaveName";

@implementation SRBookmarkController

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

+ (id)sharedInstance
{
    static SRBookmarkController*    _sharedInstance = nil;
    if (!_sharedInstance) {
        _sharedInstance = [[SRBookmarkController alloc] init];
    }
    
    return _sharedInstance;
}

- (id)init
{
    self = [super initWithWindowNibName:@"BookmarkPanel"];
    if (!self) {
        return nil;
    }
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Initialize instance variables
    
    // Register notifications
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(bookmarkUpdated:) 
            name:SRBookmarkUpdated object:nil];
    
    // Register key value observation
    [defaults addObserver:self forKeyPath:SRBookmarkMenuUsageFlags 
            options:NSKeyValueObservingOptionNew context:NULL];
    
    return self;
}

- (void)awakeFromNib
{
    // Configure window
    NSWindow*   window;
    window = [self window];
    [window setFrameAutosaveName:SRBookmarkPanelFrameAutoSaveName];
    
    // Configure bookmark outline
    [_outlineView setTarget:self];
    [_outlineView setDoubleAction:@selector(openBookmarkAction:)];
    [_outlineView registerForDraggedTypes:[NSArray arrayWithObjects:
            WebURLsWithTitlesPboardType, SRBookmarkPboardType, SRPageInfoPboardType, nil]];
    [_outlineView setAutosaveExpandedItems:YES];
    
    // Set image text cell
    NSTableColumn*          column;
    NSCell*                 oldCell;
    HMImageTextFieldCell*   cell;
    column = [_outlineView tableColumnWithIdentifier:@"bookmark"];
    oldCell = [column dataCell];
    cell = [[HMImageTextFieldCell alloc] init];
    [cell setFont:[oldCell font]];
    [column setDataCell:cell];
    [cell release];
    
    // Configure menu button
    [_menuButton setDelegate:self];
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObserver:self forKeyPath:SRBookmarkMenuUsageFlags];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Persistent stack --
//--------------------------------------------------------------//

- (NSManagedObjectContext*)managedObjectContext
{
    return [[SRAppController sharedInstance] managedObjectContext];
}

//--------------------------------------------------------------//
#pragma mark -- Bookmark --
//--------------------------------------------------------------//

- (NSArray*)selectedBookmarks
{
    NSMutableArray* bookmarks;
    bookmarks = [NSMutableArray array];
    
    // Get selected objects
    NSIndexSet* indexSet;
    int         row;
    indexSet = [_outlineView selectedRowIndexes];
    row = [indexSet firstIndex];
    do {
        id  item;
        item = [_outlineView itemAtRow:row];
        if ([item isKindOfClass:[NSManagedObject class]]) {
            [bookmarks addObject:item];
        }
    } while ((row = [indexSet indexGreaterThanIndex:row]) != NSNotFound);
    
    return bookmarks;
}

- (NSMenu*)_contextMenuForView:(id)view 
        event:(NSEvent*)event
        from:(id)from
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get modifier key flag
    unsigned int    modifierFlags;
    unsigned int    cmdFlag, optionFlag, shiftFlag;
    modifierFlags = [event modifierFlags];
    cmdFlag = modifierFlags & NSCommandKeyMask;
    optionFlag = modifierFlags & NSAlternateKeyMask;
    shiftFlag = modifierFlags & NSShiftKeyMask;
    
    // Create array for tags
    NSMutableArray* tags;
    tags = [NSMutableArray array];
    
    // For right click on the outline view
    if (from == _outlineView) {
        // Select bookmark under the cursor
        NSPoint point;
        int     rowUnderPoint;
        point = [view convertPoint:[event locationInWindow] 
                fromView:nil];
        rowUnderPoint = [view rowAtPoint:point];
        if (rowUnderPoint >=0) {
            if (![[view selectedRowIndexes] containsIndex:rowUnderPoint]) {
                [view selectRowIndexes:[NSIndexSet indexSetWithIndex:rowUnderPoint] 
                        byExtendingSelection:NO];
            }
        }
    }
    
    // Get selected rows and bookmarks
    NSIndexSet* indexSet;
    NSArray*    selectedBookmarks;
    indexSet = [view selectedRowIndexes];
    selectedBookmarks = [self selectedBookmarks];
    
    SRBookmark* bookmark = nil;
    NSArray*    bookmarks = nil;
    
    // No bookmark is selected
    if ([indexSet count] == 0 || [selectedBookmarks count] == 0) {
        if ([view isKindOfClass:[NSOutlineView class]]) {
            [tags addObject:[NSNumber numberWithInt:SRShowBookmarkShelfTag]];
        }
    }
    else {
        // Just one bookmark is selected
        if ([selectedBookmarks count] == 1) {
            // Get bookmark
            bookmark = [selectedBookmarks objectAtIndex:0];
        }
        else {
            // Get bookmarks
            bookmarks = selectedBookmarks;
        }
        
        // Case of one HTML bookmark
        if (bookmark && ![[bookmark isFolder] boolValue]) {
            // Create tag array
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkTag]];
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewWindowTag]];
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewTabTag]];
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewBackgroundWindowTag]];
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewBackgroundTabTag]];
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewWindowTag]];
            //[tags addObject:[NSNumber numberWithInt:SRShowBookmarkInShelfTag]];
            [tags addObject:[NSNumber numberWithInt:SRShowBookmarkShelfTag]];
        }
        // Case of one bookmark folder or multiple bookmarks
        if ((bookmark && [[bookmark isFolder] boolValue]) || bookmarks) {
            // Create tag array
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInTabsTag]];
            //[tags addObject:[NSNumber numberWithInt:SRShowBookmarkInShelfTag]];
            [tags addObject:[NSNumber numberWithInt:SRShowBookmarkShelfTag]];
        }
    }
    
    if ([tags count] > 0) {
        // Copy menu
        NSMenu* menu;
        menu = HMCopyMenuWithTags([SRBookmarkContextMenu contextMenu], tags, self);
        
        // Set represented object
        if (bookmark) {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:bookmark];
        }
        else {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:bookmarks];
        }
        
        // Set alt menu
        NSMenuItem* altMenuItem;
        if ([defaults boolForKey:SRTabSelectNewTabs]) {
            altMenuItem = [menu itemWithTag:SROpenBookmarkInNewBackgroundWindowTag];
            [altMenuItem setKeyEquivalentModifierMask:NSShiftKeyMask];
            [altMenuItem setAlternate:YES];
            
            altMenuItem = [menu itemWithTag:SROpenBookmarkInNewBackgroundTabTag];
            [altMenuItem setKeyEquivalentModifierMask:NSShiftKeyMask];
            [altMenuItem setAlternate:YES];
        }
        else {
            altMenuItem = [menu itemWithTag:SROpenBookmarkInNewWindowTag];
            [altMenuItem setKeyEquivalentModifierMask:NSShiftKeyMask];
            [altMenuItem setAlternate:YES];
            
            altMenuItem = [menu itemWithTag:SROpenBookmarkInNewTabTag];
            [altMenuItem setKeyEquivalentModifierMask:NSShiftKeyMask];
            [altMenuItem setAlternate:YES];
        }
        
        return menu;
    }
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Actions --
//--------------------------------------------------------------//

- (void)showWindow:(id)sender
{
    // Reload data
    [_outlineView reloadData];
    
    [super showWindow:sender];
}

- (void)openBookmarkAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    
    SRBookmark* bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        // Get selected row
        int row;
        row = [_outlineView selectedRow];
        if (row == -1) {
            return;
        }
        
        // Get item
        id  item;
        item = [_outlineView itemAtRow:row];
        
        // Toogle expansion
        if ([_outlineView isItemExpanded:item]) {
            [_outlineView collapseItem:item];
        }
        else {
            [_outlineView expandItem:item];
        }
    }
    // Other types
    else {
        // Open bookmark
        WebView*    webView;
        webView = [[[SRAppController sharedInstance] openBookmark:bookmark] webView];
        
        // Make web view window key window
        NSWindow*   window;
        window = [webView window];
        if (window && ![window isKeyWindow]) {
            [window makeKeyWindow];
        }
    }
}

- (void)openBookmarkInNewWindowAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    SRBookmark* bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    SRBrowserDocument*  document;
    document = [[SRBrowserController mainBrowserController] 
            openInNewWindowBookmark:bookmark];
}

- (void)openBookmarkInNewBackgroundWindowAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    SRBookmark* bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    SRBrowserDocument*  document;
    document = [[SRBrowserController mainBrowserController] 
            openInNewBackgroundWindowBookmark:bookmark];
}

- (void)openBookmarkInNewTabAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    SRBookmark* bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    SRPageController*   pageController;
    pageController = [[SRBrowserController mainBrowserController] 
            openInNewTabBookmark:bookmark select:YES];
}

- (void)openBookmarkInNewBackgroundTabAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    SRBookmark* bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    SRPageController*   pageController;
    pageController = [[SRBrowserController mainBrowserController] 
            openInNewTabBookmark:bookmark select:NO];
}

- (void)openBookmarkInTabsAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    bookmarks = [self selectedBookmarks];
    
    // Open bookmark
    [[SRBrowserController mainBrowserController] 
            openInTabsBookmarks:bookmarks];
}

- (void)deleteBookmarkAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] == 0) {
        return;
    }
    
    // Delete bookmarks
    SRBookmark* bookmark;
    SRBookmark* parent;
    bookmark = [bookmarks objectAtIndex:0];
    parent = [bookmark valueForKey:@"parent"];
    [parent removeBookmarks:[NSArray arrayWithObject:bookmark]];
}

- (void)createNewBookmarkFolderAction:(id)sender
{
    [[NSApplication sharedApplication] 
            beginSheet:_createNewFolderPanel 
            modalForWindow:[self window] 
            modalDelegate:self 
            didEndSelector:@selector(createNewFolderSheetDidEnd:returnCode:contextInfo:) 
            contextInfo:NULL];
}

- (void)createNewFolderSheetDidEnd:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo
{
    // Close sheet
    [sheet orderOut:self];
    
    // Check return code
    if (returnCode != NSOKButton) {
        return;
    }
    
    // Get folder title
    NSString*   title;
    title = [_createNewFolderTextField stringValue];
    if ([title length] == 0) {
        title = NSLocalizedString(@"(Untitled)", nil);
    }
    
    // Get selected bookmark
    NSArray*    bookmarks;
    SRBookmark* selectedBookmark = nil;
    SRBookmark* parent = nil;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] > 0) {
        selectedBookmark = [bookmarks objectAtIndex:0];
        parent = [selectedBookmark valueForKey:@"parent"];
    }
    else {
        parent = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserShiira];
    }
    
    // Create folder
    SRBookmark* bookmark;
    bookmark = [SRBookmark folderWithTitle:title 
            context:[[SRAppController sharedInstance] managedObjectContext] 
            persistentStore:[[SRAppController sharedInstance] persistentStore]];
    
    // Insert bookmark
    int index;
    if (selectedBookmark) {
        index = [[parent sortedChildren] indexOfObject:selectedBookmark] + 1;
    }
    else {
        index = [[parent valueForKey:@"children"] count];
    }
    
    [parent insertBookmarks:[NSArray arrayWithObject:bookmark] atIndex:index];
}

- (void)closeSheetAction:(id)sender
{
    // End sheet
    [[NSApplication sharedApplication] 
            endSheet:_createNewFolderPanel returnCode:[sender tag]];
}

- (void)showShelfAction:(id)sender
{
    [[SRAppController sharedInstance] openShelf:@"jp.hmdt.shiira.bookmarkshelf"];
}

- (void)showInShelfAction:(id)sender
{
#if 0
    // For NSMenuItem
    if ([sender isKindOfClass:[NSMenuItem class]]) {
        // Get represented object
        id  representedObject;
        representedObject = [sender representedObject];
        if ([representedobjec
    }
#endif
}

//--------------------------------------------------------------//
#pragma mark -- NSWindow delegate --
//--------------------------------------------------------------//

- (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect
{
    rect.origin.y -= 24;
    
    return rect;
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineView data source --
//--------------------------------------------------------------//

- (int)outlineView:(NSOutlineView*)outlineView 
        numberOfChildrenOfItem:(id)item
{
    // For root
    if (!item) {
        // Get usage flags
        NSDictionary*   usageFlags;
        usageFlags = [[NSUserDefaults standardUserDefaults] objectForKey:SRBookmarkMenuUsageFlags];
        if (![usageFlags isKindOfClass:[NSDictionary class]]) {
            usageFlags = nil;
        }
        
        int count = 0;
        
        // Get Shiira root
        SRBookmark* shiiraRoot;
        shiiraRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserShiira];
        if (shiiraRoot) {
            count += [[shiiraRoot valueForKey:@"children"] count];
        }
        
        // Get Safari root
        if ([[usageFlags objectForKey:SRBrowserSafari] boolValue]) {
            SRBookmark* safariRoot;
            safariRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserSafari];
            if (safariRoot) {
                count ++; // For title
                count += [[safariRoot valueForKey:@"children"] count];
            }
        }
        
        // Get Firefox root
        if ([[usageFlags objectForKey:SRBrowserFirefox] boolValue]) {
            SRBookmark* firefoxRoot;
            firefoxRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserFirefox];
            if (firefoxRoot) {
                count ++; // For title
                count += [[firefoxRoot valueForKey:@"children"] count];
            }
        }
        
        return count;
    }
    
    // Check item class
    if  (![item isKindOfClass:[NSManagedObject class]]) {
        return 0;
    }
    
    // Get children count
    return [[item valueForKey:@"children"] count];
}

- (id)outlineView:(NSOutlineView*)outlineView 
        child:(int)index 
        ofItem:(id)item
{
    static NSDictionary*    _attr = nil;
    if (!_attr) {
        _attr = [[NSDictionary alloc] initWithObjectsAndKeys:
                [NSFont boldSystemFontOfSize:11], NSFontAttributeName, 
                [NSColor lightGrayColor], NSForegroundColorAttributeName, 
                nil];
    }
    
    // For root
    if (!item) {
        // Get usage flags
        NSDictionary*   usageFlags;
        usageFlags = [[NSUserDefaults standardUserDefaults] objectForKey:SRBookmarkMenuUsageFlags];
        if (![usageFlags isKindOfClass:[NSDictionary class]]) {
            usageFlags = nil;
        }
        
        int count = 0;
        
        // Get Shiira root
        SRBookmark* shiiraRoot;
        shiiraRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserShiira];
        if (shiiraRoot) {
            count += [[shiiraRoot valueForKey:@"children"] count];
            if (index < count) {
                return [[shiiraRoot sortedChildren] objectAtIndex:index];
            }
        }
        
        // Get Safari root
        if ([[usageFlags objectForKey:SRBrowserSafari] boolValue]) {
            SRBookmark* safariRoot;
            safariRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserSafari];
            if (safariRoot) {
                if (index == count) {
                    static NSAttributedString*  _safariTitle = nil;
                    if (!_safariTitle) {
                        _safariTitle = [[NSAttributedString alloc] 
                                initWithString:NSLocalizedString(@"Safari", nil) attributes:_attr];
                    }
                    return _safariTitle;
                }
                count++;
                
                int safariCount;
                safariCount = [[safariRoot valueForKey:@"children"] count];
                if (index < count + safariCount) {
                    return [[safariRoot sortedChildren] objectAtIndex:index - count];
                }
                count += safariCount;
            }
        }
        
        // Get Firefox root
        if ([[usageFlags objectForKey:SRBrowserFirefox] boolValue]) {
            SRBookmark* firefoxRoot;
            firefoxRoot = [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserFirefox];
            if (firefoxRoot) {
                if (index == count) {
                    static NSAttributedString*  _firefoxTitle = nil;
                    if (!_firefoxTitle) {
                        _firefoxTitle = [[NSAttributedString alloc] 
                                initWithString:NSLocalizedString(@"Firefox", nil) attributes:_attr];
                    }
                    return _firefoxTitle;
                }
                count++;
                
                int firefoxCount;
                firefoxCount = [[firefoxRoot valueForKey:@"children"] count];
                if (index < count + firefoxCount) {
                    return [[firefoxRoot sortedChildren] objectAtIndex:index - count];
                }
                count += firefoxCount;
            }
        }
        
        return nil;
    }
    
    // Check item class
    if  (![item isKindOfClass:[NSManagedObject class]]) {
        return nil;
    }
    
    // Check children count
    if ([[item valueForKey:@"children"] count] <= index) {
        return nil;
    }
    
    // Get children count
    return [[item sortedChildren] objectAtIndex:index];
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        isItemExpandable:(id)item
{
    // Check item class
    if  (![item isKindOfClass:[NSManagedObject class]]) {
        return NO;
    }
    
    // Check type
    return [[item isFolder] boolValue];
}

- (id)outlineView:(NSOutlineView*)outlineView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        byItem:(id)item
{
    // For bookmark
    if  ([item isKindOfClass:[NSManagedObject class]]) {
        return [item title];
    }
    
    return item;
}

- (SRBookmark*)_folderWithItem:(id)item atIndex:(int)index
{
    // For root
    if (!item) {
        return [[SRAppController sharedInstance] rootBookmarkOfBrowser:SRBrowserShiira];
    }
    
    // Chcek item class
    if (![item isKindOfClass:[SRBookmark class]]) {
        return nil;
    }
    
    // For folder
    if ([[item isFolder] boolValue]) {
        return item;
    }
    
    // Get parent
    return [item valueForKey:@"parent"];
}

- (NSArray*)_draggedBookmarksWithPboard:(NSPasteboard*)pboard
{
    // Get managed object context
    NSManagedObjectContext* context;
    id                      persistentStore;
    context = [[SRAppController sharedInstance] managedObjectContext];
    persistentStore = [[SRAppController sharedInstance] persistentStore];
    
    // Get bookmarks
    NSMutableArray* bookmarks;
    NSArray*        bookmarkInfos;
    NSEnumerator*   enumerator;
    NSString*       bookmarkInfo;
    bookmarks = [NSMutableArray array];
    bookmarkInfos = [pboard propertyListForType:SRBookmarkPboardType];
    enumerator = [bookmarkInfos objectEnumerator];
    while (bookmarkInfo = [enumerator nextObject]) {
        NSURL*              URIRepresentation;
        NSManagedObjectID*  objectId;
        SRBookmark*         bookmark;
        URIRepresentation = [NSURL URLWithString:bookmarkInfo];
        objectId = [[context persistentStoreCoordinator] 
                managedObjectIDForURIRepresentation:URIRepresentation];
        bookmark = (SRBookmark*)[context objectWithID:objectId];
        if (!bookmark) {
            continue;
        }
        
        [bookmarks addObject:bookmark];
    }
    
    return bookmarks;
}

- (NSDragOperation)_dragOperationWithBookmarks:(NSArray*)bookmarks 
        withFolder:(SRBookmark*)folder
{
    // Check argument
    if (!bookmarks || [bookmarks count] == 0) {
        return NSDragOperationNone;
    }
    
    // Check modifier key
    unsigned int    modifiers;
    modifiers = [[NSApp currentEvent] modifierFlags];
    
    if (modifiers & NSAlternateKeyMask) {
        return NSDragOperationCopy;
    }
    
    // Check bookmarks
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Check with folder
        if ([[bookmark isFolder] boolValue] && 
            [bookmark isDescendantOf:folder])
        {
            return NSDragOperationNone;
        }
        
        // Check browser type
        if (![[bookmark browser] isEqualToString:SRBrowserShiira]) {
            return NSDragOperationCopy;
        }
    }
    
    return NSDragOperationMove;
}

- (NSDragOperation)outlineView:(NSOutlineView*)outlineView 
        validateDrop:(id <NSDraggingInfo>)info 
        proposedItem:(id)item 
        proposedChildIndex:(int)index
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    if (![types containsObject:SRBookmarkPboardType] && 
        ![types containsObject:SRPageInfoPboardType] && 
        ![types containsObject:WebURLsWithTitlesPboardType])
    {
        return NSDragOperationNone;
    }
    
    // Check index
    if (index == NSOutlineViewDropOnItemIndex) {
        return NSDragOperationNone;
    }
    if (!item && index == 0) {
        return NSDragOperationNone;
    }
    
    // Get folder
    SRBookmark* folder;
    folder = [self _folderWithItem:item atIndex:index];
    if (!folder || ![[folder browser] isEqualToString:SRBrowserShiira]) {
        return NSDragOperationNone;
    }
    
    // For SRBookmarkPboardType
    if ([types containsObject:SRBookmarkPboardType]) {
        // Get bookmarks
        NSArray*    bookmarks;
        bookmarks = [self _draggedBookmarksWithPboard:pboard];
        
        // Get drag operation
        return [self _dragOperationWithBookmarks:bookmarks withFolder:folder];
    }
    
    // For SRPageInfoPboardType
    if ([types containsObject:SRPageInfoPboardType]) {
        return NSDragOperationCopy;
    }
    
    // For WebURLsWithTitlesPboardType
    if ([types containsObject:WebURLsWithTitlesPboardType]) {
        return NSDragOperationCopy;
    }
    
    return NSDragOperationNone;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        acceptDrop:(id <NSDraggingInfo>)info 
        item:(id)item 
        childIndex:(int)index
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    if (![types containsObject:SRBookmarkPboardType] && 
        ![types containsObject:SRPageInfoPboardType] && 
        ![types containsObject:WebURLsWithTitlesPboardType])
    {
        return NO;
    }
    
    // Check index
    if (index == NSOutlineViewDropOnItemIndex) {
        return NO;
    }
    if (!item && index == 0) {
        return NO;
    }
    
    // Get folder
    SRBookmark* folder;
    folder = [self _folderWithItem:item atIndex:index];
    if (!folder || ![[folder browser] isEqualToString:SRBrowserShiira]) {
        return NO;
    }
    
    // Get bookmark at index
    NSArray*    children;
    SRBookmark* targetBookmark = nil;
    children = [folder sortedChildren];
    if (index > 0) {
        if (index - 1< [children count]) {
            targetBookmark = [children objectAtIndex:index - 1];
        }
        else {
            targetBookmark = [children lastObject];
        }
    }
    
    // Get managed object context
    NSManagedObjectContext* context;
    id                      persistentStore;
    context = [[SRAppController sharedInstance] managedObjectContext];
    persistentStore = [[SRAppController sharedInstance] persistentStore];
    
    // For bookmark insertion
    NSArray*        insertedBookmarks = nil;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    
    // For SRBookmarkPboardType
    if ([types containsObject:SRBookmarkPboardType]) {
        // Get bookmarks
        NSArray*    bookmarks;
        bookmarks = [self _draggedBookmarksWithPboard:pboard];
        if (!bookmarks || [bookmarks count] == 0) {
            return NO;
        }
        
        // Get drag operation
        NSDragOperation     operation;
        operation = [self _dragOperationWithBookmarks:bookmarks withFolder:folder];
        if (operation == NSDragOperationNone) {
            return NO;
        }
        
        // For copying
        if (operation == NSDragOperationCopy) {
            SRBookmarkFactory*  factory;
            factory = [SRBookmarkFactory sharedInstanceWithManagedObjectContext:context 
                    persistentStore:persistentStore];
            
            // Copy bookmarks
            NSMutableArray* copiedBookmarks;
            copiedBookmarks = [NSMutableArray array];
            enumerator = [bookmarks objectEnumerator];
            while (bookmark = [enumerator nextObject]) {
                // Copy bookmark
                SRBookmark* copiedBookmark;
                copiedBookmark = [factory copyBookmark:bookmark];
                
                [copiedBookmarks addObject:copiedBookmark];
            }
            
            // Insert copied bookmarks
            [folder insertBookmarks:copiedBookmarks atIndex:index];
            insertedBookmarks = copiedBookmarks;
        }
        
        // For moving
        else {
            // Remove from parent
            enumerator = [bookmarks objectEnumerator];
            while (bookmark = [enumerator nextObject]) {
                SRBookmark* parent;
                parent = [bookmark valueForKey:@"parent"];
                [parent removeBookmarks:[NSArray arrayWithObject:bookmark]];
            }
            
            // Insert bookmarks
            int bookmarkIndex = 0;
            children = [folder sortedChildren];
            if (targetBookmark) {
                bookmarkIndex = [children indexOfObject:targetBookmark];
            }
            if (bookmarkIndex == NSNotFound) {
                NSLog(@"Not found bookmark index");
                bookmarkIndex = 0;
            }
            if (index == 0) {
                [folder insertBookmarks:bookmarks atIndex:0];
            }
            else {
                [folder insertBookmarks:bookmarks atIndex:bookmarkIndex + 1];
            }
            insertedBookmarks = bookmarks;
        }
    }
    
    // For SRPageInfoPboardType
    else if ([types containsObject:SRPageInfoPboardType]) {
        // Get title and URL string
        NSDictionary*   pageInfo;
        NSString*       URLString;
        NSString*       title;
        NSString*       mimeType;
        pageInfo = [pboard propertyListForType:SRPageInfoPboardType];
        URLString = [pageInfo objectForKey:@"urlString"];
        if (!URLString) {
            return NO;
        }
        title = [pageInfo objectForKey:@"title"];
        if (!title) {
            title = URLString;
        }
        mimeType = [pageInfo objectForKey:@"mime"];
        
        // Create bookmark
        bookmark = [SRBookmark bookmarkWithTitle:title URLString:URLString 
                context:context persistentStore:persistentStore];
        
        // Insert bookmarks
        insertedBookmarks = [NSArray arrayWithObject:bookmark];
        [folder insertBookmarks:insertedBookmarks atIndex:index];
    }
    
    // For WebURLsWithTitlesPboardType
    else if ([types containsObject:WebURLsWithTitlesPboardType]) {
        // Get title and URL string
        NSArray*    titles;
        NSArray*    URLs;
        titles = [WebURLsWithTitles titlesFromPasteboard:pboard];
        URLs = [WebURLsWithTitles URLsFromPasteboard:pboard];
        
        // Create bookmarks
        NSMutableArray* bookmarks;
        NSEnumerator*   titleEnumerator;
        NSEnumerator*   URLEnumerator;
        NSString*       title;
        NSURL*          URL;
        bookmarks = [NSMutableArray array];
        titleEnumerator = [titles objectEnumerator];
        URLEnumerator = [URLs objectEnumerator];
        while ((title = [titleEnumerator nextObject]) && 
               (URL = [URLEnumerator nextObject]))
        {
            // Create bookmark
            bookmark = [SRBookmark bookmarkWithTitle:title URLString:[URL absoluteString] 
                    context:context persistentStore:persistentStore];
            
            [bookmarks addObject:bookmark];
        }
        
        // Insert bookmarks
        [folder insertBookmarks:bookmarks atIndex:index];
        insertedBookmarks = bookmarks;
    }
    
    // For other
    else {
        return NO;
    }
    
    // Expand folder
    if (![_outlineView isItemExpanded:folder]) {
        [_outlineView expandItem:folder];
    }
    
    // Select inserted bookmarks
    NSMutableIndexSet*  indexSet;
    indexSet = [NSMutableIndexSet indexSet];
    enumerator = [insertedBookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        int row;
        row = [_outlineView rowForItem:bookmark];
        if (row == -1) {
            continue;
        }
        [indexSet addIndex:row];
    }
    
    [_outlineView selectRowIndexes:indexSet byExtendingSelection:NO];
    
    return YES;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        writeItems:(NSArray*)items 
        toPasteboard:(NSPasteboard*)pboard
{
    // Set bookmarks
    NSMutableArray* bookmarkInfos;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    bookmarkInfos = [NSMutableArray array];
    enumerator = [items objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        NSString*   bookmarkInfo;
        bookmarkInfo = [[[bookmark objectID] URIRepresentation] absoluteString];
        
        [bookmarkInfos addObject:bookmarkInfo];
    }
    
    // Write data to pasteboard
    [pboard declareTypes:[NSArray arrayWithObject:SRBookmarkPboardType] owner:nil];
    [pboard setPropertyList:bookmarkInfos forType:SRBookmarkPboardType];
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineView delegate --
//--------------------------------------------------------------//

- (BOOL)outlineView:(NSOutlineView*)outlineView shouldSelectItem:(id)item
{
    return [item isKindOfClass:[SRBookmark class]];
}

- (void)outlineView:(NSOutlineView*)outlineView 
        willDisplayCell:(id)cell 
        forTableColumn:(NSTableColumn*)column 
        item:(id)item
{
    // Bookmark outline
    if (outlineView == _outlineView) {
        // Get column identifier
        id  identifier;
        identifier = [column identifier];
        
        // For bookmark column
        if ([identifier isEqualToString:@"bookmark"]) {
            // Set image
            NSImage*    image = nil;
            if ([item isKindOfClass:[NSManagedObject class]]) {
                image = [item valueForKey:@"icon"];
            }
            [cell setImage:image];
            
            return;
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineViewEx delegate --
//--------------------------------------------------------------//

- (NSMenu*)outlineView:(NSOutlineView*)outlineView menuForEvent:(NSEvent*)event
{
    return [self _contextMenuForView:outlineView event:event from:outlineView];
}

//--------------------------------------------------------------//
#pragma mark -- HMOutlineView delegate --
//--------------------------------------------------------------//

- (BOOL)hmOutlineView:(id)outlineView 
        frameOfCellAtColumn:(int)column 
        row:(int)row 
        proposedFrame:(NSRect*)proposedFrame
{
    // Get item at row
    id  item;
    item = [_outlineView itemAtRow:row];
    if (!item || [item isKindOfClass:[NSManagedObject class]]) {
        return NO;
    }
    
    // Expand frame
    proposedFrame->origin.x -= 11.0f;
    proposedFrame->size.width += 11.0f;
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- HMMenuButton delegate --
//--------------------------------------------------------------//

- (NSMenu*)menuButton:(HMMenuButton*)menuButton menuForEvent:(NSEvent*)event
{
    return [self _contextMenuForView:_outlineView event:event from:menuButton];
}

//--------------------------------------------------------------//
#pragma mark -- SRBookmark notification --
//--------------------------------------------------------------//

- (void)bookmarkUpdated:(NSNotification*)notification
{
    // Reload data
    [_outlineView reloadData];
}

//--------------------------------------------------------------//
#pragma mark -- Key value observation --
//--------------------------------------------------------------//

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void *)context
{
    // For user defaults
    if (object == [NSUserDefaults standardUserDefaults]) {
        // Bookmark pref
        if ([keyPath isEqualToString:SRBookmarkMenuUsageFlags]) {
            [_outlineView reloadData];
        }
    }
}

@end
