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

#import "SRDefaultsKey.h"

#import "SRAppDelegate.h"

#import "SRDownloadsController.h"
#import "SRDownloadCenter.h"
#import "SRDownloadHistory.h"
#import "SRDownloadHistoryItem.h"
#import "SRPreferencesController.h"

#import "SRUtil.h"
#import "FoundationEx.h"

NSString*    SRDownloadAddedNotification = @"SRDownloadAddedNotification";
NSString*    SRDownloadRemovedNotification = @"SRDownloadRemovedNotification";
NSString*    SRDownloadStatusChangedNotification = @"SRDownloadStatusChangedNotification";

@implementation SRDownloadCenter

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

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

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

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

- (void) _scheduleUpdateTimer
{
    static const NSTimeInterval   _updatePeriod = 0.5;
    [_updateTimer invalidate];
    
    // Start the new timer
    // The timer is retained by the runloop, no need to retain it here.
    _updateTimer = [NSTimer scheduledTimerWithTimeInterval:_updatePeriod 
        target:self 
        selector:@selector(updateTimerExpired:) 
        userInfo:nil 
        repeats:YES];
}

- (void)updateTimerExpired:(id)userInfo
{
    // For status changing active item
    if ([_statusChangedItems count] > 0) {
        // Notify status changed
        [[NSNotificationCenter defaultCenter] 
                postNotificationName:SRDownloadStatusChangedNotification 
                object:_statusChangedItems];
        
        [_statusChangedItems removeAllObjects];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Notifications --
//--------------------------------------------------------------//

- (void)_notifyAddition:(SRDownloadHistoryItem*)historyItem
{
	// Notify addition
	[[NSNotificationCenter defaultCenter] 
			postNotificationName:SRDownloadAddedNotification 
			object:[NSArray arrayWithObject:historyItem]];
}

- (void)_notifyRemoval:(SRDownloadHistoryItem*)historyItem
{
	// Notify removal
	[[NSNotificationCenter defaultCenter] 
			postNotificationName:SRDownloadRemovedNotification 
			object:[NSArray arrayWithObject:historyItem]];
}

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

- (NSArray*)downloadItems
{
	return _downloadItems;
}

- (void)setDownlaodItems:(NSArray*)items
{
    // Remove old items
    NSArray*    removedItems;
    removedItems = [NSArray arrayWithArray:_downloadItems];
	[_downloadItems removeAllObjects];
    
    // Notify removal
	[[NSNotificationCenter defaultCenter] 
			postNotificationName:SRDownloadRemovedNotification 
			object:removedItems];
    
    // Add new items
	[_downloadItems addObjectsFromArray:items];
	
    // Notify addition
	[[NSNotificationCenter defaultCenter] 
			postNotificationName:SRDownloadAddedNotification 
			object:[NSArray arrayWithArray:_downloadItems]];
}

- (SRDownloadHistoryItem*)_itemForDownload:(NSURLDownload*)download
{
    NSEnumerator*           enumerator;
    SRDownloadHistoryItem*  historyItem;
    enumerator = [_downloadItems objectEnumerator];
    while (historyItem = [enumerator nextObject]) {
        if ([historyItem download] == download) {
            return historyItem;
        }
    }
    
    return nil;
}

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

- (void)pauseDownloadForItem:(SRDownloadHistoryItem*)item
{
    // Get download for item
    NSURLDownload*          download;
    SRDownloadItemStatus    status;
    download = [item download];
    if (!download) {
        return;
    }
    status = [item status];
    
    // Check download status
    if (status != SRDownloadStatusActive) {
        return;
    }
    
    // Pause download
    [download _setDeletesFileAfterFailure:NO];
    [download cancel];
    
    // Make status paused
    [item setStatus:SRDownloadStatusPaused];
    
    // Set resume info
    id  resumeInfo;
    resumeInfo = [download _resumeInformation];
    [item setResumeInfo:resumeInfo];
}

- (void)resumeDownloadForItem:(SRDownloadHistoryItem*)item
{
    // Check download status
    SRDownloadItemStatus    status;
    status = [item status];
    if (status != SRDownloadStatusPaused && 
        status != SRDownloadStatusError)
    {
        return;
    }
    
    // Get resume info
    NSDictionary*   resumeInfo;
    resumeInfo = [item resumeInfo];
    if (resumeInfo) {
        // 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];
    }
    else {
        // Retry download
        [self retryDownloadForItem:item];
    }
}

- (void)retryDownloadForItem:(SRDownloadHistoryItem*)item
{
    // 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];
        
        // Get parent directory path
        NSString*   directoryPath;
        directoryPath = [downloadedFilePath stringByDeletingLastPathComponent];
        
        BOOL    isDir;
        if ([fileMgr fileExistsAtPath:directoryPath isDirectory:&isDir] && 
            isDir && 
            [fileMgr isDeletableFileAtPath:directoryPath])
        {
            [fileMgr removeFileAtPath:directoryPath handler:nil];
        }
        else 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];
}

- (void)removeDownloadForItem:(SRDownloadHistoryItem*)item
{
    // Get download for item
    NSURLDownload*          download;
    SRDownloadItemStatus    status;
    download = [item download];
    status = [item status];
    if (download) {
        // Cancel download
        [download _setDeletesFileAfterFailure:NO];
        [download cancel];
    }
    
    // Remove download item
    [_downloadItems removeObject:item];
    
    // Notify removal
    [self _notifyRemoval:item];
}

- (void)removeAllCompletedActiveItems
{
    // Enumerate active items
    NSEnumerator*           enumerator;
    SRDownloadHistoryItem*  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];
}

