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

#import "SRConstants.h"
#import "SRAppControllerProxy.h"

@implementation SRBookmarkConverter

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

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

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    
    return self;
}

//--------------------------------------------------------------//
#pragma mark -- Controller --
//--------------------------------------------------------------//

- (id<SRAppController>)appController
{
    return _appController;
}

- (void)setAppController:(id<SRAppController>)appController
{
    _appController = appController;
}

//--------------------------------------------------------------//
#pragma mark -- Conversion --
//--------------------------------------------------------------//

- (NSString*)_bookmarksPathOfShiira1
{
    // Create path ~/Library/Shiira/Bookmarks.plist
    NSArray*    libraryPaths;
    NSString*	bookmarksPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        bookmarksPath = [[libraryPaths objectAtIndex:0] stringByAppendingPathComponent:
                [NSString stringWithFormat:@"Shiira/%@", SRBookmarksFileName]];
    }
    
    return bookmarksPath;
}

- (NSString*)_bookmarksPathOfSafari
{
    // Get the paths of ~/Library/Safari/Bookmarks.plist
    NSArray*	libraryPaths;
    NSString*	bookmarksPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        bookmarksPath = [[libraryPaths objectAtIndex:0] 
                stringByAppendingPathComponent:@"Safari/Bookmarks.plist"];
    }
    
    return bookmarksPath;
}

- (NSString*)_bookmarksPathOfFirefox
{
    // Get the paths of ~/Library/Application Support/Firefox/profiles.ini
    NSArray*	libraryPaths;
    NSString*	profilesPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        profilesPath = [[libraryPaths objectAtIndex:0] 
                stringByAppendingPathComponent:@"Application Support/Firefox/profiles.ini"];
    }
    
    return profilesPath;
}

- (BOOL)hasBookmarksOfBrowser:(NSString*)browser
{
    NSString*   bookmarksPath = nil;
    
    // Find bookmarks path
    if ([browser isEqualToString:SRBrowserShiira]) {
        bookmarksPath = [self _bookmarksPathOfShiira1];
    }
    if ([browser isEqualToString:SRBrowserSafari]) {
        bookmarksPath = [self _bookmarksPathOfSafari];
    }
    if ([browser isEqualToString:SRBrowserFirefox]) {
        bookmarksPath = [self _bookmarksPathOfFirefox];
    }
    
    if (!bookmarksPath) {
        return NO;
    }
    
    return [[NSFileManager defaultManager] fileExistsAtPath:bookmarksPath];
}

- (void)_convertShiira1Bookmarks:(NSArray*)bookmarks withParent:(SRBookmark*)parent
{
    // Get managed object context
    NSManagedObjectContext* context;
    id                      store;
    context = [_appController managedObjectContext];
    store = [_appController persistentStore];
    
    // Convert bookmarks
    int             index = 0;
    NSEnumerator*   enumerator;
    NSDictionary*   bookmarkDict;
    enumerator = [bookmarks objectEnumerator];
    while (bookmarkDict = [enumerator nextObject]) {
        // Get bookmark info
        NSString*   type;
        NSString*   title;
        NSString*   URLString;
        type = [bookmarkDict objectForKey:@"Type"];
        title = [bookmarkDict objectForKey:@"Title"];
        URLString = [bookmarkDict objectForKey:@"URLString"];
        
        // Create bookmark
        SRBookmark* bookmark;
        bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
                inManagedObjectContext:context];
        [context assignObject:bookmark toPersistentStore:store];
        [bookmark setBrowser:SRBrowserShiira];
        [bookmark setType:type];
        [bookmark setTitle:title];
        [bookmark setUrlString:URLString];
        [bookmark setIsMutable:[NSNumber numberWithBool:YES]];
        [bookmark setIndex:[NSNumber numberWithInt:index++]];
        [parent addChildrenObject:bookmark];
        
        // For HTML
        if ([type isEqualToString:@"HTML"]) {
            if (HMIsRSSURLString(URLString)) {
                [bookmark setValue:@"RSS" forKey:@"type"];
            }
            else if (HMIsJavaScriptURLString(URLString)) {
                [bookmark setValue:SRBookmarkTypeJavaScript forKey:@"type"];
            }
            else {
                [bookmark setValue:SRBookmarkTypeHTML forKey:@"type"];
            }
        }
        // For folder
        else if ([type isEqualToString:@"Folder"]) {
            [bookmark setValue:SRBookmarkTypeFolder forKey:@"type"];
            
            BOOL        isAutoTab = NO;
            NSNumber*   number;
            if (number = [bookmarkDict objectForKey:@"AutoTab"]) {
                isAutoTab = [number boolValue];
            }
            [bookmark setValue:[NSNumber numberWithBool:isAutoTab] forKey:@"isAutoTab"];
        }
        // For bookmark bar
        else if ([type isEqualToString:@"BookmarksBar"]) {
            [bookmark setValue:SRBookmarkTypeBookmarkBar forKey:@"type"];
            [bookmark setValue:NSLocalizedString(@"Bookmarks Bar", nil) 
                    forKey:@"title"];
        }
        else {
            [bookmark setValue:SRBookmarkTypeHTML forKey:@"type"];
        }
        
        // Get children
        NSArray*    children;
        children = [bookmarkDict objectForKey:@"Children"];
        if (children) {
            // Convert children
            [self _convertShiira1Bookmarks:children withParent:bookmark];
        }
    }
}

