/*
RSSManager.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 "SRConstants.h"
#import "SRAppController.h"

#import "SRPrefDefaultKeys.h"

#import "RSSFeedParser.h"
#import "RSSManager.h"
#import "RSSPersistentStack.h"

#import "RSSDefaultKeys.h"
#import "RSSPanelController.h"

#import "RSSSyndication.h"

@implementation RSSManager

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

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

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Register observer
    if (![[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"jp.hmdt.rsssyndication"]) {
        NSDistributedNotificationCenter*    distributedCenter;
        distributedCenter = [NSDistributedNotificationCenter defaultCenter];
        [distributedCenter addObserver:self selector:@selector(feedDidEndRefresh:) 
                name:RSSDFeedDidEndRefresh object:nil];
        
        NSNotificationCenter*   center;
        center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(rssArticlesRead:) 
                name:RSSArticlesRead object:nil];
        [center addObserver:self selector:@selector(managedObjectContextDidSave:) 
                name:NSManagedObjectContextDidSaveNotification object:nil];
        
        NSUserDefaults* defaults;
        defaults = [NSUserDefaults standardUserDefaults];
        [defaults addObserver:self forKeyPath:RSSOtherBrowserUsageFlags 
                options:NSKeyValueObservingOptionNew context:NULL];
    }
    return self;
}

- (void)dealloc
{
    [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObserver:self forKeyPath:RSSOtherBrowserUsageFlags];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Feed management --
//--------------------------------------------------------------//

- (void)_addFeedBookmark:(SRBookmark*)bookmark
{
    // Get URL
    NSString*   urlString;
    urlString = [bookmark urlString];
    if (!urlString) {
        return;
    }
    
    // Swap feed scheme
    urlString = HMSwapSchemeStringFeedToHttp(urlString);
    
    // Get RSS managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Find feed with bookmark's URL
    NSFetchRequest* request;
    NSArray*        feeds;
    request = [[NSFetchRequest alloc] init];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSFeed" inManagedObjectContext:context]];
    [request setPredicate:
            [NSPredicate predicateWithFormat:@"feedURL == %@", urlString]];
    
    feeds = [context executeFetchRequest:request error:NULL];
    [request release];
    if ([feeds count] > 0) {
        return;
    }
    
    // Create feed
    id  feed;
    feed = [NSEntityDescription insertNewObjectForEntityForName:@"RSSFeed" 
            inManagedObjectContext:context];
    if (!feed) {
        return;
    }
    
    // Set feed URL
    [feed setValue:urlString forKey:@"feedURL"];
    
    // Set title
    NSString*   title;
    title = [bookmark title];
    if (!title) {
        title = urlString;
    }
    [feed setValue:title forKey:@"title"];
}

- (void)_removeOldFeedsWithBookmarks:(NSArray*)usingBookmarks
{
    // Get URL strings
    NSMutableSet*   usingURLStrings;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    usingURLStrings = [NSMutableSet set];
    enumerator = [usingBookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        NSString*   urlString;
        urlString = [bookmark urlString];
        if (!urlString) {
            continue;
        }
        urlString = HMSwapSchemeStringFeedToHttp(urlString);
        
        if (![usingURLStrings containsObject:urlString]) {
            [usingURLStrings addObject:urlString];
        }
    }
    
    // Get RSS managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Get feeds from managed object context
    NSFetchRequest* request;
    NSArray*        results;
    request = [[NSFetchRequest alloc] init];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSFeed" inManagedObjectContext:context]];
    
    results = [context executeFetchRequest:request error:NULL];
    [request release];
    
    // Collect not used feeds
    NSMutableArray* notUsedFeeds;
    id              feed;
    notUsedFeeds = [NSMutableArray array];
    enumerator = [results objectEnumerator];
    while (feed = [enumerator nextObject]) {
        NSString*   urlString;
        urlString = [feed valueForKey:@"feedURL"];
        urlString = HMSwapSchemeStringFeedToHttp(urlString);
        
        if (![usingURLStrings containsObject:urlString]) {
            [notUsedFeeds addObject:feed];
        }
    }
    
    // Remove not used feeds
    enumerator = [notUsedFeeds objectEnumerator];
    while (feed = [enumerator nextObject]) {
        [context deleteObject:feed];
    }
}

- (void)addFeedsInBookmarks
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[SRAppController sharedInstance] managedObjectContext];
    
    // Get Shiira RSS bookmarks
    NSMutableArray* bookmarks;
    NSFetchRequest* request;
    NSArray*        results;
    bookmarks = [NSMutableArray array];
    request = [[NSFetchRequest alloc] init];
    [request setEntity:
            [NSEntityDescription entityForName:@"Bookmark" inManagedObjectContext:context]];
    [request setPredicate:
            [NSPredicate predicateWithFormat:@"type == %@ AND browser == %@", @"RSS", SRBrowserShiira]];
    
    results = [context executeFetchRequest:request error:NULL];
    [request release];
    
    [bookmarks addObjectsFromArray:results];
    
    // Get other browser RSS bookmarks
    NSDictionary*   browserInfo;
    NSEnumerator*   enumerator;
    NSString*       browser;
    browserInfo = [[NSUserDefaults standardUserDefaults] objectForKey:RSSOtherBrowserUsageFlags];
    enumerator = [browserInfo keyEnumerator];
    while (browser = [enumerator nextObject]) {
        NSNumber*   on;
        on = [browserInfo objectForKey:browser];
        if (![on boolValue]) {
            continue;
        }
        
        request = [[NSFetchRequest alloc] init];
        [request setEntity:
                [NSEntityDescription entityForName:@"Bookmark" inManagedObjectContext:context]];
        [request setPredicate:
                [NSPredicate predicateWithFormat:@"type == %@ AND browser == %@", @"RSS", browser]];
        
        results = [context executeFetchRequest:request error:NULL];
        [request release];
        
        [bookmarks addObjectsFromArray:results];
    }
    
    // Remove not used feeds
    [self _removeOldFeedsWithBookmarks:bookmarks];
    
    // Add feeds
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        [self _addFeedBookmark:bookmark];
    }
    
    // Save RSS persistent stack
    [[RSSPersistentStack sharedInstance] save];
}

- (int)numberOfUnreadItems
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Get unread items
    NSFetchRequest* request;
    NSArray*        items;
    request = [[NSFetchRequest alloc] init];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSItem" inManagedObjectContext:context]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"isRead == NO"]];
    
    items = [context executeFetchRequest:request error:NULL];
    [request release];
    
    return [items count];
}

- (void)removeAllArticles
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Get items
    NSFetchRequest* request;
    NSArray*        items;
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSItem" inManagedObjectContext:context]];
    
    items = [context executeFetchRequest:request error:NULL];
    
    // Remove all articles
    NSEnumerator*   enumerator;
    id              item;
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        [context deleteObject:item];
    }
    
    // Get feeds
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSFeed" inManagedObjectContext:context]];
    
    items = [context executeFetchRequest:request error:NULL];
    
    // Set number of unread articles
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        [item setValue:[NSNumber numberWithInt:0] forKey:@"numberOfUnreadArticles"];
    }
    
    [[RSSPersistentStack sharedInstance] save];
}

- (void)makeItemPreviewed:(id)item
{
    if (![[item valueForKey:@"isRead"] boolValue]) {
        [item setValue:[NSNumber numberWithBool:YES] forKey:@"isRead"];
        
        [RSSFeedParser updateNumberOfUnreadArticles:[item valueForKey:@"channel"]];
        
        // Save managed object context
        [[RSSPersistentStack sharedInstance] save];
        
        // Notify it
        [[NSNotificationCenter defaultCenter] 
                postNotificationName:RSSArticlesRead object:self];
    }
}

- (void)_makeItemsPreviewedWithFeed:(id)feed
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Make items previewed
    NSEnumerator*   enumerator;
    id              item;
    enumerator = [[feed valueForKey:@"items"] objectEnumerator];
    while (item = [enumerator nextObject]) {
        if (![[item valueForKey:@"isRead"] boolValue]) {
            [item setValue:[NSNumber numberWithBool:YES] forKey:@"isRead"];
        }
    }
    
    [feed setValue:[NSNumber numberWithInt:0] forKey:@"numberOfUnreadArticles"];
}

- (void)makeItemsPreviewedWithFeed:(id)feed
{
    // Make items previewed
    [self _makeItemsPreviewedWithFeed:feed];
    
    // Save managed object context
    [[RSSPersistentStack sharedInstance] save];
    
    // Notify it
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:RSSArticlesRead object:self];
}

- (void)makeAllArticlesPreviewed
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [[RSSPersistentStack sharedInstance] managedObjectContext];
    
    // Get feeds
    NSFetchRequest* request;
    NSArray*        feeds;
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    [request setEntity:
            [NSEntityDescription entityForName:@"RSSFeed" inManagedObjectContext:context]];
    
    feeds = [context executeFetchRequest:request error:NULL];
    
    // Make items previewed
    NSEnumerator*   enumerator;
    id              feed;
    enumerator = [feeds objectEnumerator];
    while (feed = [enumerator nextObject]) {
        [self _makeItemsPreviewedWithFeed:feed];
    }
    
    // Save managed object context
    [[RSSPersistentStack sharedInstance] save];
    
    // Notify it
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:RSSArticlesRead object:self];
}

- (void)refreshFeeds:(NSArray*)feeds
{
    // Launch RSS syndication
    [self launchRSSSyndicationWithFeeds:feeds];
}

- (void)refreshAllFeeds
{
    // Launch RSS syndication
    [self launchRSSSyndicationWithFeeds:nil];
}

//--------------------------------------------------------------//
#pragma mark -- Launch RSS syndication --
//--------------------------------------------------------------//

- (BOOL)isRSSSyndicationWorking
{
    // Create task
    NSTask* task;
    NSPipe* pipe;
    task = [[NSTask alloc] init];
    pipe = [NSPipe pipe];
    [task setLaunchPath:@"/bin/ps"];
    [task setArguments:[NSArray arrayWithObject:@"-cx"]];
    [task setStandardOutput:pipe];
    [task launch];
    [task waitUntilExit];
    
    // Read error
    NSData*     data;
    NSString*   string;
    data = [[pipe fileHandleForReading] availableData];
    string = [NSString stringWithCString:[data bytes] length:[data length]];
    
    // Check command name
    if ([string rangeOfString:@"RSSSyndication"].location != NSNotFound) {
        return YES;
    }
    
    return NO;
}

- (void)launchRSSSyndicationWithFeeds:(NSArray*)feeds
{
    // Check working
    if ([self isRSSSyndicationWorking]) {
        return;
    }
    
    // Get path
    NSString*   syndicationPath;
    syndicationPath = [[NSBundle mainBundle] bundlePath];
    syndicationPath = [syndicationPath stringByAppendingPathComponent:
            @"Contents/Support Applications/RSSSyndication.app/Contents/MacOS/RSSSyndication"];
    
    // Launch RSS syndication
    if (_RSSTask) {
        if ([_RSSTask isRunning]) {
            @try {
                [_RSSTask terminate];
            }
            @catch (NSException* ex) {
                NSLog(@"Failed to terminate RSS task, %@", [ex reason]);
            }
        }
        [_RSSTask release];
    }
    
    @try {
        if (!feeds) {
            feeds = [NSArray array];
        }
        
        _RSSTask = [[NSTask launchedTaskWithLaunchPath:syndicationPath 
                arguments:feeds] retain];
        
        // Set priority
        int pid;
        pid = [_RSSTask processIdentifier];
        if (pid > 0) {
            setpriority(PRIO_PROCESS, pid, 10);
        }
    }
    @catch (NSException* ex) {
        NSLog(@"Failed to launch RSS task, %@", [ex reason]);
    }
}

//--------------------------------------------------------------//
#pragma mark -- Application support --
//--------------------------------------------------------------//

- (void)updateRSSBadge
{
    // Check user defaults
    if (![[NSUserDefaults standardUserDefaults] boolForKey:SRRSSShowArticlesNumber]) {
        // Reset app icon
        [NSApp setApplicationIconImage:[NSImage imageNamed:@"NSApplicationIcon"]];
        
        return;
    }
    
    // Get not read articles number
    int count;
    count = [self numberOfUnreadItems];
    
    // Get application icon
    NSImage*    icon;
    icon = [NSImage imageNamed:@"NSApplicationIcon"];
    
    // When no articles
    if (count == 0) {
        [NSApp setApplicationIconImage:icon];
        return;
    }
    
    // Composite counts on the application icon
    NSImage*    image;
    NSImage*    badge;
    NSRect      srcRect, destRect;
    image = [[NSImage alloc] initWithSize:[icon size]];
    if (count < 100) {
        badge = [NSImage imageNamed:@"rssBadge1and2"];
    }
    else if (count < 1000) {
        badge = [NSImage imageNamed:@"rssBadge3"];
    }
    else if (count < 10000) {
        badge = [NSImage imageNamed:@"rssBadge4"];
    }
    else {
        badge = [NSImage imageNamed:@"rssBadge5"];
    }
    
    [image lockFocus];
    
    // Draw icon
    srcRect.origin = NSZeroPoint;
    srcRect.size = [icon size];
    [icon drawAtPoint:NSZeroPoint fromRect:srcRect operation:NSCompositeSourceOver fraction:1.0f];
    
    // Draw badge
    srcRect.origin = NSZeroPoint;
    srcRect.size = [badge size];
    destRect.origin.x = 4.0f;
    destRect.origin.y = 4.0f;
    destRect.size = srcRect.size;
    [badge drawInRect:destRect fromRect:srcRect operation:NSCompositeSourceOver fraction:1.0f];
    
    // Draw count
    static NSDictionary*    _attr = nil;
    static float            _ascender = 0.0f;
    if (!_attr) {
        NSFont* font;
        font = [NSFont boldSystemFontOfSize:22.0f];
        _attr = [[NSDictionary dictionaryWithObjectsAndKeys:
                [NSColor whiteColor], NSForegroundColorAttributeName, 
                font, NSFontAttributeName, 
                nil] retain];
        _ascender = [font ascender];
    }
    
    NSAttributedString* attrStr;
    NSSize              strSize;
    attrStr = [[NSAttributedString alloc] 
            initWithString:[NSString stringWithFormat:@"%d", count] attributes:_attr];
    strSize = [attrStr size];
    [attrStr drawInRect:NSMakeRect(
            destRect.origin.x + (destRect.size.width - strSize.width) / 2.0f, 
            destRect.origin.y + (destRect.size.height - _ascender) / 2.0f - 2.0f, 
            strSize.width, 
            strSize.height)];
    [attrStr release];
    
    [image unlockFocus];
    
    [NSApp setApplicationIconImage:image];
    [image release];
}

- (void)growl
{    
// For Growl support
#ifdef SR_SUPPORT_GROWL
    if ([[notification name] isEqualToString:SRRSSDidEndRefresh]) {
        int newComingArticles;
        newComingArticles = [[SRRSSManager sharedInstance] numberOfNewComingArticles];
        if (newComingArticles > 0) {
            // Notify Growl
            NSString*   description;
            if (newComingArticles == 1) {
                description = NSLocalizedString(@"There is an new article", nil);
            }
            else {
                description = [NSString stringWithFormat:
                        NSLocalizedString(@"There are %d new articles", nil), newComingArticles];
            }
            
            [GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"New RSS articles come", nil) 
                    description:description 
                    notificationName:SRGrowlRSSComesNotification 
                    iconData:nil 
                    priority:0 
                    isSticky:NO 
                    clickContext:nil];
        }
    }
#endif // SR_SUPPORT_GROWL
}

//--------------------------------------------------------------//
#pragma mark -- RSSSyndication notification --
//--------------------------------------------------------------//

- (void)feedDidEndRefresh:(NSNotification*)notification
{
    // Reset managed object context
    [[[RSSPersistentStack sharedInstance] managedObjectContext] reset];
    
    // Update RSS badge
    [self updateRSSBadge];
    
    // Notify it
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:RSSPersistentStackRefreshed 
            object:[RSSPersistentStack sharedInstance]];
}

//--------------------------------------------------------------//
#pragma mark -- RSSController notification --
//--------------------------------------------------------------//

- (void)rssArticlesRead:(NSNotification*)notification
{
    // Update RSS badge
    [self updateRSSBadge];
}

//--------------------------------------------------------------//
#pragma mark -- NSManagedObjectContext notification --
//--------------------------------------------------------------//

- (void)managedObjectContextDidSave:(NSNotification*)notificaiton
{
    // Update RSS badge
    [self updateRSSBadge];
}

//--------------------------------------------------------------//
#pragma mark -- Key value observation --
//--------------------------------------------------------------//

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void *)context
{
    // For user defaults
    if (object == [NSUserDefaults standardUserDefaults]) {
        // RSS pref
        if ([keyPath isEqualToString:RSSOtherBrowserUsageFlags]) {
            [self addFeedsInBookmarks];
        }
    }
}

@end
