/*
SRBookmark.m

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 "SRBookmark.h"
#import "SRBookmarkStorage.h"
#import "SRBookmarkIconDatabase.h"

#import "WebKitEx.h"

NSArray* SRBrowserNames = nil;

static NSString*    SRBookmarkType = @"Type";
static NSString*    SRBookmarkTitle = @"Title";
static NSString*    SRBookmarkURLString = @"URLString";
static NSString*    SRBookmarkIconURLString = @"IconURLString";
static NSString*    SRBookmarkMutable = @"Mutable";
static NSString*    SRBookmarkAutoTab = @"AutoTab";
static NSString*    SRBookmarkBrowser = @"Browser";
static NSString*    SRBookmarkChildren = @"Children";
static NSString*    SRBookmarkUUID = @"UUID";

//static NSString*    SRBookmarkInfoDictKey = @"SRBookmarkInfoDictKey";
//static NSString*    SRBookmarkParentKey = @"SRBookmarkParentKey";

@implementation SRBookmark

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

+ (void)initialize
{
    if (!SRBrowserNames) {
        SRBrowserNames = [[NSArray arrayWithObjects:
                @"Shiira", 
                @"Safari", 
                @"Firefox", 
                @"Camino", 
                @"Internet Explorer", 
                @"OmniWeb", 
                @"Opera", 
                @"iCab", 
                nil] retain];
    }
}

- (void)_setUUID
{
    // Create CFUUID
    CFUUIDRef   uuid;
    NSString*   uuidString;
    uuid = CFUUIDCreate(NULL);
    uuidString = (NSString*)CFUUIDCreateString(NULL, uuid);
    
    // Set UUID info dictionary
    [_infoDict setObject:uuidString forKey:SRBookmarkUUID];
    
    CFRelease(uuidString);
    CFRelease(uuid);
}

+ (SRBookmark*)bookmarkWithTitle:(NSString*)title 
        URLString:(NSString*)URLString 
        originalBrowser:(int)originalBrowser
{
    // Turn off notification
    BOOL    notifyFlag;
    notifyFlag = [[SRBookmarkStorage sharedInstance] notifyFlag];
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:NO];
    
    SRBookmark* bookmark;
    bookmark = [[SRBookmark alloc] initWithType:SRBookmarkTypeHTML 
            URLString:URLString 
            isMutable:YES 
            originalBrowser:originalBrowser];
    [bookmark autorelease];
    [bookmark setTitle:title];
    
    // Restore notification
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:notifyFlag];
    
    return bookmark;
}

+ (SRBookmark*)folderWithTitle:(NSString*)title 
        originalBrowser:(int)originalBrowser
{
    // Turn off notification
    BOOL    notifyFlag;
    notifyFlag = [[SRBookmarkStorage sharedInstance] notifyFlag];
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:NO];
    
    SRBookmark* bookmark;
    bookmark = [[SRBookmark alloc] initWithType:SRBookmarkTypeFolder 
            URLString:nil 
            isMutable:YES 
            originalBrowser:originalBrowser];
    [bookmark autorelease];
    [bookmark setTitle:title];
    
    // Restore notification
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:notifyFlag];
    
    return bookmark;
}

+ (SRBookmark*)bookmarkWithBookmark:(SRBookmark*)bookmark
{
    return [self bookmarkWithBookmark:bookmark deep:NO];
}

+ (SRBookmark*)bookmarkWithBookmark:(SRBookmark*)bookmark 
        deep:(BOOL)flag
{
    return [self bookmarkWithBookmark:bookmark 
            deep:flag 
            browser:[bookmark originalBrowser]];
}

+ (SRBookmark*)bookmarkWithBookmark:(SRBookmark*)bookmark 
        deep:(BOOL)flag 
        browser:(int)browser
{
    SRBookmark* copiedBookmark;

    // Turn off Notification
    BOOL    notifyFlag;
    notifyFlag = [[SRBookmarkStorage sharedInstance] notifyFlag];
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:NO];

    copiedBookmark = [[SRBookmark alloc] initWithType:[bookmark type] 
            URLString:[bookmark URLString] 
            isMutable:browser == SRBrowserShiira 
            originalBrowser:browser];
    [copiedBookmark autorelease];
    [copiedBookmark setTitle:[bookmark title]];
    
    // Copy child
    if (flag) {
        SRBookmarkStorage*  storage;
        storage = [SRBookmarkStorage sharedInstance];
        [storage setNotifyFlag:NO];
        
        NSEnumerator*   enumerator;
        SRBookmark*     child;
        enumerator = [[bookmark children] objectEnumerator];
        while (child = [enumerator nextObject]) {
            SRBookmark* copiedChild;
            copiedChild = [self bookmarkWithBookmark:child deep:flag browser:browser];
            [[copiedBookmark->_infoDict objectForKey:SRBookmarkChildren] addObject:copiedChild];
        }
        
        [storage setNotifyFlag:YES];
    }
    
    // Restore notification
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:notifyFlag];
   
    return copiedBookmark;
}

+ (SRBookmark*)bookmarksBar
{
    // Turn off notification
    BOOL    notifyFlag;
    notifyFlag = [[SRBookmarkStorage sharedInstance] notifyFlag];
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:NO];
    
    SRBookmark* bookmark;
    bookmark = [[SRBookmark alloc] initWithType:SRBookmarkTypeBookmarksBar 
            URLString:nil 
            isMutable:YES 
            originalBrowser:SRBrowserShiira];
    [bookmark autorelease];
    [bookmark setTitle:NSLocalizedStringFromTable(@"ShiiraBookmarksBar", @"Bookmark", nil)];
    
    // Restore notification
    [[SRBookmarkStorage sharedInstance] setNotifyFlag:notifyFlag];
    
    return bookmark;
}

- (id)initWithType:(int)type 
        URLString:(NSString*)URLString 
        isMutable:(BOOL)isMutable 
        originalBrowser:(int)originalBrowser
{
    self = [super init];
    if (self) {
        // Initialize instance variables
        _infoDict = [[NSMutableDictionary dictionary] retain];
        
        // Set attributes
        [_infoDict setObject:[NSNumber numberWithInt:type] 
                forKey:SRBookmarkType];
        [_infoDict setObject:[NSNumber numberWithInt:originalBrowser] 
                forKey:SRBookmarkBrowser];
        [_infoDict setObject:[NSNumber numberWithBool:isMutable] 
                forKey:SRBookmarkMutable];
        
        // For folder
        if ([self isFolderType]) {
            // Make children
            [_infoDict setObject:[NSMutableArray array] 
                    forKey:SRBookmarkChildren];
        }
        
        // For leaf
        else {
            // Set URL string
            [_infoDict setObject:URLString 
                    forKey:SRBookmarkURLString];
        }
        
        // Set uuid
        [self _setUUID];
    }
    
    return self;
}

- (id)initWithCoder:(NSCoder*)coder
{
    self = [super init];
    if (self) {
        _infoDict = [[coder decodeObject] retain];
    }
    
    return self;
}

- (void)encodeWithCoder:(NSCoder*)coder
{
    [coder encodeObject:_infoDict];
}

- (id)copyWithZone:(NSZone*)zone
{
    SRBookmark* bookmark;
    bookmark = [[SRBookmark alloc] init];
    bookmark->_infoDict = [_infoDict retain];
    
    return bookmark;
}

- (unsigned)hash
{
    return [[self UUID] hash];
}

- (BOOL)isEqual:(id)object
{
    return [[self UUID] isEqual:[object UUID]];
}

- (void)dealloc
{
    [_infoDict release];
    [super dealloc];
}

- (NSString*)description
{
    return [_infoDict description];
}

//--------------------------------------------------------------//
#pragma mark -- Bookmark attributes --
//--------------------------------------------------------------//
- (int)type
{
    return [[_infoDict objectForKey:SRBookmarkType] intValue];
}

- (BOOL)isFolderType
{
    // Get type
    int type;
    type = [self type];
    
    // Check type is kind of folde or not
    return type == SRBookmarkTypeFolder || 
           type == SRBookmarkTypeBookmarksMenu || 
           type == SRBookmarkTypeBookmarksBar;
}

- (NSString*)title
{
    return [_infoDict objectForKey:SRBookmarkTitle];
}

- (void)setTitle:(NSString*)title
{
    [_infoDict setObject:title forKey:SRBookmarkTitle];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkChangedNotificationName 
            object:[NSArray arrayWithObject:self]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (NSString*)URLString
{
    return [_infoDict objectForKey:SRBookmarkURLString];
}

- (void)setURLString:(NSString*)URLString
{
    [_infoDict setObject:URLString forKey:SRBookmarkURLString];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkChangedNotificationName 
            object:[NSArray arrayWithObject:self]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (NSImage*)icon
{
    // For folder
    if ([self isFolderType]) {
        switch ([self originalBrowser]) {
        case SRBrowserShiira: {
            return [NSImage imageNamed:@"shiiraFolder"];
        }
        case SRBrowserSafari: {
            return [NSImage imageNamed:@"safariFolder"];
        }
        case SRBrowserFirefox: {
            return [NSImage imageNamed:@"firefoxFolder"];
        }
        case SRBrowserCamino: {
            return [NSImage imageNamed:@"caminoFolder"];
        }
        case SRBrowserIE: {
            return [NSImage imageNamed:@"ieFolder"];
        }
        default: {
            return [NSImage imageNamed:@"otherFolder"];
        }
        }
    }
    
    // For leaf
    else {
        SRBookmarkIconDatabase* database;
        database = [SRBookmarkIconDatabase sharedInstance];
        
        NSImage*    icon = nil;
        
        // Get icon URL
        NSString*   iconURLString;
        iconURLString = [self iconURLString];
        if (iconURLString) {
            icon = [database iconForFaviconURLString:iconURLString];
        }
        if (icon) {
            return icon;
        }
        
        // Get favicon
        return [database iconOrDefaultIconForURLString:[self URLString]];
    }
}

- (NSString*)iconURLString
{
    return [_infoDict objectForKey:SRBookmarkIconURLString];
}

- (void)setIconURLString:(NSString*)URLString
{
    [_infoDict setObject:URLString forKey:SRBookmarkIconURLString];
}

- (int)originalBrowser
{
    return [[_infoDict objectForKey:SRBookmarkBrowser] intValue];
}

- (void)setOriginalBrowser:(int)browser
{
    [_infoDict setObject:[NSNumber numberWithInt:browser] 
            forKey:SRBookmarkBrowser];
}

- (BOOL)isShiiraBookmark
{
    // Get browser
    int browser;
    browser = [self originalBrowser];
    
    return browser == SRBrowserShiira;
}

- (BOOL)isMutable
{
    return [[_infoDict objectForKey:SRBookmarkMutable] boolValue];
}

- (BOOL)isAutoTab
{
    if (![self isFolderType]) {
        return NO;
    }
    
    // Get auto tab flag
    NSNumber*   number;
    number = [_infoDict objectForKey:SRBookmarkAutoTab];
    if (!number) {
        return NO;
    }
    return [number boolValue];
}

- (void)setAutoTab:(BOOL)isAutoTab
{
    if (![self isFolderType]) {
        // Do nothing
        return;
    }
    
    [_infoDict setObject:[NSNumber numberWithBool:isAutoTab] 
            forKey:SRBookmarkAutoTab];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkChangedNotificationName 
            object:[NSArray arrayWithObject:self]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)makeItShiira
{
    [self makeItOriginalBrowser:SRBrowserShiira];
}

- (void)makeItOriginalBrowser:(int)browser
{
    // Set original browser
    [self setOriginalBrowser:browser];
    // Set mutable
    [self setMutable:browser == SRBrowserShiira];
    
    // For folder
    if ([self isFolderType]) {
        // For children
        NSEnumerator*   enumerator;
        SRBookmark*     child;
        enumerator = [[self children] objectEnumerator];
        while (child = [enumerator nextObject]) {
            [child makeItOriginalBrowser:browser];
        }
    }
}

- (void)setMutable:(BOOL)isMutable
{
    [_infoDict setObject:[NSNumber numberWithBool:isMutable] 
            forKey:SRBookmarkMutable];
}

- (NSString*)UUID
{
    return [_infoDict objectForKey:SRBookmarkUUID];
}

- (void)setUUID:(NSString*)UUID
{
    if (!UUID) {
        [self _setUUID];
    }
    else {
        [_infoDict setObject:UUID forKey:SRBookmarkUUID];
    }
}

- (void)update
{
}

//--------------------------------------------------------------//
#pragma mark -- Handling hierarchy --
//--------------------------------------------------------------//

- (NSArray*)children
{
    return [_infoDict objectForKey:SRBookmarkChildren];
}

- (void)addChild:(SRBookmark*)child
{
    if (!child) {
        return;
    }
    
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    
    // Add child
    NSMutableArray* children;
    children = [_infoDict objectForKey:SRBookmarkChildren];
    [children addObject:child];
    
    // Change original browser
    //[self makeItOriginalBrowser:[self originalBrowser]];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
            object:[NSArray arrayWithObject:child]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)addChildren:(NSArray*)children
{
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    
    // Get children array
    NSMutableArray* array;
    array = [_infoDict objectForKey:SRBookmarkChildren];
    
    // Enumerate bookmark
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [children objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Add bookmark
        [array addObject:bookmark];
        
        // Change original browser
        //[self makeItOriginalBrowser:[self originalBrowser]];
    }
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
            object:children];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)insertChild:(SRBookmark*)child atIndex:(int)index
{
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    
    // Insert child
    NSMutableArray* children;
    children = [_infoDict objectForKey:SRBookmarkChildren];
    [children insertObject:child atIndex:index];
    
    // Change original browser
    //[self makeItOriginalBrowser:[self originalBrowser]];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
            object:[NSArray arrayWithObject:child]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)insertChildren:(NSArray*)children atIndex:(int)index
{
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    
    // Get children array
    NSMutableArray* array;
    array = [_infoDict objectForKey:SRBookmarkChildren];
    
    // Enumerate bookmark
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [children reverseObjectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Insert bookmark
        [array insertObject:bookmark atIndex:index];
        
        // Change original browser
        //[self makeItOriginalBrowser:[self originalBrowser]];
    }
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
            object:children];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)removeChild:(SRBookmark*)child
{
    SRBookmarkStorage*  bookmarkStorage;
    bookmarkStorage = [SRBookmarkStorage sharedInstance];
    
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    // Check special bookmark
    if (child == [bookmarkStorage rootBookmark] || 
        child == [bookmarkStorage shiiraBookmarksBar])
    {
        return;
    }
    
    // Remove child
    NSMutableArray* children;
    children = [_infoDict objectForKey:SRBookmarkChildren];
    if ([children indexOfObject:child] == NSNotFound) {
        // Warning
        SR_WARNING(@"Could not find child");
    }
    // Retain child to avoid immediate release
    [child retain];
    [children removeObject:child];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkRemovedNotificationName 
            object:[NSArray arrayWithObject:child]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
    
    // Release child
    [child release];
}

- (void)removeChildAtIndex:(int)index
{
    SRBookmarkStorage*  bookmarkStorage;
    bookmarkStorage = [SRBookmarkStorage sharedInstance];
    
    // Check self type and mutabilitiy
    if (![self isFolderType] || ![self isMutable]) {
        // Exception
        return;
    }
    // Check special bookmark
    if (self == [bookmarkStorage rootBookmark] || 
        self == [bookmarkStorage shiiraBookmarksBar])
    {
        return;
    }
    
    // Remove child
    NSMutableArray* children;
    SRBookmark*     child;
    children = [_infoDict objectForKey:SRBookmarkChildren];
    child = [[children objectAtIndex:index] retain];
    [children removeObjectAtIndex:index];
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkRemovedNotificationName 
            object:[NSArray arrayWithObject:child]];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
    [child release];
}

- (void)removeChildren:(NSArray*)children
{
    SRBookmarkStorage*  bookmarkStorage;
    bookmarkStorage = [SRBookmarkStorage sharedInstance];
    
    // Enumerate children
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    enumerator = [children objectEnumerator];
    while (child = [enumerator nextObject]) {
        // Get parent of child
        SRBookmark* parent;
        parent = [bookmarkStorage parentOfBookmark:child fromRoot:self];
        if (!parent) {
            continue;
        }
        
        // Check parent type and mutabilitiy
        if (![parent isFolderType] || ![parent isMutable]) {
            continue;
        }
        // Check child mutabilitiy
        if (![child isMutable]) {
            continue;
        }
        // Check special bookmark
        if (child == [bookmarkStorage rootBookmark] || 
            child == [bookmarkStorage shiiraBookmarksBar])
        {
            continue;
        }
        
        // Remove child
        NSMutableArray* array;
        array = [parent->_infoDict objectForKey:SRBookmarkChildren];
        if ([array indexOfObject:child] == NSNotFound) {
            // Warning
            SR_WARNING(@"Could not find child");
        }
        [array removeObject:child];
    }
    
    // Notify bookmark change
    NSNotification* notification;
    notification = [NSNotification notificationWithName:SRBookmarkRemovedNotificationName 
            object:children];
    [[SRBookmarkStorage sharedInstance] notifyBookmarkUpdated:notification];
}

- (void)removeAllChildren
{
    // Remove all children
    NSMutableArray* children;
    children = [_infoDict objectForKey:SRBookmarkChildren];
    [children removeAllObjects];
}

//--------------------------------------------------------------//
#pragma mark -- Dragging --
//--------------------------------------------------------------//

- (NSImage*)draggingImage
{
    // Get title
    NSString*   title;
    title = [self title];
    if (!title) {
        title = [self URLString];
        if (!title) {
            title = @"";
        }
    }
    
    // Get attributed sring and calc its size
    static NSDictionary*    _attrDict = nil;
    NSAttributedString*     attrString;
    NSSize                  stringSize;
    if (!_attrDict) {
        _attrDict = [[NSDictionary alloc] initWithObjectsAndKeys:
                [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], 
                NSFontAttributeName, 
                nil];
    }
    attrString = [[NSAttributedString alloc] initWithString:title attributes:_attrDict];
    [attrString autorelease];
    
    // Since too long string causes "Can't cache image" error, truncate it
    if ([attrString length] > 300) {
        attrString = [attrString attributedSubstringFromRange:NSMakeRange(0, 300)];
    }
    stringSize = [attrString size];
    if (stringSize.width > 1000) {
        stringSize.width = 1000;
    }
    
    // Get icon
    NSImage*    icon;
    icon = [self icon];
    
    // Create image
    NSSize      imageSize;
    NSImage*    draggingImage;
    imageSize = stringSize;
    imageSize.width += [icon size].width + 2;
    draggingImage = [[NSImage alloc] initWithSize:imageSize];
    [draggingImage autorelease];
    
    // Draw image
    NSMutableAttributedString*  coloredAttrString;
    float                       alpha = 0.7;
    
    coloredAttrString = [[NSMutableAttributedString alloc] 
            initWithAttributedString:attrString];
    [coloredAttrString autorelease];
    [coloredAttrString addAttribute:NSForegroundColorAttributeName 
            value:[NSColor colorWithCalibratedWhite:0.0 alpha:alpha] 
            range:NSMakeRange(0, [attrString length])];
    
    [NSGraphicsContext saveGraphicsState];
    [draggingImage lockFocus];
    [icon dissolveToPoint:NSZeroPoint fraction:alpha];
    [coloredAttrString drawAtPoint:NSMakePoint([icon size].width + 2, 0)];
    [draggingImage unlockFocus];
    [NSGraphicsContext restoreGraphicsState];
    
    return draggingImage;
}

@end