- (void)_convertBookmarksOfShiira1
{
    // Get bookmarks path
    NSString*	bookmarksPath;
    bookmarksPath = [self _bookmarksPathOfShiira1];
    if (!bookmarksPath) {
        return;
    }
    
    // Read bookmark plist
    NSDictionary*   bookmarkDict = nil;
    NSData*         data;
    if ([[NSFileManager defaultManager] fileExistsAtPath:bookmarksPath]) {
        data = [NSData dataWithContentsOfFile:bookmarksPath];
        bookmarkDict = [NSPropertyListSerialization propertyListFromData:data 
                mutabilityOption:0 format:NULL errorDescription:NULL];
    }
    if (!bookmarkDict) {
        return;
    }
    
    // Get cildren of root
    NSArray*    children;
    children = [bookmarkDict objectForKey:@"Children"];
    if (children) {
        // Convert children
        [self _convertShiira1Bookmarks:children 
                withParent:[_appController rootBookmarkOfBrowser:SRBrowserShiira]];
    }
}

- (void)_convertSafariBookmarks:(NSArray*)bookmarks withParent:(SRBookmark*)parent
{
    // Get managed object context
    NSManagedObjectContext* context;
    id                      store;
    context = [_appController managedObjectContext];
    store = [_appController memoryPersistentStore];
    
    // Convert bookmarks
    int             index = 0;
    NSEnumerator*   enumerator;
    NSDictionary*   bookmarkDict;
    enumerator = [bookmarks objectEnumerator];
    while (bookmarkDict = [enumerator nextObject]) {
        SRBookmark* bookmark = nil;
        
        // Get bookmark info
        NSString*   type;
        type = [bookmarkDict objectForKey:SafariBookmarkTypeName];
        
        // For list
        if ([type isEqualToString:SafariBookmarksTypeListName] || 
            [type isEqualToString:SafariBookmarksBarName] || 
            [type isEqualToString:SafariBookmarksMenuName])
        {
            // Get title
            NSString*   title;
            NSString*   localizedTitle;
            title = [bookmarkDict objectForKey:SafariBookmarkTitleName];
            localizedTitle = title;
            if (!localizedTitle) {
                if ([bookmarkDict objectForKey:SafariBookmarkVersion]) {
                    // This is root
                    localizedTitle = NSLocalizedString(@"Safari Bookmarks", nil);
                }
                else {
                    localizedTitle = NSLocalizedString(@"Untitled", nil);
                }
            }
            else if([localizedTitle isEqualToString:SafariBookmarksBarName]) {
                // Safari bookmarks bar
                localizedTitle = NSLocalizedString(@"Safari Bookmarks Bar", nil);
            }
            else if([localizedTitle isEqualToString:SafariBookmarksMenuName]) {
                // Safari bookmarks menu
                localizedTitle = NSLocalizedString(@"Safari Bookmarks Menu", nil);
            }
            
            // Get auto tab
            NSNumber*   isAutoTab;
            isAutoTab = [bookmarkDict objectForKey:SafariBookmarkAutoTabName];
            
            // Create bookmark
            bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
                    inManagedObjectContext:context];
            [context assignObject:bookmark toPersistentStore:store];
            [bookmark setBrowser:SRBrowserSafari];
            [bookmark setIsMutable:[NSNumber numberWithBool:NO]];
            [bookmark setType:SRBookmarkTypeFolder];
            [bookmark setTitle:localizedTitle];
            [bookmark setIndex:[NSNumber numberWithInt:index++]];
            [parent addChildrenObject:bookmark];
            if (isAutoTab) {
                [bookmark setValue:[NSNumber numberWithBool:[isAutoTab boolValue]] 
                        forKey:@"isAutoTab"];
            }
            
            // Add children
            NSArray*        children;
            children = [bookmarkDict objectForKey:SafariBookmarkChildrenName];
            if (children) {
                // Convert children
                [self _convertSafariBookmarks:children withParent:bookmark];
            }
        }
        
        // For leaf
        else if ([type isEqualToString:SafariBookmarksTypeLeafName]) {
            // Get title, URLString
            NSString*   title;
            NSString*   URLString;
            title = [[bookmarkDict objectForKey:SafariBookmarkURIDictionaryName] 
                    objectForKey:SafariBookmarkURITitleName];
            URLString = [bookmarkDict objectForKey:SafariBookmarkURLName];
            
            if (title && URLString) {
                // Create bookmark
                bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
                        inManagedObjectContext:context];
                [context assignObject:bookmark toPersistentStore:store];
                [bookmark setBrowser:SRBrowserSafari];
                [bookmark setIsMutable:[NSNumber numberWithBool:NO]];
                [bookmark setTitle:title];
                [bookmark setUrlString:URLString];
                [bookmark setIndex:[NSNumber numberWithInt:index++]];
                [parent addChildrenObject:bookmark];
                
                if (HMIsRSSURLString(URLString)) {
                    [bookmark setValue:@"RSS" forKey:@"type"];
                }
                else if (HMIsJavaScriptURLString(URLString)) {
                    [bookmark setValue:SRBookmarkTypeJavaScript forKey:@"type"];
                }
                else {
                    [bookmark setValue:SRBookmarkTypeHTML forKey:@"type"];
                }
            }
        }
	}
}

