/*
SRDownloadManager.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 <Growl/Growl.h>

#import "SRDownloadItem.h"

#import "SRAppController.h"
#import "SRDownloader.h"
#import "SRDownloadController.h"
#import "SRDownloadManager.h"

#import "SRPreference.h"
#import "SRPrefDefaultKeys.h"

// Notificatioin names
NSString*    SRDownloadDidBeginNotification = @"SRDownloadDidBeginNotification";
NSString*    SRDownloadDidReceiveResponseNotification = @"SRDownloadDidReceiveResponseNotification";
NSString*    SRDownloadDidFinishNotification = @"SRDownloadDidFinishNotification";
NSString*    SRDownloadDidFailNotification = @"SRDownloadDidFailNotification";
NSString*    SRDownloadStatusChangedNotification = @"SRDownloadStatusChangedNotification";

@interface SRDownloadManager (DownloadItems)
- (void)_updatItems;
@end

@implementation SRDownloadManager

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

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

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
	// Initialize instance variables
	_downloaders = [[NSMutableArray array] retain];
	_statusChangedDownloaders = [[NSMutableArray array] retain];
	
    // Update items
    [self _updatItems];
    
    return self;
}

- (void)dealloc
{
	[_downloaders release], _downloaders = nil;
	[_statusChangedDownloaders release];
	
    [super dealloc];
}

- (void)_scheduleUpdateTimer
{
    if (_updateTimer) {
        return;
    }
    
    // Start new timer
    static const NSTimeInterval   _updatePeriod = 0.5f;
    _updateTimer = [NSTimer scheduledTimerWithTimeInterval:_updatePeriod 
            target:self 
            selector:@selector(updateTimerExpired:) 
            userInfo:nil 
            repeats:NO];
}

- (void)updateTimerExpired:(id)userInfo
{
    // For status changing active item
    if ([_statusChangedDownloaders count] > 0) {
        // Notify status changed
        NSEnumerator*   enumerator;
        SRDownloader*   donwloader;
        enumerator = [_statusChangedDownloaders objectEnumerator];
        while (donwloader = [enumerator nextObject]) {
            [[NSNotificationCenter defaultCenter] 
                    postNotificationName:SRDownloadStatusChangedNotification 
                    object:donwloader];
        }
        
        [_statusChangedDownloaders removeAllObjects];
    }
    
    _updateTimer = nil;
}

//--------------------------------------------------------------//
#pragma mark -- Refresh --
//--------------------------------------------------------------//

- (void)refreshDownloadStatus
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[SRAppController sharedInstance] managedObjectContext];
    
    // Get download items
    NSFetchRequest*     request;
    NSArray*            downloadItems;
    request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:
            [NSEntityDescription entityForName:@"DownloadItem" inManagedObjectContext:context]];
    
    downloadItems = [context executeFetchRequest:request error:NULL];
    
    // Check download status
    NSEnumerator*   enumerator;
    SRDownloadItem* downloadItem;
    enumerator = [downloadItems objectEnumerator];
    while (downloadItem = [enumerator nextObject]) {
        int status;
        status = [[downloadItem valueForKey:@"status"] intValue];
        
        if (status == SRDownloadStatusStarted || 
            status == SRDownloadStatusCommitted)
        {
            [downloadItem setValue:[NSNumber numberWithInt:SRDownloadStatusError] 
                    forKey:@"status"];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- Download items --
//--------------------------------------------------------------//

- (NSArray*)downloaders
{
	return _downloaders;
}

- (SRDownloader*)downloaderForDownlaod:(NSURLDownload*)download
{
    NSEnumerator*   enumerator;
    SRDownloader*   downloader;
    enumerator = [_downloaders objectEnumerator];
    while (downloader = [enumerator nextObject]) {
        if ([downloader download] == download) {
            return downloader;
        }
    }
    
    return nil;
}

- (SRDownloader*)downloaderForDownlaodItem:(SRDownloadItem*)downloadItem
{
    NSEnumerator*   enumerator;
    SRDownloader*   downloader;
    enumerator = [_downloaders objectEnumerator];
    while (downloader = [enumerator nextObject]) {
        if ([downloader downloadItem] == downloadItem) {
            return downloader;
        }
    }
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Download items --
//--------------------------------------------------------------//

- (void)_updatItems
{
    // Relase old items
    [_items release];
    [_activeItems release];
    
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[SRAppController sharedInstance] managedObjectContext];
    
    // Get download items
    NSFetchRequest*     request;
    NSSortDescriptor*   sortDescriptor;
    request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:
            [NSEntityDescription entityForName:@"DownloadItem" inManagedObjectContext:context]];
    
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    [request setSortDescriptors:
            [NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];
    
    _items = [[context executeFetchRequest:request error:NULL] retain];
    
    // Get active download items
    [request setPredicate:
            [NSPredicate predicateWithFormat:@"status == %d OR status == %d", 
                    SRDownloadStatusStarted, SRDownloadStatusCommitted]];
    
    _activeItems = [[context executeFetchRequest:request error:NULL] retain];
}

- (NSArray*)items
{
    return _items;
}

- (NSArray*)activeItems
{
    return _activeItems;
}

//--------------------------------------------------------------//
#pragma mark -- Download panel --
//--------------------------------------------------------------//

- (void)showDownloadPanelAutomatically
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Show download panel
    switch ([defaults integerForKey:SRDownloadNotification]) {
    case SRDownloadNotificationShowPanel: {
        // Show download panel
        SRDownloadController*   downloadController;
        downloadController = [SRDownloadController sharedInstance];
        if (![[downloadController window] isVisible]) {
            [downloadController showWindow:self];
            [downloadController setOpendInternally:YES];
        }
        
        break;
    }
    }
}

//--------------------------------------------------------------//
#pragma mark -- Download management --
//--------------------------------------------------------------//

- (NSURLDownload*)startDownloadWithRequest:(NSURLRequest*)request
{
    // Start download
    NSURLDownload*  download;
    download = [[NSURLDownload alloc] initWithRequest:request delegate:self];
    [download autorelease];
    
    return download;
}

- (void)pauseDownloadForItem:(SRDownloadItem*)downloadItem
{
    // Get download for item
    SRDownloader*           downloader;
    NSURLDownload*          download;
    SRDownloadItemStatus    status;
    downloader = [self downloaderForDownlaodItem:downloadItem];
    download = [downloader download];
    if (!download) {
        return;
    }
    status = [[downloadItem valueForKey:@"status"] intValue];
    
    // Check download status
    if (status != SRDownloadStatusStarted && 
        status != SRDownloadStatusCommitted)
    {
        return;
    }
    
    // Pause download
    [download setDeletesFileUponFailure:NO];
    [download cancel];
    
    // Make status paused
    [downloadItem setValue:[NSNumber numberWithInt:SRDownloadStatusPaused] forKey:@"status"];
    
    // Set resume info
    NSData* resumeData;
    resumeData = [download resumeData];
    [downloadItem setResumeData:resumeData];
    
    // Notify it as error
    [self download:download didFailWithError:NULL];
}

- (void)resumeDownloadForItem:(SRDownloadItem*)item
{
#if 0
    // Check download status
    SRDownloadItemStatus    status;
    status = [item status];
    if (status != SRDownloadStatusPaused) {
        return;
    }
    
    // Get resume info
    NSDictionary*   resumeInfo;
    resumeInfo = [item resumeInfo];
    if (!resumeInfo) {
        return;
    }
    
    // Resume download
    NSURLDownload*  resumeDownload;
    resumeDownload = [[NSURLDownload alloc] 
            _initWithResumeInformation:resumeInfo 
            delegate:self 
            path:[item downloadedFilePath]];
    [item setDownload:resumeDownload];
    [resumeDownload autorelease];
    
    // Make status active
    [item setStatus:SRDownloadStatusActive];
    
    // Remove resume info
    [item setResumeInfo:nil];
#endif
}

- (void)retryDownloadForItem:(SRDownloadItem*)item
{
#if 0
    // Get download for item
    NSURLDownload*          download;
    SRDownloadItemStatus    status;
    download = [item download];
    status = [item status];
    if (download) {
        // Cancel current download
        [download _setDeletesFileAfterFailure:YES];
        [download cancel];
    }
    
    // Delete previous file
    NSString*   downloadedFilePath;
    downloadedFilePath = [item downloadedFilePath];
    if (downloadedFilePath) {
        NSFileManager*  fileMgr;
        fileMgr = [NSFileManager defaultManager];
        if ([fileMgr fileExistsAtPath:downloadedFilePath] && 
            [fileMgr isDeletableFileAtPath:downloadedFilePath])
        {
            [fileMgr removeFileAtPath:downloadedFilePath handler:nil];
        }
    }
    
    // Start new download
    NSURLRequest*   request;
    NSURLDownload*  newDownload;
    request = [NSURLRequest requestWithURL:[NSURL URLWithString:[item URLString]]];
    newDownload = [[NSURLDownload alloc] 
            initWithRequest:request delegate:self];
    [newDownload autorelease];
    
    // Add download
    [item setDownload:newDownload];
    
    // Make status active, and reset length
    [item setStatus:SRDownloadStatusActive];
    [item resetLength];
#endif
}

- (void)removeDownloadForItem:(SRDownloadItem*)item
{
    // Get downloader for item
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaodItem:item];
    if (downloader) {
        // Cancel download
        NSURLDownload*  download;
        download = [downloader download];
        if (download) {
            [download _setDeletesFileAfterFailure:NO];
            [download cancel];
        }
        
        // Remove downloader
        if ([_downloaders containsObject:downloader]) {
            [_downloaders removeObject:downloader];
        }
    }
    
    // Remove download item
    [[[SRAppController sharedInstance] managedObjectContext] deleteObject:item];
}

- (void)removeAllCompletedActiveItems
{
#if 0
    // Enumerate active items
    NSEnumerator*           enumerator;
    SRDownloadItem*  historyItem;
    NSMutableArray*         removeItems;
    removeItems = [NSMutableArray array];
    enumerator = [_downloadItems objectEnumerator];
    while (historyItem = [enumerator nextObject]) {
        // Check item status
        if ([historyItem status] == SRDownloadStatusCompleted) {
            [removeItems addObject:historyItem];
        }
    }
    
    // Remove completed item
    [_downloadItems removeObjectsInArray:removeItems];
    
    // Notify removal
	[[NSNotificationCenter defaultCenter] 
			postNotificationName:SRDownloadRemovedNotification 
			object:removeItems];
#endif
}

- (void)pauseAllActiveDownloads
{
}

- (void)_showFindAlertForPath:(NSString*)path
{
    // Show alert
    NSAlert*    alert;
    alert = [[NSAlert alloc] init];
    [alert autorelease];
    [alert setAlertStyle:NSWarningAlertStyle];
    [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(UTF8STR("Shiira can’t show the file “%@” in the Finder."), UTF8STR("Shiira can’t show the file “%@” in the Finder.")), [path lastPathComponent]]];
    [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK")];
    
    int result;
    result = [alert runModal];
}

- (void)findItem:(SRDownloadItem*)item
{
#if 0
    // Get download path and its parent
    NSString*   filePath;
    NSString*   parent;
    filePath = [item downloadedFilePath];
    if (!filePath) {
        return;
    }
    parent = [filePath stringByDeletingLastPathComponent];
    
    // For download file wrapper
    if ([[parent pathExtension] isEqualToString:@"download"]) {
        filePath = parent;
    }
    
    // Check file existense
    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        // Show alert
        [self _showFindAlertForPath:filePath];
        return;
    }
    
    // Find file by Finder
    if (![[NSWorkspace sharedWorkspace] selectFile:filePath inFileViewerRootedAtPath:@""]) {
        // Show alert
        [self _showFindAlertForPath:filePath];
        return;
    }
#endif
}

//--------------------------------------------------------------//
#pragma mark -- NSURLDownload delegate --
//--------------------------------------------------------------//

- (void)downloadDidBegin:(NSURLDownload*)download
{
    // Create downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    if (!downloader) {
        // Create downloader
        downloader = [[SRDownloader alloc] init];
        [downloader setDownload:download];
        
        // Add downloader
        [_downloaders addObject:downloader];
        [downloader release];
    }
    
    // Notify to downloader
    [downloader downloadDidBegin:download];
    
    // Update items
    [self _updatItems];
    
    // Notify status changed
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRDownloadDidBeginNotification 
            object:downloader];
}

- (NSURLRequest*)download:(NSURLDownload*)download 
        willSendRequest:(NSURLRequest*)request 
        redirectResponse:(NSURLResponse*)redirectResponse
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    return [downloader download:download 
            willSendRequest:request redirectResponse:redirectResponse];
}

- (void)download:(NSURLDownload*)download 
        didReceiveResponse:(NSURLResponse*)response
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader download:download didReceiveResponse:response];
    
    // Notify status changed
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRDownloadDidReceiveResponseNotification 
            object:downloader];
}

- (void)download:(NSURLDownload*)download 
        decideDestinationWithSuggestedFilename:(NSString*)fileName
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader download:download decideDestinationWithSuggestedFilename:fileName];
}

- (void)download:(NSURLDownload*)download 
        didCreateDestination:(NSString*)path
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader download:download didCreateDestination:path];
}

- (void)download:(NSURLDownload*)download 
        didReceiveDataOfLength:(unsigned)length
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader download:download didReceiveDataOfLength:length];
    
    // Add itself to status changed downloaders
    if (![_statusChangedDownloaders containsObject:downloader]) {
        [_statusChangedDownloaders addObject:downloader];
    }
    
    // Start update timer
    [self _scheduleUpdateTimer];
}

- (void)downloadDidFinish:(NSURLDownload*)download
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader downloadDidFinish:download];
    
    // Get file name and URL
    SRDownloadItem* donwloadItem;
    NSString*       fileName;
    NSString*       URLString;
    donwloadItem = [downloader downloadItem];
    fileName = [donwloadItem valueForKey:@"fileName"];
    URLString = [donwloadItem valueForKey:@"urlString"];
    
    // Create title and description
    NSString*   title;
    NSString*   description;
    title = NSLocalizedString(@"Download is completed", nil);
    description = [NSString stringWithFormat:@"%@\n%@", fileName, URLString];
    
    // Notify to Growl
    [GrowlApplicationBridge notifyWithTitle:title 
            description:description 
            notificationName:SRGrowlDownloadCompletedNotification 
            iconData:nil 
            priority:0 
            isSticky:NO 
            clickContext:nil];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Remove download item
    WebPreferences* preferences;
    BOOL            isPrivate;
    preferences = [SRPreference webPreferences];
    isPrivate = [preferences privateBrowsingEnabled];
    if ([defaults integerForKey:SRDownloadItemRemove] == SRDownloadItemRemoveAutomatically || isPrivate) {
        [self removeDownloadForItem:[downloader downloadItem]];
    }
    
    // Update items
    [self _updatItems];
    
    // Remove itself from status changed downloaders
    if ([_statusChangedDownloaders containsObject:downloader]) {
        [_statusChangedDownloaders removeObject:downloader];
    }
    
    // Notify status changed
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRDownloadDidFinishNotification 
            object:downloader];
}

- (void)download:(NSURLDownload*)download 
        didFailWithError:(NSError*)error
{
    // Notify to downloader
    SRDownloader*   downloader;
    downloader = [self downloaderForDownlaod:download];
    [downloader download:download didFailWithError:error];
    
    // Update items
    [self _updatItems];
    
    // Remove itself from status changed downloaders
    if ([_statusChangedDownloaders containsObject:downloader]) {
        [_statusChangedDownloaders removeObject:downloader];
    }
    
    // Notify status changed
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRDownloadDidFailNotification 
            object:downloader];
}

- (BOOL)download:(NSURLDownload*)download 
        shouldDecodeSourceDataOfMIMEType:(NSString*)encodingType
{
    // Decode automatically
    return YES;
}

@end
