/*
SRDownloadHistory.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 "SRDownloadCenter.h"
#import "SRDownloadHistory.h"
#import "SRDownloadHistoryItem.h"
#import "SRPreferencesController.h"

// File names
NSString*   SRDownloadsFileName = @"Shiira/Downloads.plist";

// Notification names
NSString*    SRDownloadHistoryItemsAddedNotification = @"SRDownloadHistoryItemsAddedNotification";
NSString*    SRDownloadHistoryItemsRemovedNotification = @"SRDownloadHistoryItemsRemovedNotification";
NSString*    SRDownloadHistoryAllItemsRemovedNotification = @"SRDownloadHistoryAllItemsRemovedNotification";

@implementation SRDownloadHistory

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

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

- (id)init
{
    self = [super init];
    if (!self) {
        return self;
    }
    
    // Initialize instance variables
    _URLs = [[NSMutableSet set] retain];
    _lastVisitedDays = [[NSMutableArray array] retain];
    _downloadItems = [[NSMutableDictionary dictionary] retain];
    _historyAgeInDaysLimit = 7;
    _willSave = NO;
    
    return self;
}

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

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

- (void)_reconstructDownloadItemsDictionary
{
    // Remove URLs and last visited days
    [_URLs removeAllObjects];
    [_lastVisitedDays removeAllObjects];
    
    // Check items
    NSMutableArray* deleteDays;
    NSEnumerator*   enumerator;
    NSCalendarDate* calendarDate;
    deleteDays = [NSMutableArray array];
    enumerator = [_downloadItems keyEnumerator];
    while (calendarDate = [enumerator nextObject]) {
        NSArray*    items;
        items = [_downloadItems objectForKey:calendarDate];
        if (!items || [items count] == 0) {
            // This date will be deleted
            [deleteDays addObject:calendarDate];
        }
        else {
            // Add to last visited days
            [_lastVisitedDays addObject:calendarDate];
            
            // Enumerate items
            NSEnumerator*           itemEnumerator;
            SRDownloadHistoryItem*  item;
            itemEnumerator = [items objectEnumerator];
            while (item = [itemEnumerator nextObject]) {
                [_URLs addObject:[item URLString]];
            }
        }
    }
    
    // Sort last visited days
    [_lastVisitedDays sortUsingSelector:@selector(reverseCompare:)];
    
    // Delete calendar date
    [_downloadItems removeObjectsForKeys:deleteDays];
}

- (void)addItem:(SRDownloadHistoryItem*)item
{
    // Check private browse mode
    WebPreferences* preferences;
    BOOL            isPrivate;
    preferences = [SRPreferencesController defaultWebPreferences];
    isPrivate = [preferences privateBrowsingEnabled];
    if (isPrivate) {
        return;
    }
    
    // Get current calendar date
    NSCalendarDate* now;
    NSCalendarDate* calendarDate;
    now = [NSCalendarDate calendarDate];
    // Omit hours, minutes, and seconds
    calendarDate = [NSCalendarDate 
            dateWithYear:[now yearOfCommonEra] month:[now monthOfYear] day:[now dayOfMonth] 
            hour:0 minute:0 second:0 timeZone:[now timeZone]];
    
    // Find items from download items
    NSMutableArray* downloadItems;
    downloadItems = [_downloadItems objectForKey:calendarDate];
    if (!downloadItems) {
        // Create empty array
        downloadItems = [NSMutableArray array];
        
        // Add it with current date
        [_downloadItems setObject:downloadItems forKey:calendarDate];
        
        // Add current date in last vidied days
        [_lastVisitedDays addObject:calendarDate];
        
        // Sort last visited days
        [_lastVisitedDays sortUsingSelector:@selector(reverseCompare:)];
    }
    
    // Check existence
    if (![_URLs containsObject:[item URLString]]) {
        [_URLs addObject:[item URLString]];
    }
    
    // Add items
    [downloadItems addObject:item];
    
    // Notify addition
    [self notifyNotification:
            [NSNotification notificationWithName:SRDownloadHistoryItemsAddedNotification 
                    object:[NSArray arrayWithObject:item]]];
}

- (void)removeItems:(NSArray*)items
{
    // Remove items
    NSEnumerator*   enumerator;
    id              downloadHistoryItem;
    enumerator = [items objectEnumerator];
    while (downloadHistoryItem = [enumerator nextObject]) {
        if ([downloadHistoryItem isKindOfClass:[NSCalendarDate class]]) {
            // Remove date
            [_downloadItems removeObjectForKey:downloadHistoryItem];
        }
        if ([downloadHistoryItem isKindOfClass:[SRDownloadHistoryItem class]]) {
            // Remove history item
            NSEnumerator*   objectEnumerator;
            NSMutableArray* downloadItems;
            objectEnumerator = [_downloadItems objectEnumerator];
            while (downloadItems = [objectEnumerator nextObject]) {
                if ([downloadItems containsObject:downloadHistoryItem]) {
                    [downloadItems removeObject:downloadHistoryItem];
                    break;
                }
            }
        }
    }
    
    // Reconstruct download items dictionary
    [self _reconstructDownloadItemsDictionary];
    
    // Notify removal
    [self notifyNotification:
            [NSNotification notificationWithName:SRDownloadHistoryItemsRemovedNotification object:items]];
}

- (void)removeAllItems
{
    // Remove all download items
    [_downloadItems removeAllObjects];
}

//--------------------------------------------------------------//
#pragma mark -- Access to download items --
//--------------------------------------------------------------//

- (NSArray*)orderedLastVisitedDays
{
    return _lastVisitedDays;
}

- (NSArray*)orderedItemsLastVisitedOnDay:(NSCalendarDate*)calendarDate
{
    return [_downloadItems objectForKey:calendarDate];
}

//--------------------------------------------------------------//
#pragma mark -- History age in days limit --
//--------------------------------------------------------------//

- (int)historyAgeInDaysLimit
{
    return _historyAgeInDaysLimit;
}

- (void)setHistoryAgeInDaysLimit:(int)days
{
    _historyAgeInDaysLimit = days;
    
    // Remove old history
    NSCalendarDate* expireDate;
    int             expireDay;
    expireDate = [[NSCalendarDate calendarDate] 
            dateByAddingYears:0 months:0 days:-1 * days hours:0 minutes:0 seconds:0];
    expireDay = [expireDate dayOfCommonEra];
    
    NSEnumerator*   enumerator;
    NSCalendarDate* date;
    enumerator = [_lastVisitedDays reverseObjectEnumerator];
    while (date = [enumerator nextObject]) {
        if ([date dayOfCommonEra] < expireDay) {
            // Remove history
            [_lastVisitedDays removeObject:date];
            [_downloadItems removeObjectForKey:date];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- Notification --
//--------------------------------------------------------------//

- (void)notifyNotification:(NSNotification*)notification
{
    // Notify notification
    [[NSNotificationCenter defaultCenter] postNotification:notification];
    
    // Save download history after delay
    if (!_willSave) {
        [self performSelector:@selector(_saveDownloadHistory) withObject:nil afterDelay:5];
        
        _willSave = YES;
    }
}

- (void)_saveDownloadHistory
{
    // Save search engines
    [self saveDownloadHistory];
    
    _willSave = NO;
}

//--------------------------------------------------------------//
#pragma mark -- Persistence --
//--------------------------------------------------------------//

- (void)loadDownloadHistory
{
    // Get the paths of ~/Library/Shiira/Downloads.plist
    NSArray*	libraryPaths;
    NSString*	downloadsPath;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    downloadsPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:SRDownloadsFileName];
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:downloadsPath]) {
        return;
    }
    
    // Load from path
    NSDictionary*   dict;
    NSData*         data;
    data = [NSData dataWithContentsOfFile:downloadsPath];
    dict = [NSPropertyListSerialization propertyListFromData:data 
            mutabilityOption:0 format:NULL errorDescription:NULL];
    if (!dict) {
        return;
    }
    
    // Convert from string to download items
    NSEnumerator*   dateEnumerator;
    NSString*       dateString;
    dateEnumerator = [dict keyEnumerator];
    
    while (dateString = [dateEnumerator nextObject]) {
        // For active items
        if ([dateString isEqualTo:@"ActiveItems"]) {
            NSArray*        activeItems;
            NSEnumerator*   activeEnumerator;
            NSMutableArray* items;
            NSDictionary*   historyDict;
            activeItems = [dict objectForKey:dateString];
            activeEnumerator = [activeItems objectEnumerator];
            items = [NSMutableArray array];
            
            while (historyDict = [activeEnumerator nextObject]) {
                // Create download item
                SRDownloadHistoryItem*  historyItem;
                historyItem = [[SRDownloadHistoryItem alloc] initWithHistoryDictionary:historyDict];
                
                // Set status
                if ([historyItem resumeInfo]) {
                    [historyItem setStatus:SRDownloadStatusPaused];
                }
                else {
                    [historyItem setStatus:SRDownloadStatusCompleted];
                }
                
                [items addObject:historyItem];
            }
            
            // Add them download center
            [[SRDownloadCenter sharedInstance] setDownlaodItems:items];
        }
        
        // Create calendar date
        NSCalendarDate* date;
        date = [[NSCalendarDate alloc] initWithString:dateString];
        if (!date) {
            continue;
        }
        [date autorelease];
        
        // Add date
        if (![_lastVisitedDays containsObject:date]) {
            [_lastVisitedDays addObject:date];
        }
        
        // Get array for download items
        NSMutableArray* downloadItems;
        downloadItems = [_downloadItems objectForKey:date];
        if (!downloadItems) {
            downloadItems = [NSMutableArray array];
            [_downloadItems setObject:downloadItems forKey:date];
        }
        
        // Enumerate items
        NSArray*        array;
        NSEnumerator*   itemEnumerator;
        NSDictionary*   historyDict;
        array = [dict objectForKey:dateString];
        itemEnumerator = [array objectEnumerator];
        
        while (historyDict = [itemEnumerator nextObject]) {
            // Get URL string
            NSString*   URLString;
            URLString = [historyDict objectForKey:@"URLString"];
            if (!URLString) {
                continue;
            }
            
            // Check existence
            if ([_URLs containsObject:URLString]) {
                continue;
            }
            [_URLs addObject:URLString];
            
            // Create download item
            SRDownloadHistoryItem*  historyItem;
            historyItem = [[SRDownloadHistoryItem alloc] initWithHistoryDictionary:historyDict];
            
            // Add download item
            [downloadItems addObject:historyItem];
        }
    }
    
    // Sort last visited days
    [_lastVisitedDays sortUsingSelector:@selector(reverseCompare:)];
    
    // Notify addition
    [self notifyNotification:
            [NSNotification notificationWithName:SRDownloadHistoryItemsAddedNotification object:self]];
}

- (void)saveDownloadHistory
{
    // Get the paths of ~/Library/Shiira/Downloads.plist
    NSArray*	libraryPaths;
    NSString*	downloadsPath;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    downloadsPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:SRDownloadsFileName];
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:downloadsPath]) {
        // Create file
        SRCreateFile(downloadsPath);
    }
    
    // Pause all active downloads
    [[SRDownloadCenter sharedInstance] pauseAllActiveDownloads];
    
    SRDownloadHistoryItem*  historyItem;
    NSDictionary*           historyDict;
    
    // For download history items
    NSMutableDictionary*    dict;
    NSEnumerator*           dateEnumrator;
    NSCalendarDate*         date;
    dict = [NSMutableDictionary dictionary];
    dateEnumrator = [_downloadItems keyEnumerator];
    
    while (date = [dateEnumrator nextObject]) {
        NSMutableArray* array;
        NSArray*        downloadItems;
        NSEnumerator*   itemEnumerator;
        array = [NSMutableArray array];
        downloadItems = [_downloadItems objectForKey:date];
        itemEnumerator = [downloadItems objectEnumerator];
        
        while (historyItem = [itemEnumerator nextObject]) {
            historyDict = [historyItem historyDictionary];
            
            [array addObject:historyDict];
        }
        
        [dict setObject:array forKey:[date description]];
    }
    
    // For active history items
    NSMutableArray* activeItems;
    NSEnumerator*   activeEnumerator;
    activeItems = [NSMutableArray array];
    activeEnumerator = [[[SRDownloadCenter sharedInstance] 
            downloadItems] objectEnumerator];
    while (historyItem = [activeEnumerator nextObject]) {
        historyDict = [historyItem historyDictionary];
        
        [activeItems addObject:historyDict];
    }
    
    [dict setObject:activeItems forKey:@"ActiveItems"];
    
    // Save to URL
    NSData* data;
    data = [NSPropertyListSerialization dataFromPropertyList:dict 
            format:NSPropertyListBinaryFormat_v1_0 
            errorDescription:NULL];
    [data writeToFile:downloadsPath atomically:YES];
}

@end
