// Copyright 2003 - 2004 Nathan Hamblen
// Modified BSD license:
// 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.
// 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "ImportController.h"
#import "MyDocument.h"

@interface ImportController(Internals)
- (int)importBookmarksDictionary:(NSDictionary *)dict toDirectory:(NSString *)currentDir;
@end

// This is the window controller for the import progress window and controller for the import procedure itself. Furthermore, a separate instance of the class responds to the menu command(s) that initiate imports. This global instance intantiates the window controller / import procedure controller instances.
@implementation ImportController

#pragma mark  Initilization

// Displays the import window and kicks off the import procedure.
- (id)initSafariImportWindow
{
  NSString *bookmarksPath = [@"~/Library/Safari/Bookmarks.plist"
    stringByExpandingTildeInPath];
  NSDictionary *bookmarks;
  NSFileManager *fileManager = [NSFileManager defaultManager];

  self = [super initWithWindowNibName:@"Import"];

  if (!self)
    return nil;

  [self showWindow:self];

  mNewBookmarksFolder = nil;
  mFilePathDictionary = nil;

  mNewBookmarksFolder = [[@"~/Desktop/Imported Safari Bookmarks"
    stringByExpandingTildeInPath] retain];

  if (![fileManager fileExistsAtPath:bookmarksPath]) {
    NSBeginCriticalAlertSheet(@"Import Error",  
// title
                              nil,              
// default button is Ok
                              nil, nil,          
// no other buttons
                              [self window],    
// window for sheet
                              self,              
// sheet end delegate
                              nil,              
// modal session ended selector
                              @selector(sheetDidDismiss:returnCode:contextInfo:),      
// sheet dismissed selector
                              nil,              
// context info
                              @"Unable to find your Safari bookmarks file.");
    return self;
  }
  bookmarks = [NSDictionary  dictionaryWithContentsOfFile:bookmarksPath];
  if (!bookmarks) {
    NSBeginCriticalAlertSheet(@"Import Error",  
// title
                              nil,              
// default button is Ok
                              nil, nil,          
// no other buttons
                              [self window],    
// window for sheet
                              self,              
// sheet end delegate
                              nil,              
// modal session ended selector
                              @selector(sheetDidDismiss:returnCode:contextInfo:),      
// sheet dismissed selector
                              nil,              
// context info
                              @"Error reading your Safari bookmarks file.");
    return self;
  }

  if([fileManager fileExistsAtPath:mNewBookmarksFolder]) {
    NSBeginCriticalAlertSheet(@"Import Error",  
// title
                              nil,              
// default button is Ok
                              nil, nil,          
// no other buttons
                              [self window],    
// window for sheet
                              self,              
// sheet end delegate
                              nil,              
// modal session ended selector
                              @selector(sheetDidDismiss:returnCode:contextInfo:),      
// sheet dismissed selector
                              nil,              
// context info
                              @"There is already an imported bookmarks folder on your desktop. Please move or rename this folder and try again.");
    return self;
  }

  if(![fileManager createDirectoryAtPath:mNewBookmarksFolder attributes:nil]) {
    NSBeginCriticalAlertSheet(@"Import Error",  
// title
                              nil,              
// default button is Ok
                              nil, nil,          
// no other buttons
                              [self window],    
// window for sheet
                              self,              
// sheet end delegate
                              nil,              
// modal session ended selector
                              @selector(sheetDidDismiss:returnCode:contextInfo:),      
// sheet dismissed selector
                              nil,              
// context info
                              @"Error creating your imported bookmarks directory.");
    return self;
  }
  mFilePathDictionary = [[NSMutableDictionary alloc] initWithCapacity:128];

  mPartialLoadCount = 0;

  mBookmarksCount = [self importBookmarksDictionary:bookmarks
                                         toDirectory:mNewBookmarksFolder];
  return self;
}

- (void) sheetDidDismiss:(NSWindow *)sheet
          returnCode:(int)returnCode
         contextInfo:(void *)contextInfo
{
  [self close];
  [self autorelease];
}

// Setup the nib elements.
- (void)windowDidLoad
{
  [mImportProgress setUsesThreadedAnimation:TRUE];
  [mImportProgress startAnimation:self];
}

#pragma mark  Import Procedure

- (void) updateProgress
{
  if (mImportProgress) {
    if ([mImportProgress isIndeterminate]) {
      [mImportProgress setIndeterminate:NO];
      [mImportProgress setMaxValue:(double)mBookmarksCount * 3];
    }
    [mImportProgress setDoubleValue:(double)
      (mBookmarksCount - [mFilePathDictionary count] + mPartialLoadCount * 2)];
  } else NSLog(@"No import progress indicator pointer");
}