- (void)_convertBookmarksOfSafari
{
    // Get bookmarks path
    NSString*	safariBookmarkPath;
    safariBookmarkPath = [self _bookmarksPathOfSafari];
    if (!safariBookmarkPath) {
        return;
    }
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:safariBookmarkPath]) {
        NSLog(@"Could not find Safari bookmark at %@", safariBookmarkPath);
        return;
    }
    
    // Read bookmark dictionary
    NSDictionary*   bookmarkDict;
    bookmarkDict = [NSDictionary dictionaryWithContentsOfFile:safariBookmarkPath];
    if (!bookmarkDict) {
        NSLog(@"Could not read Safari bookmark file");
        return;
    }
    
    // Convert Safari bookmark
    [self _convertSafariBookmarks:[bookmarkDict objectForKey:SafariBookmarkChildrenName] 
            withParent:[_appController rootBookmarkOfBrowser:SRBrowserSafari]];
}

- (NSString*)_removeTags:(NSArray*)tags fromHtml:(NSString*)html
{
    NSMutableString*    buffer;
    buffer = [NSMutableString string];
    
    NSScanner*  scanner;
    scanner = [NSScanner scannerWithString:html];
    [scanner setCharactersToBeSkipped:nil];
    while (![scanner isAtEnd]) {
        // Scan '<'
        NSString*   token;
        if ([scanner scanUpToString:@"<" intoString:&token]) {
            [buffer appendString:token];
        }
        
        // Scan '>'
        NSString*   tag;
        if ([scanner scanUpToString:@">" intoString:&tag]) {
            // Append tag if it is not contained in tags
            tag = [tag stringByAppendingString:@">"];
            if (![tags containsObject:tag]) {
                [buffer appendString:tag];
            }
            [scanner scanString:@">" intoString:nil];
        }
    }
    
    return buffer;
}