- (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:(SRDownloadHistoryItem*)item
{
    // 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;
    }
}

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

- (void)downloadDidBegin:(NSURLDownload*)download
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    switch ([defaults integerForKey:SRDownloadNotification]) {
    case SRDownloadNotificationSideBar: {
        // Show downloads side bar
        [NSApp sendAction:@selector(showDownloadsAction:) to:nil from:self];
        break;
    }
    case SRDownloadNotificationInfoCenter: {
        // Show download panel
        if (![[[[NSApp delegate] downloadsController] window] isVisible]) {
            [NSApp sendAction:@selector(showDownloadsPanelAction:) to:nil from:self];
        }
        break;
    }
    }
    
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (!historyItem) {
        // Create download history item
        historyItem = [[SRDownloadHistoryItem alloc] init];
        [historyItem autorelease];
        // Set download
        [historyItem setDownload:download];
        // Set request
        [historyItem setRequest:[download request]];
        // Set download time
        [historyItem setStartTime:[NSDate date]];
        // Set active status
        [historyItem setStatus:SRDownloadStatusActive];
        
        // Add history item
        [_downloadItems addObject:historyItem];
        
		// Notify addition
		[self _notifyAddition:historyItem];
		
		// Add history item to download history
        [[SRDownloadHistory sharedInstance] addItem:historyItem];
    }
    
    // Starts timer
    [self _scheduleUpdateTimer];
}

//- download:willSendRequest:redirectResponse:

- (void)download:(NSURLDownload*)download 
        didReceiveResponse:(NSURLResponse*)response
{
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (historyItem) {
        // Set response
        [historyItem setResponse:response];
        
        // For notification
        if (![_statusChangedItems containsObject:historyItem]) {
            [_statusChangedItems addObject:historyItem];
        }
    }
}

- (void)download:(NSURLDownload*)download 
        decideDestinationWithSuggestedFilename:(NSString*)fileName
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    NSFileManager*  fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Get download file path
    NSString*       downloadPath;
    downloadPath = [defaults stringForKey:SRGeneralDownloadPath];
    if (!downloadPath) {
        // Default path is desktop
        downloadPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"];
    }
    
 	// Make date sub dir
	if([defaults boolForKey:SRDownloadInDateSubDirEnabled]){
		NSString*   dateStr;
		dateStr=[[NSDate date]descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil];
		if(dateStr){
			BOOL	isDirectory;
			NSString* tidyDirPath=[downloadPath stringByAppendingPathComponent:dateStr];
			if(![fileMgr fileExistsAtPath:tidyDirPath isDirectory:&isDirectory]){
				// Create folder
				if([fileMgr createDirectoryAtPath:tidyDirPath attributes:nil])
					isDirectory=YES;
			}
			if(isDirectory)
				downloadPath=tidyDirPath;
		}
	}
    
    // Create file wrapper path
    NSString*       fileWrapperPath;
    fileWrapperPath = [downloadPath stringByAppendingPathComponent:fileName];
    fileWrapperPath = [fileWrapperPath stringByAppendingPathExtension:@"download"];
    fileWrapperPath = [fileMgr makeUniqueFilePath:fileWrapperPath];
    if (![fileMgr createDirectoryAtPath:fileWrapperPath attributes:nil]) {
        // Error
    }
    
    // Create file wrapper
    NSFileWrapper*  fileWrapper;
    fileWrapper = [[NSFileWrapper alloc] initWithPath:fileWrapperPath];
    if (![fileWrapper writeToFile:fileWrapperPath atomically:YES updateFilenames:NO]) {
        // Error
    }
    
    // Set destination file path
    [download setDestination:[fileWrapperPath stringByAppendingPathComponent:fileName] 
            allowOverwrite:NO];
}