- (void)docImagePartiallyLoaded:(NSNotification *)notification
{
  mPartialLoadCount++;
  [self updateProgress];
}

// Called whenever a document image finishes loading.
- (void)docImageLoaded:(NSNotification *)notification
{
  MyDocument *doc = [notification object];
  NSNumber *key = [NSNumber numberWithInt:(int)doc];
  NSString *filePath = [mFilePathDictionary objectForKey:key];

  if (!filePath) {
    NSLog([NSString stringWithFormat:@"Error, document not in dictionary: %@", doc]);
    return;
  }
  [doc writeWithBackupToFile:filePath
                      ofType:@"Web Location File"
               saveOperation:NSSaveAsOperation];
  [mFilePathDictionary removeObjectForKey:key];

  [doc close];
  [doc release];

  [self updateProgress];
  if (![mFilePathDictionary count])
  {
    NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
    [[NSWorkspace sharedWorkspace] openFile:mNewBookmarksFolder];

    [mFilePathDictionary release];
    mFilePathDictionary = nil;
    [noteCenter removeObserver:self];
    [self close];
    [self autorelease];
  }
}

// Recursively process a property list dictionary containing bookmarks. Returns the number of bookmarks processed so that the calling function may wait for site icons to load.
- (int)importBookmarksDictionary:(NSDictionary *)dict toDirectory:(NSString *)currentDir
{
  NSString *type = [dict objectForKey:@"WebBookmarkType"];
  NSFileManager *fileManager = [NSFileManager defaultManager];

  if ([type isEqualToString:@"WebBookmarkTypeList"]) {
    NSString *title = [dict objectForKey:@"Title"];
    NSArray *children;
    NSEnumerator *childEnum;
    NSDictionary *child;
    int countInChildren = 0;

    
// only the root folder does not have a title, and its folder is created in the calling function.
    if (title) {
      currentDir = [currentDir stringByAppendingPathComponent:title];
      [fileManager createDirectoryAtPath:currentDir attributes:nil];
    }
    children = [dict objectForKey:@"Children"];
    if (!children)
      return 0;
    childEnum = [children objectEnumerator];
    while (child = [childEnum nextObject])
      countInChildren += [self importBookmarksDictionary:child toDirectory:currentDir];
    return countInChildren;
  }
  else if ([type isEqualToString:@"WebBookmarkTypeLeaf"])
  {
    NSDictionary *uriDict = [dict objectForKey:@"URIDictionary"];
    NSString *filePath = [[currentDir stringByAppendingPathComponent:
      [uriDict objectForKey:@"title"]] stringByAppendingPathExtension:@"webloc"];
    MyDocument *doc = [[MyDocument alloc] init];
    NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];

    [doc setUrl:[dict objectForKey:@"URLString"]];
    if (![doc validate]) {
      NSLog([NSString stringWithFormat:@"Imported URL fails validation: %@",
        [dict objectForKey:@"URLString"]]);
      [doc close];
      [doc release];
      return 0;
    }

    [mFilePathDictionary setObject:filePath forKey:[NSNumber numberWithInt:(int)doc]];

    [noteCenter addObserver:self selector:@selector(docImageLoaded:)
                       name:@"SiteImageChanged" object:doc];
    [noteCenter addObserver:self selector:@selector(docImagePartiallyLoaded:)
                       name:@"PartialLoad" object:doc];
    [doc refreshSiteImage];
    return 1;
  }
  return 0;
}

#pragma mark  IBActions

// Target for the stop button, called on the instance acting as that window's controller.
-(IBAction)abortImport:(id)sender
{
  NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
  NSEnumerator *docPointers;
  NSDocument *doc;

  [noteCenter removeObserver:self];
  
// Display as much as has finished already.
  [[NSWorkspace sharedWorkspace] openFile:mNewBookmarksFolder];

  
// Close and release the remaining documents.
  docPointers = [[mFilePathDictionary allKeys] objectEnumerator];
  while (doc = (NSDocument *) [[docPointers nextObject] intValue])
  {
    [doc close];
    [doc release];
  }

  [mFilePathDictionary removeAllObjects];
  [mFilePathDictionary release];
  mFilePathDictionary = nil;
  [self close];
  [self autorelease];
}

// Target for the menu item. This instantiates a separate global controller instance.
-(IBAction)importSafariBookmarks:(id)sender
{
  [[ImportController alloc] initSafariImportWindow];
}

#pragma mark  Deallocation

- (void) dealloc
{
  if (mFilePathDictionary) [mFilePathDictionary release];
  if (mNewBookmarksFolder) [mNewBookmarksFolder release];
  [super dealloc];
}
@end