- (void)_convertBookmarksOfFirefoxWithDLNode:(NSXMLNode*)dlNode 
        parent:(SRBookmark*)parent
{
    // Get managed object context
    NSManagedObjectContext* context;
    id                      store;
    context = [_appController managedObjectContext];
    store = [_appController memoryPersistentStore];
    
    // Convert bookmarks
    int             index = 0;
    NSEnumerator*   enumerator;
    NSXMLNode*      node;
    enumerator = [[dlNode children] objectEnumerator];
    while (node = [enumerator nextObject]) {
        // For DD node
        if ([[node name] isEqualToString:@"dd"]) {
            // When DD has H3, it is a folder
            NSArray*    h3Nodes;
            h3Nodes = [node nodesForXPath:@"h3" error:NULL];
            if ([h3Nodes count] > 0) {
                NSXMLElement*   h3Element;
                h3Element = [h3Nodes objectAtIndex:0];
                
                // Get title
                NSString*   title;
                title = [h3Element stringValue];
                title = [title stringByTrimmingCharactersInSet:[NSCharacterSet newLineCharacterSet]];
                
                // Check PERSONAL_TOOLBAR_FOLDER attribute
                if ([[[h3Element attributeForName:@"personal_toolbar_folder"] 
                        stringValue] isEqualToString:@"true"])
                {
                    // Firefox bookmarks bar
                    title = NSLocalizedString(@"Firefox Bookmarks Bar", nil);
                }
                
                // Create folder
                SRBookmark* bookmark;
                bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
                        inManagedObjectContext:context];
                [context assignObject:bookmark toPersistentStore:store];
                [bookmark setBrowser:SRBrowserFirefox];
                [bookmark setIsMutable:[NSNumber numberWithBool:NO]];
                [bookmark setType:SRBookmarkTypeFolder];
                [bookmark setTitle:title];
                [bookmark setIndex:[NSNumber numberWithInt:index++]];
                [parent addChildrenObject:bookmark];
                
                // Get dlNode
                NSArray*    dlNodes;
                dlNodes = [node nodesForXPath:@"dl" error:NULL];
                if ([dlNodes count] > 0) {
                    [self _convertBookmarksOfFirefoxWithDLNode:[dlNodes objectAtIndex:0] parent:bookmark];
                }
            }
        }
        
        // For DT node
        if ([[node name] isEqualToString:@"dt"]) {
            NSArray*    aNodes;
            aNodes = [node nodesForXPath:@"a" error:NULL];
            if ([aNodes count] > 0) {
                NSXMLElement*   aElement;
                aElement = [aNodes objectAtIndex:0];
                
                // Get title and URL string
                NSString*   title;
                NSString*   URLString;
                title = [aElement stringValue];
                title = [title stringByTrimmingCharactersInSet:[NSCharacterSet newLineCharacterSet]];
                URLString = [[aElement attributeForName:@"href"] stringValue];
                
                // Create bookmark
                if (title && URLString) {
                    SRBookmark* bookmark;
                    bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
                            inManagedObjectContext:context];
                    [context assignObject:bookmark toPersistentStore:store];
                    [bookmark setBrowser:SRBrowserFirefox];
                    [bookmark setIsMutable:[NSNumber numberWithBool:NO]];
                    [bookmark setTitle:title];
                    [bookmark setUrlString:URLString];
                    [bookmark setIndex:[NSNumber numberWithInt:index++]];
                    [parent addChildrenObject:bookmark];
                    
                    if (HMIsRSSURLString(URLString)) {
                        [bookmark setValue:@"RSS" forKey:@"type"];
                    }
                    else if (HMIsJavaScriptURLString(URLString)) {
                        [bookmark setValue:SRBookmarkTypeJavaScript forKey:@"type"];
                    }
                    else {
                        [bookmark setValue:SRBookmarkTypeHTML forKey:@"type"];
                    }
                }
            }
        }
    }
}