- (void)download:(NSURLDownload*)download 
        didCreateDestination:(NSString*)path
{
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (historyItem) {
        // Set downloaded file path
        [historyItem setDownloadedFilePath:path];
        
        // For notification
        if (![_statusChangedItems containsObject:historyItem]) {
            [_statusChangedItems addObject:historyItem];
        }
    }
}

- (void)download:(NSURLDownload*)download 
        didReceiveDataOfLength:(unsigned)length
{
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (historyItem) {
        // Increase data length
        [historyItem increaseDownloadedLength:(long long)length];
        
        // For notification
        if (![_statusChangedItems containsObject:historyItem]) {
            [_statusChangedItems addObject:historyItem];
        }
    }
}

- (void)downloadDidFinish:(NSURLDownload*)download
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    NSFileManager*  fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (historyItem) {
        // Make status completed
        [historyItem setStatus:SRDownloadStatusCompleted];
        
        // Set data length
        long long   expectedLength, downloadedLength;
        expectedLength = [historyItem expectedLength];
        downloadedLength = [historyItem downloadedLength];
        if (downloadedLength < expectedLength) {
            [historyItem setDownloadedLength:expectedLength];
        }
        
        // Get downloaded file from file wrapper
        NSString*   downloadedFilePath;
        NSString*   parentDirectory;
        downloadedFilePath = [historyItem downloadedFilePath];
        parentDirectory = [downloadedFilePath stringByDeletingLastPathComponent];
        if ([[parentDirectory pathExtension] isEqualToString:@"download"]) {
            NSString*   newFilePath;
            newFilePath = [[parentDirectory stringByDeletingLastPathComponent] 
                    stringByAppendingPathComponent:[downloadedFilePath lastPathComponent]];
            newFilePath = [fileMgr makeUniqueFilePath:newFilePath];
            
            // Move downloaded file
            if (![fileMgr movePath:downloadedFilePath toPath:newFilePath handler:nil]) {
                // Error
                NSLog(@"Failed to move downloaded file");
            }
            else {
                // Change downloaded file path
                [historyItem setDownloadedFilePath:newFilePath];
                
                // Remove .download file wrapper
                if (![fileMgr removeFileAtPath:parentDirectory handler:nil]) {
                    // Error
                    NSLog(@"Failed to remove file download wrapper");
                }
            }
        }
        
// For Growl support
#ifdef SR_SUPPORT_GROWL
        // Get file name and URL
        NSString*   fileName;
        NSString*   URLString;
        fileName = [historyItem fileName];
        URLString = [historyItem URLString];
        
        // Create title and description
        NSString*   title;
        NSString*   description;
        title = NSLocalizedString(@"Download is completed", nil);
        description = [NSString stringWithFormat:@"%@\n%@", fileName, URLString];
        
        // Notify Growl
        [GrowlApplicationBridge notifyWithTitle:title 
                description:description 
                notificationName:SRGrowlDownloadCompletedNotification 
                iconData:nil 
                priority:0 
                isSticky:NO 
                clickContext:nil];
#endif // SR_SUPPORT_GROWL
        
        // Remove it from active items
        WebPreferences* preferences;
        BOOL            isPrivate;
        preferences = [SRPreferencesController defaultWebPreferences];
        isPrivate = [preferences privateBrowsingEnabled];
		if ([defaults integerForKey:SRDownloadItemRemove] == SRDownloadItemRemoveAutomatically || isPrivate) {
			[self removeDownloadForItem:historyItem];
        }
		else {
			// For notification
			if (![_statusChangedItems containsObject:historyItem]) {
				[_statusChangedItems addObject:historyItem];
			}
		}
    }
}

- (void)download:(NSURLDownload*)download 
        didFailWithError:(NSError*)error
{
    // Get download history item
    SRDownloadHistoryItem*  historyItem;
    historyItem = [self _itemForDownload:download];
    
    if (historyItem) {
        // Pause download
        [download _setDeletesFileAfterFailure:NO];
        [download cancel];
        
        // Set resume info
        id  resumeInfo;
        resumeInfo = [download _resumeInformation];
        [historyItem setResumeInfo:resumeInfo];
        
        // Make status error
        [historyItem setStatus:SRDownloadStatusError];
        [historyItem setErrorDescription:[error localizedDescription]];
        
        // For notification
        if (![_statusChangedItems containsObject:historyItem]) {
            [_statusChangedItems addObject:historyItem];
        }
    }
}

//- download:didReceiveAuthenticationChallenge:
//- download:didCancelAuthenticationChallenge:

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

@end
