/*
SRBookmark.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 "SRConstants.h"

#import "SRBookmark.h"

@implementation SRBookmark 

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

+ (SRBookmark*)bookmarkWithTitle:(NSString*)title 
        URLString:(NSString*)urlString 
        context:(NSManagedObjectContext*)context 
        persistentStore:(id)store
{
    // Replace new line characters
    if ([title rangeOfCharacterFromSet:[NSCharacterSet newLineCharacterSet]].location != NSNotFound) {
        NSMutableString*    string;
        string = [NSMutableString stringWithString:title];
        [string replaceOccurrencesOfString:@"\n" withString:@" " 
                options:0 range:NSMakeRange(0, [string length])]; 
        title = string;
    }
    
    // Create bookmark
    SRBookmark* bookmark;
    bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
            inManagedObjectContext:context];
    [context assignObject:bookmark toPersistentStore:store];
    [bookmark setBrowser:SRBrowserShiira];
    [bookmark setTitle:title];
    [bookmark setUrlString:urlString];
    [bookmark setIsMutable:[NSNumber numberWithBool:YES]];
    
    // For RSS
    if (HMIsRSSURLString(urlString)) {
        [bookmark setType:@"RSS"];
    }
    // For JavaScript
    else if (HMIsJavaScriptURLString(urlString)) {
        [bookmark setType:SRBookmarkTypeJavaScript];
    }
    // Other
    else {
        [bookmark setType:SRBookmarkTypeHTML];
    }
    
    return bookmark;
}

+ (SRBookmark*)folderWithTitle:(NSString*)title 
        context:(NSManagedObjectContext*)context 
        persistentStore:(id)store
{
    // Replace new line characters
    if ([title rangeOfCharacterFromSet:[NSCharacterSet newLineCharacterSet]].location != NSNotFound) {
        NSMutableString*    string;
        string = [NSMutableString stringWithString:title];
        [string replaceOccurrencesOfString:@"\n" withString:@" " 
                options:0 range:NSMakeRange(0, [string length])]; 
        title = string;
    }
    
    SRBookmark* bookmark;
    bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
            inManagedObjectContext:context];
    [context assignObject:bookmark toPersistentStore:store];
    [bookmark setBrowser:SRBrowserShiira];
    [bookmark setType:SRBookmarkTypeFolder];
    [bookmark setTitle:title];
    [bookmark setIsMutable:[NSNumber numberWithBool:YES]];
    [bookmark setIsAutoTab:[NSNumber numberWithBool:NO]];
    
    return bookmark;
}

+ (SRBookmark*)bookmarkForBookmarkBarWithContext:(NSManagedObjectContext*)context 
        persistentStore:(id)store
{
    SRBookmark* bookmark;
    bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
            inManagedObjectContext:context];
    [context assignObject:bookmark toPersistentStore:store];
    [bookmark setBrowser:SRBrowserShiira];
    [bookmark setType:SRBookmarkTypeBookmarkBar];
    [bookmark setTitle:NSLocalizedString(@"Bookmarks Bar", nil)];
    [bookmark setIsMutable:[NSNumber numberWithBool:YES]];
    [bookmark setValue:0 forKey:@"index"];
    
    return bookmark;
}

//--------------------------------------------------------------//
#pragma mark -- Accessor --
//--------------------------------------------------------------//

- (NSString*)title 
{
    NSString* tmpValue;
    
    [self willAccessValueForKey: @"title"];
    tmpValue = [self primitiveValueForKey: @"title"];
    [self didAccessValueForKey: @"title"];
    
    return tmpValue;
}

- (void)setTitle:(NSString*)value 
{
    [self willChangeValueForKey: @"title"];
    [self setPrimitiveValue: value forKey: @"title"];
    [self didChangeValueForKey: @"title"];
}

- (NSString*)type 
{
    NSString* tmpValue;
    
    [self willAccessValueForKey: @"type"];
    tmpValue = [self primitiveValueForKey: @"type"];
    [self didAccessValueForKey: @"type"];
    
    return tmpValue;
}

- (void)setType:(NSString*)value 
{
    [self willChangeValueForKey: @"type"];
    [self setPrimitiveValue: value forKey: @"type"];
    [self didChangeValueForKey: @"type"];
}

- (NSString*)browser 
{
    NSString* tmpValue;
    
    [self willAccessValueForKey: @"browser"];
    tmpValue = [self primitiveValueForKey: @"browser"];
    [self didAccessValueForKey: @"browser"];
    
    return tmpValue;
}

- (void)setBrowser:(NSString*)value 
{
    [self willChangeValueForKey: @"browser"];
    [self setPrimitiveValue: value forKey: @"browser"];
    [self didChangeValueForKey: @"browser"];
}

- (NSNumber*)isMutable 
{
    NSNumber* tmpValue;
    
    [self willAccessValueForKey: @"isMutable"];
    tmpValue = [self primitiveValueForKey: @"isMutable"];
    [self didAccessValueForKey: @"isMutable"];
    
    return tmpValue;
}

- (void)setIsMutable:(NSNumber*)value 
{
    [self willChangeValueForKey: @"isMutable"];
    [self setPrimitiveValue: value forKey: @"isMutable"];
    [self didChangeValueForKey: @"isMutable"];
}

- (NSNumber*)index
{
    NSNumber* tmpValue;
    
    [self willAccessValueForKey:@"index"];
    tmpValue = [self primitiveValueForKey:@"index"];
    [self didAccessValueForKey:@"index"];
    
    return tmpValue;
}

- (void)setIndex:(NSNumber*)value
{
    [self willChangeValueForKey:@"index"];
    [self setPrimitiveValue: value forKey:@"index"];
    [self didChangeValueForKey:@"index"];
}

- (NSString*)urlString 
{
    NSString* tmpValue;
    
    [self willAccessValueForKey: @"urlString"];
    tmpValue = [self primitiveValueForKey: @"urlString"];
    [self didAccessValueForKey: @"urlString"];
    
    return tmpValue;
}

- (void)setUrlString:(NSString*)value 
{
    [self willChangeValueForKey: @"urlString"];
    [self setPrimitiveValue: value forKey: @"urlString"];
    [self didChangeValueForKey: @"urlString"];
}

- (NSNumber*)isAutoTab 
{
    NSNumber* tmpValue;
    
    [self willAccessValueForKey:@"isAutoTab"];
    tmpValue = [self primitiveValueForKey:@"isAutoTab"];
    [self didAccessValueForKey:@"isAutoTab"];
    
    return tmpValue;
}

- (void)setIsAutoTab:(NSNumber*)value 
{
    [self willChangeValueForKey: @"isAutoTab"];
    [self setPrimitiveValue:value forKey:@"isAutoTab"];
    [self didChangeValueForKey: @"isAutoTab"];
    
    // Notify update to redraw bookmark bar
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRBookmarkUpdated object:self];
}

- (void)addChildrenObject:(SRBookmark*)value 
{    
    NSSet*  changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    
    [self willChangeValueForKey:@"children" 
            withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    [[self primitiveValueForKey:@"children"] addObject:value];
    [self didChangeValueForKey:@"children" 
            withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    
    [changedObjects release];
}

- (void)removeChildrenObject:(SRBookmark*)value 
{
    NSSet*  changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    
    [self willChangeValueForKey:@"children" 
            withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    [[self primitiveValueForKey:@"children"] removeObject:value];
    [[value managedObjectContext] deleteObject:value];
    [self didChangeValueForKey:@"children" 
            withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    
    [changedObjects release];
}

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

- (NSImage*)icon 
{
    // Get type
    NSString*   type;
    type = [self type];
    
    // For folder
    if ([type isEqualToString:SRBookmarkTypeFolder]) {
        return [NSImage imageNamed:@"bookmarkFolder"];
    }
    // For bookmark bar
    if ([type isEqualToString:SRBookmarkTypeBookmarkBar]) {
        return [NSImage imageNamed:@"bookmarkBar"];
    }
    // For bookmark menu
    if ([type isEqualToString:SRBookmarkTypeBookmarkMenu]) {
        return [NSImage imageNamed:@"bookmarkMenu"];
    }
    
    // Get URL string
    NSString*   urlString;
    urlString = [self urlString];
    if (!urlString) {
        return nil;
    }
    
    // For RSS
    if (HMIsRSSURLString(urlString)) {
        return [NSImage imageNamed:@"rssPage"];
    }
    // For JavaScript
    else if (HMIsJavaScriptURLString(urlString)) {
        return [NSImage imageNamed:@"javaScriptPage"];
    }
    
    // For HTML
    if ([[WebIconDatabase sharedIconDatabase] iconURLForURL:urlString]) {
        return [[WebIconDatabase sharedIconDatabase] 
                iconForURL:urlString withSize:NSMakeSize(16, 16)];
    }
    
    return [NSImage imageNamed:@"bookmarkPage"];
}

- (NSNumber*)isFolder
{
    NSString*   type;
    type = [self type];
    
    return [NSNumber numberWithBool:
            [type isEqualToString:SRBookmarkTypeFolder] || 
            [type isEqualToString:SRBookmarkTypeBookmarkBar] || 
            [type isEqualToString:SRBookmarkTypeBookmarkMenu] || 
            [type isEqualToString:SRBookmarkTypeRoot]];
}

- (NSNumber*)isLeaf
{
    return [NSNumber numberWithBool:![[self isFolder] boolValue]];
}

//--------------------------------------------------------------//
#pragma mark -- Working with children --
//--------------------------------------------------------------//

- (NSArray*)sortedChildren
{
    // Get children
    NSArray*            children;
    NSSortDescriptor*   sortDescriptor;
    children = [[self valueForKey:@"children"] allObjects];
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
    children = [children sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];
    
    return children;
}

- (void)insertBookmarks:(NSArray*)bookmarks atIndex:(unsigned int)index
{
    if (![[self isFolder] boolValue]) {
        return;
    }
    
    // Get children
    NSArray*    children;
    children = [self sortedChildren];
    
    // Decide index value
    int indexValue = 0;
    if (index > 0) {
        indexValue = [[[children objectAtIndex:index - 1] valueForKey:@"index"] intValue] + 1;
    }
    
    // Reset index
    int i, tmp = indexValue + [bookmarks count];
    for (i = index; i < [children count]; i++) {
        SRBookmark* child;
        child = [children objectAtIndex:i];
        [child setValue:[NSNumber numberWithInt:tmp++] forKey:@"index"];
    }
    
    // Insert bookmarks
    for (i = 0; i < [bookmarks count]; i++) {
        SRBookmark* bookmark;
        bookmark = [bookmarks objectAtIndex:i];
        [bookmark setValue:[NSNumber numberWithInt:indexValue + i] forKey:@"index"];
        [self addChildrenObject:bookmark];
    }
    
    // Notify update
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRBookmarkUpdated object:self];
}

- (void)addBookmarks:(NSArray*)bookmarks
{
    [self insertBookmarks:bookmarks atIndex:[[self valueForKey:@"children"] count]];
}

- (void)removeBookmarks:(NSArray*)bookmarks
{
    if (![[self isFolder] boolValue]) {
        return;
    }
    
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    enumerator = [bookmarks objectEnumerator];
    while (child = [enumerator nextObject]) {
        [self removeChildrenObject:child];
    }
    
    // Notify update
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRBookmarkUpdated object:self];
}

- (BOOL)isDescendantOf:(SRBookmark*)bookmark
{
    // When itself is root
    if ([[self type] isEqualToString:SRBookmarkTypeRoot]) {
        return NO;
    }
    
    // Check with parent
    SRBookmark* parent;
    parent = bookmark;
    while (parent) {
        if (parent == self) {
            return YES;
        }
        
        if (![parent isKindOfClass:[SRBookmark class]]) {
            return NO;
        }
        parent = [parent valueForKey:@"parent"];
    }
    
    return NO;
}

@end

#pragma mark -

@implementation SRBookmarkFactory

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

+ (id)sharedInstanceWithManagedObjectContext:(NSManagedObjectContext*)context 
        persistentStore:(id)store
{
    static SRBookmarkFactory*   _sharedInstance = nil;
    if (!_sharedInstance) {
        _sharedInstance = [[SRBookmarkFactory alloc] initWithManagedObjectContext:context 
                persistentStore:store];
    }
    
    return _sharedInstance;
}

- (id)initWithManagedObjectContext:(NSManagedObjectContext*)context 
        persistentStore:(id)store
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _context = context;
    _store = store;
    
    return self;
}

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

- (SRBookmark*)bookmarkWithTitle:(NSString*)title 
        URLString:(NSString*)urlString
{
    return [SRBookmark bookmarkWithTitle:title 
            URLString:urlString context:_context persistentStore:_store];
}

- (SRBookmark*)folderWithTitle:(NSString*)title
{
    return [SRBookmark folderWithTitle:title context:_context persistentStore:_store];
}

- (SRBookmark*)bookmarkForBookmarkBar
{
    return [SRBookmark bookmarkForBookmarkBarWithContext:_context persistentStore:_store];
}

//--------------------------------------------------------------//
#pragma mark -- Copying --
//--------------------------------------------------------------//

- (SRBookmark*)copyBookmark:(SRBookmark*)bookmark
{
    SRBookmark* copiedBookmark;
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        copiedBookmark = [self folderWithTitle:[bookmark title]];
        
        // For children
        NSMutableArray* copiedChildren;
        NSArray*        children;
        NSEnumerator*   enumerator;
        SRBookmark*     child;
        copiedChildren = [NSMutableArray array];
        children = [bookmark sortedChildren];
        enumerator = [children objectEnumerator];
        while (child = [enumerator nextObject]) {
            SRBookmark* copiedChild;
            copiedChild = [self copyBookmark:child];
            
            [copiedChildren addObject:copiedChild];
        }
        
        // Add children
        [copiedBookmark addBookmarks:copiedChildren];
    }
    
    // For leaf
    else {
        copiedBookmark = [self bookmarkWithTitle:[bookmark title] 
                URLString:[bookmark urlString]];
    }
    
    return copiedBookmark;
}

@end