- (void)_convertBookmarksOfFirefox
{
    // Get profiles path
    NSString*	profilesPath;
    profilesPath = [self _bookmarksPathOfFirefox];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:profilesPath]) {
        //NSLog(@"Could not find Firefox profiles at %@", profilesPath);
        return;
    }
    
    // Parse profiles.ini
    NSString*   profiles;
    profiles = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:profilesPath] 
            encoding:NSUTF8StringEncoding];
    if (!profiles) {
        return;
    }
    [profiles autorelease];
    
    NSScanner*  scanner;
    NSString*   profilePath = nil;
    scanner = [NSScanner scannerWithString:profiles];
    while (![scanner isAtEnd]) {
        NSString*   token;
        if ([scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] 
                intoString:&token])
        {
            if ([token hasPrefix:@"Path="]) {
                // Remove 'Path='
                profilePath = [token substringFromIndex:5];
                break;
            }
        }
        
        [scanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:nil];
    }
    if (!profilePath) {
        return;
    }
    
    // Get bookmarks path
    NSString*   bookmarksPath;
    bookmarksPath = [[profilesPath stringByDeletingLastPathComponent] 
            stringByAppendingPathComponent:profilePath];
    bookmarksPath = [bookmarksPath stringByAppendingPathComponent:@"bookmarks.html"];
    if (![fileMgr fileExistsAtPath:bookmarksPath]) {
        //NSLog(@"Could not find Firefox bookmarks at %@", bookmarksPath);
        return;
    }
    
    // Remove unneccessary tags
    static NSArray* _tags = nil;
    if (!_tags) {
        _tags = [[NSArray arrayWithObjects:@"<p>", @"<P>", @"<dd>", @"<DD>", @"<hr>", @"<HR>", nil] retain];
    }
    
    NSString*   html;
    html = [[NSString alloc] 
            initWithData:[NSData dataWithContentsOfFile:bookmarksPath] encoding:NSUTF8StringEncoding];
    [html autorelease];
    html = [self _removeTags:_tags fromHtml:html];
    
    // Read bookmarks
    NSXMLDocument*  document;
    document = [[NSXMLDocument alloc] initWithXMLString:html options:NSXMLDocumentTidyHTML error:NULL];
    if (!document) {
        NSLog(@"Could not parse Firefox bookmarks at %@", bookmarksPath);
        return;
    }
    
    // Get dl nodes
    NSArray*    dlNodes;
    dlNodes = [[document rootElement] nodesForXPath:@"body/dl" error:NULL];
    if ([dlNodes count] == 1) {
        // Convert Firefox bookmark
        [self _convertBookmarksOfFirefoxWithDLNode:[dlNodes objectAtIndex:0] 
                parent:[_appController rootBookmarkOfBrowser:SRBrowserFirefox]];
    }
}

- (void)convertBookmarksOfBrowser:(NSString*)browser
{
    // Convert bookmarks
    if ([browser isEqualToString:SRBrowserShiira]) {
        [self _convertBookmarksOfShiira1];
        return;
    }
    if ([browser isEqualToString:SRBrowserSafari]) {
        [self _convertBookmarksOfSafari];
        return;
    }
    if ([browser isEqualToString:SRBrowserFirefox]) {
        [self _convertBookmarksOfFirefox];
        return;
    }
}

- (void)importDefaultBookmarks
{
    // Get default bookmarks path
    NSString*	bookmarksPath;
    bookmarksPath = [[NSBundle mainBundle] 
            pathForResource:@"Bookmarks" ofType:@"plist"];
    if (!bookmarksPath) {
        return;
    }
    
    // Read bookmark plist
    NSDictionary*   bookmarkDict = nil;
    NSData*         data;
    if ([[NSFileManager defaultManager] fileExistsAtPath:bookmarksPath]) {
        data = [NSData dataWithContentsOfFile:bookmarksPath];
        bookmarkDict = [NSPropertyListSerialization propertyListFromData:data 
                mutabilityOption:0 format:NULL errorDescription:NULL];
    }
    if (!bookmarkDict) {
        return;
    }
    
    // Get cildren of root
    NSArray*    children;
    children = [bookmarkDict objectForKey:@"Children"];
    if (children) {
        // Convert children
        [self _convertShiira1Bookmarks:children 
                withParent:[_appController rootBookmarkOfBrowser:SRBrowserShiira]];
    }
}

@end
