/*
SRBookmarkBar.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 "SRAppController.h"

#import "SRBookmarkBar.h"
#import "SRBookmarkButton.h"
#import "SRBookmarkButtonCell.h"

#import "SRConstants.h"

// Notification
NSString*   SRBookmarkBarHeightChanged = @"SRBookmarkBarHeightChanged";

static int  SRBookmarkBarEndMarginWidth = 8;
static int  SRBookmarkBarButtonMarginWidth = 4;

@implementation SRBookmarkBar

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

- (void)_init
{
    // Initialize instance variables
    _bookmarkButtons = [[NSMutableArray array] retain];
    _wrap = YES;
    _resizedBySelf = NO;
    
    _indicatorIndex = -1;
    _droppedFolderIndex = -1;
    
    // Create button
    NSImage*    clipImage;
    NSRect      frame;
    clipImage = [NSImage imageNamed:@"clipIndicator"];
    frame.origin = NSZeroPoint;
    frame.size = [clipImage size];
    _clipButton = [[HMMenuButton alloc] initWithFrame:frame];
    [_clipButton setButtonType:NSMomentaryChangeButton];
    [_clipButton setBezelStyle:NSRegularSquareBezelStyle];
    [_clipButton setBordered:NO];
    [_clipButton setImage:clipImage];
    [_clipButton setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
    [_clipButton setDelegate:self];
    [_clipButton setHidden:YES];
    [self addSubview:_clipButton];
    
    // Register drag types
    [self registerForDraggedTypes:
            [NSArray arrayWithObjects:SRBookmarkPboardType, SRPageInfoPboardType, 
                    WebURLsWithTitlesPboardType, nil]];
    
    // Register notifications
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(bookmarkUpdated:) 
            name:SRBookmarkUpdated object:nil];
}

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (!self) {
        return nil;
    }
    
    // Common init
    [self _init];
    
    return self;
}

- (id)initWithCoder:(NSCoder*)coder
{
    self = [super initWithCoder:coder];
    if (!self) {
        return nil;
    }
    
    // Common init
    [self _init];
    
    return self;
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [_bookmarkButtons release], _bookmarkButtons = nil;
    [_clipButton release], _clipButton = nil;
    
    [(HMWindow*)[self window] removeMouseMoveListener:self];
    
    [super dealloc];
}

- (void)viewDidMoveToWindow
{
    // Add itself as mouse listner
    [(HMWindow*)[self window] addMouseMoveListener:self];
}

//--------------------------------------------------------------//
#pragma mark -- Bookmarks --
//--------------------------------------------------------------//

- (void)_updateButtonFrames
{
    // Get bounds
    NSRect  barBounds;
    barBounds = [self bounds];
    
    // Decide height
    float   height = 19.0f;
    
    // Update button frames
    NSRect              buttonFrame;
    NSEnumerator*       enumerator;
    SRBookmarkButton*   button;
    BOOL                isButtonHidden = NO;
    buttonFrame.origin.x = SRBookmarkBarEndMarginWidth;
    buttonFrame.origin.y = barBounds.size.height - 1 - height;
    enumerator = [_bookmarkButtons objectEnumerator];
    while (button = [enumerator nextObject]) {
        // Size to fit
        [button sizeToFit];
        
        // Get button frame
        NSRect  frame;
        frame = [button frame];
        buttonFrame.size.width = frame.size.width + 4;
        buttonFrame.size.height = height;
        
        // For wrapping
        if (_wrap) {
            if (buttonFrame.origin.x + buttonFrame.size.width + SRBookmarkBarEndMarginWidth > 
                barBounds.size.width)
            {
                buttonFrame.origin.x = SRBookmarkBarEndMarginWidth;
                buttonFrame.origin.y -= 1 + height;
            }
        }
        
        // Set button frame
        [button setFrame:buttonFrame];
        
        // Set visibility
        frame = barBounds;
        frame.size.width -= 20; // Clip image size
        if (!NSContainsRect(frame, buttonFrame)) {
            [button setHidden:YES];
            isButtonHidden = YES;
        }
        else {
            [button setHidden:NO];
        }
        
        // Decide next point
        buttonFrame.origin.x = buttonFrame.origin.x + buttonFrame.size.width + 
                SRBookmarkBarButtonMarginWidth;
    }
    
    // Set clip button
    if (!_wrap && isButtonHidden) {
        buttonFrame.origin.x = barBounds.size.width - 20;
        buttonFrame.origin.y = 5;
        buttonFrame.size = NSMakeSize(20, 10);
        
        [_clipButton setFrame:buttonFrame];
        [_clipButton setHidden:NO];
    }
    else {
        [_clipButton setHidden:YES];
    }
}

- (void)setBookmarks:(NSArray*)bookmarks
{
    // Remove old buttons
    [_bookmarkButtons makeObjectsPerformSelector:@selector(removeFromSuperview)];
    [_bookmarkButtons removeAllObjects];
    
    // Create bookmark buttons
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Create bookmark button
        SRBookmarkButton*   button;
        button = [[SRBookmarkButton alloc] initWithFrame:NSMakeRect(0, 0, 10, 10)];
        [button autorelease];
        [button setBookmark:bookmark];
        [button setTarget:_target];
        [button setAction:_action];
        [button setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
        [[button cell] setControlSize:NSSmallControlSize];
        
        // Add bookmark button
        [self addSubview:button];
        [_bookmarkButtons addObject:button];
    }
    
    // Update button frames
    [self _updateButtonFrames];
}

//--------------------------------------------------------------//
#pragma mark -- Wrapping --
//--------------------------------------------------------------//

- (BOOL)isWraped
{
    return _wrap;
}

- (void)setWrap:(BOOL)wrap
{
    _wrap = wrap;
}

- (void)sizeToFit
{
    // Get frame
    NSRect  frame;
    float   height;
    frame = [self frame];
    height = frame.size.height;
    
    // For no wrapping
    if (!_wrap) {
        height = 21.0f;
    }
    // For wrapping
    else {
        NSRect              unionRect = NSZeroRect;
        NSEnumerator*       enumerator;
        SRBookmarkButton*   button;
        enumerator = [_bookmarkButtons objectEnumerator];
        while (button = [enumerator nextObject]) {
            unionRect = NSUnionRect(unionRect, [button frame]);
        }
        height = unionRect.size.height + 2;
        
        if (height < 21.0f) {
            height = 21.0f;
        }
    }
    
    // Set frame
    if (frame.size.height != height) {
        // Left top point is fixed
        frame.origin.y = frame.origin.y + frame.size.height - height;
        frame.size.height = height;
        
        _resizedBySelf = YES;
        [self setFrame:frame];
        _resizedBySelf = NO;
        
        // Notify it
        [[NSNotificationCenter defaultCenter] 
                postNotificationName:SRBookmarkBarHeightChanged object:self];
    }
    
    // Update button frames
    [self _updateButtonFrames];
    
    // Set needs display
    [self setNeedsDisplay:YES];
}

- (void)setFrame:(NSRect)frame
{
    [super setFrame:frame];
    
    if (!_resizedBySelf) {
        [self _updateButtonFrames];
        [self sizeToFit];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Button action --
//--------------------------------------------------------------//

- (id)target
{
    return _target;
}

- (void)setTarget:(id)target
{
    _target = target;
}

- (SEL)action
{
    return _action;
}

- (void)setAction:(SEL)action
{
    _action = action;
}

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

- (void)drawRect:(NSRect)rect
{
    // Get bounds
    NSRect  bounds;
    bounds = [self bounds];
    
    // Fill background with image
    NSImage*    bookmarkBarImage;
    NSRect      srcRect;
    bookmarkBarImage = [NSImage imageNamed:@"bookmarkBarBack"];
    srcRect.origin = NSZeroPoint;
    srcRect.size = [bookmarkBarImage size];
    [bookmarkBarImage drawInRect:bounds fromRect:srcRect operation:NSCompositeCopy fraction:1.0f];
    
    // Draw indicator
    if (_indicatorIndex != -1) {
        // Decide indicator origin
        NSPoint origin;
        if (_indicatorIndex < [_bookmarkButtons count]) {
            origin = [[_bookmarkButtons objectAtIndex:_indicatorIndex] frame].origin;
        }
        else if ([_bookmarkButtons count] > 0) {
            NSRect  frame;
            frame = [[_bookmarkButtons objectAtIndex:[_bookmarkButtons count] - 1] frame];
            origin = frame.origin;
            origin.x += frame.size.width;
        }
        else {
            origin.x = SRBookmarkBarEndMarginWidth;
            origin.y = 1;
        }
        
        // Draw indicator
        [[NSColor colorWithCalibratedWhite:0.3f alpha:1.0f] set];
        NSRectFill(NSMakeRect(origin.x - 3, origin.y + 6, 2, 12));
        
        NSBezierPath*   path;
        path = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(origin.x - 4, origin.y + 2, 4, 4)];
        [path setLineWidth:1.5f];
        [path stroke];
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSDraggingDestination protocol --
//--------------------------------------------------------------//

- (NSDragOperation)_updateDragging:(id<NSDraggingInfo>)info
{
    // Get location
    NSPoint location;
    location = [self convertPoint:[info draggingLocation] fromView:nil];
    
    // Clear indicator index
    int oldIndex;
    oldIndex = _indicatorIndex;
    _indicatorIndex = -1;
    
    // Enumerate bookmark buttons
    int i;
    for (i = 0; i < [_bookmarkButtons count]; i++) {
        // Get bookmark button
        SRBookmarkButton*   button;
        button = [_bookmarkButtons objectAtIndex:i];
        if ([button isHidden]) {
            continue;
        }
        
        // Get frame
        NSRect  buttonFrame;
        buttonFrame = [button frame];
        buttonFrame = NSInsetRect(buttonFrame, -2, -1);
        
        // Divide frame
        NSRect  leftFrame, rightFrame;
        NSDivideRect(
                buttonFrame, &leftFrame, &rightFrame, 
                buttonFrame.size.width / 2.0f, NSMinXEdge);
        if (leftFrame.origin.x <= 6.0f) {
            leftFrame.origin.x = 0;
        }
        
        // In left frame
        if (NSPointInRect(location, leftFrame)) {
            _indicatorIndex = i;
            break;
        }
        
        // In right frame
        if (NSPointInRect(location, rightFrame)) {
            _indicatorIndex = i + 1;
            break;
        }
    }
    
    if (_indicatorIndex == -1) {
        _indicatorIndex = [_bookmarkButtons count];
    }
    
    // Update appearance
    if (_indicatorIndex != oldIndex) {
        [self setNeedsDisplay:YES];
    }
    
#if 0
    // Check drag source
    id  draggingSource;
    draggingSource = [info draggingSource];
    if ([draggingSource isKindOfClass:[SRBookmarkButton class]]) {
        // Copy with option key
        if ([info draggingSourceOperationMask] == NSDragOperationCopy) {
            return NSDragOperationCopy;
        }
        
        return NSDragOperationMove;
    }
#endif
    
    return NSDragOperationCopy;
}

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
{
    return [self _updateDragging:info];
}

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
{
    return [self _updateDragging:info];
}

- (void)draggingEnded:(id<NSDraggingInfo>)info
{
    _indicatorIndex = -1;
    //[self _setDropAcceptForlderIndex:-1];
    [self setNeedsDisplay:YES];
}

- (void)draggingExited:(id<NSDraggingInfo>)info
{
    _indicatorIndex = -1;
    //[self _setDropAcceptForlderIndex:-1];
    [self setNeedsDisplay:YES];
}

- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)info
{
    return YES;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
{
    // Check dragged data type
    NSPasteboard*   pboard;
    NSArray*        types;
    pboard = [info draggingPasteboard];
    types = [pboard types];
    if (![types containsObject:SRBookmarkPboardType] && 
        ![types containsObject:SRPageInfoPboardType] && 
        ![types containsObject:WebURLsWithTitlesPboardType])
    {
        // Do not accept drag
        _indicatorIndex = -1;
        //[self _setDropAcceptForlderIndex:-1];
        [self setNeedsDisplay:YES];
        return NO;
    }
    
    // Get managed object context
    NSManagedObjectContext* context;
    id                      persistentStore;
    context = [[SRAppController sharedInstance] managedObjectContext];
    persistentStore = [[SRAppController sharedInstance] persistentStore];
    
    // For SRBookmarkPboardType
    if ([types containsObject:SRBookmarkPboardType]) {
#if 0
        // Read bookmarks
        NSArray*    bookmarks;
        bookmarks = SRReadBookmarksFromPasteboard(pboard);
        if (!bookmarks || [bookmarks count] == 0) {
            _indicatorIndex = -1;
            [self _setDropAcceptForlderIndex:-1];
            [self setNeedsDisplay:YES];
            return NO;
        }
        
        if (_indicatorIndex != -1) {
            if ([[info draggingSource] isKindOfClass:[SRBookmarkButton class]] && 
                [info draggingSourceOperationMask] != NSDragOperationCopy)
            {
                // Tunr off bookmark changed notification
                // It would be invoked when old bookmarks are removed
                [[SRBookmarkStorage sharedInstance] setNotifyFlag:NO];
            }
            
            // Insert bookmarks
            [_bookmarksBar insertChildren:bookmarks atIndex:_indicatorIndex];
            
            // Tunr on bookmark changed notification
            [[SRBookmarkStorage sharedInstance] setNotifyFlag:YES];
        }
        
        if (_droppedFolderIndex != -1 && ![[info draggingSource] isKindOfClass:[SRBookmarkButton class]]) {
            // Insert bookmarks into bookmark folder
            if ([_bookmarkButtons count] > _droppedFolderIndex) {
                SRBookmarkButton*   button;
                SRBookmark*         bookmark;
                button = [_bookmarkButtons objectAtIndex:_droppedFolderIndex];
                bookmark = [button bookmark];
                [bookmark addChildren:bookmarks];
            }
        }
        
#endif
        return YES;
    }
    
    // For SRPageInfoPboardType
    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 YES;
        }
        title = [pageInfo objectForKey:@"title"];
        if (!title) {
            title = URLString;
        }
        mimeType = [pageInfo objectForKey:@"mime"];
        
        // Create bookmark
        SRBookmark* bookmark;
        bookmark = [SRBookmark bookmarkWithTitle:title URLString:URLString 
                context:context persistentStore:persistentStore];
        
        // Insert bookmarks into bookmarks bar
        if (_indicatorIndex != -1) {
            SRBookmark* bookmarkForBookmarkBar;
            bookmarkForBookmarkBar = [[SRAppController sharedInstance] bookmarkForBookmarkBar];
            [bookmarkForBookmarkBar insertBookmarks:[NSArray arrayWithObject:bookmark] atIndex:_indicatorIndex];
        }
        
        return YES;
    }
    
    // For WebURLsWithTitlesPboardType
    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
            SRBookmark* bookmark;
            bookmark = [SRBookmark bookmarkWithTitle:title URLString:[URL absoluteString] 
                    context:context persistentStore:persistentStore];
            
            [bookmarks addObject:bookmark];
        }
        
        // Insert bookmarks into bookmarks bar
        if (_indicatorIndex != -1) {
            SRBookmark* bookmarkForBookmarkBar;
            bookmarkForBookmarkBar = [[SRAppController sharedInstance] bookmarkForBookmarkBar];
            [bookmarkForBookmarkBar insertBookmarks:bookmarks atIndex:_indicatorIndex];
        }
        
        if (_droppedFolderIndex != -1) {
#if 0
            // Insert bookmarks into bookmark folder
            if ([_bookmarkButtons count] > _droppedFolderIndex) {
                SRBookmarkButton*   button;
                SRBookmark*         bookmark;
                button = [_bookmarkButtons objectAtIndex:_droppedFolderIndex];
                bookmark = [button bookmark];
                [bookmark addChildren:bookmarks];
            }
#endif
        }
        
        return YES;
    }
    
    return NO;
}

- (void)concludeDragOperation:(id<NSDraggingInfo>)info
{
    _indicatorIndex = -1;
    //[self _setDropAcceptForlderIndex:-1];
    [self setNeedsDisplay:YES];
}

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

- (NSMenu*)menuButton:(HMMenuButton*)menuButton menuForEvent:(NSEvent*)event
{
    // Collect hidden bookmarks
    NSMutableArray*     bookmarks;
    NSEnumerator*       enumerator;
    SRBookmarkButton*   bookmarkButton;
    bookmarks = [NSMutableArray array];
    enumerator = [_bookmarkButtons objectEnumerator];
    while (bookmarkButton = [enumerator nextObject]) {
        if ([bookmarkButton isHidden]) {
            [bookmarks addObject:[bookmarkButton bookmark]];
        }
    }
    
    // Create menu
    NSMenu* menu;
    menu = [[NSMenu alloc] initWithTitle:@""];
    SRAppendBookmarksIntoMenu(bookmarks, menu, NO);
    
    return menu;
}

//--------------------------------------------------------------//
#pragma mark -- HMMouseMoveListener delegate --
//--------------------------------------------------------------//

- (SRBookmarkButton*)_buttonUnderPoint:(NSPoint)point
{
    NSEnumerator*       enumerator;
    SRBookmarkButton*   button;
    enumerator = [_bookmarkButtons objectEnumerator];
    while (button = [enumerator nextObject]) {
        if (NSPointInRect(point, [button frame])) {
            return button;
        }
    }
    
    return nil;
}

- (void)hmWindow:(NSWindow*)window mouseMoved:(NSEvent*)event
{
    // Refresh hovered bookmark
    NSEnumerator*       enumerator;
    SRBookmarkButton*   button;
    enumerator = [_bookmarkButtons objectEnumerator];
    while (button = [enumerator nextObject]) {
        if ([button isHovered]) {
            [button setNeedsDisplay:YES];
        }
    }
    
    // Get mouse point
    NSPoint point;
    point = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // Get bookmark button under point
    SRBookmarkButton*   oldButton;
    oldButton = [self _buttonUnderPoint:_mousePoint];
    if (oldButton) {
        // Refresh
        [oldButton setNeedsDisplay:YES];
    }
    
    SRBookmarkButton*   newButton;
    newButton = [self _buttonUnderPoint:point];
    if (newButton && newButton != oldButton) {
        // Refresh
        [newButton setNeedsDisplay:YES];
    }
    
    // Set mouse point
    _mousePoint = point;
}

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

- (void)bookmarkUpdated:(NSNotification*)notification
{
    // For bookmark bar
    SRBookmark* bookmarkForBookmarkBar;
    bookmarkForBookmarkBar = [[SRAppController sharedInstance] bookmarkForBookmarkBar];
    if ([notification object] == bookmarkForBookmarkBar) {
        // Update itself
        [self setBookmarks:[bookmarkForBookmarkBar sortedChildren]];
    }
}

@end
