/*
SRSDXMLFileManager.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 "SRSDSyndication.h"
#import "SRSDAppController.h"
#import "SRSDRSSDocument.h"
#import "SRSDXMLFileManager.h"

#import "SRSDUtil.h"

@interface NSCharacterSet (NewLine)
+ (NSCharacterSet*)newLineCharacterSet;
@end

@implementation NSCharacterSet (NewLine)

+ (NSCharacterSet*)newLineCharacterSet
{
    static NSCharacterSet*  _newlineCharacterSet = nil;
    if (!_newlineCharacterSet) {
        unichar     newlineChars[] = {0x000A, 0x000D, 0x0085};
        NSString*   newlineString;
        newlineString = [NSString stringWithCharacters:newlineChars 
                length:sizeof(newlineChars) / sizeof(unichar)];
        _newlineCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:newlineString] retain];
    }
    
    return _newlineCharacterSet;
}

@end

#pragma mark -

@implementation SRSDXMLFileManager

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

- (id)initWithAppController:(SRSDAppController*)appController
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    //  Initialize member variables
    _appController = appController;
    _filePathDict = [[NSMutableDictionary dictionary] retain];
    
    return self;
}

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

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

+ (NSString*)libraryFolder
{
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Get the paths of ~/Library/
    NSArray*	libraryPaths;
    NSString*	libraryPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        libraryPath = [libraryPaths objectAtIndex:0];
    }
    
    if (!libraryPath || ![fileMgr fileExistsAtPath:libraryPath]) {
        // Error
        NSLog(@"Could not find library directory");
        return nil;
    }
    
    // Check Shiira directory
    NSString*   shiiraPath;
    shiiraPath = [libraryPath stringByAppendingPathComponent:@"Shiira"];
    if (![fileMgr fileExistsAtPath:shiiraPath]) {
        // Create Shiira directory
        [fileMgr createDirectoryAtPath:shiiraPath attributes:nil];
    }
    
    return shiiraPath;
}

+ (NSString*)feedsFolderPath
{
    return [[self libraryFolder] stringByAppendingPathComponent:@"Feeds"];
}

+ (NSString*)feedsFilePath
{
    return [[self feedsFolderPath] stringByAppendingPathComponent:@"Feeds.plist"];
}

//--------------------------------------------------------------//
#pragma mark -- RSS document --
//--------------------------------------------------------------//

- (NSMutableDictionary*)_channelWithDocumentId:(NSString*)documentId feeds:(NSArray*)feeds
{
    NSEnumerator*           enumerator;
    NSMutableDictionary*    channel;
    enumerator = [feeds objectEnumerator];
    while (channel = [enumerator nextObject]) {
        if ([documentId isEqualToString:[channel objectForKey:@"documentId"]]) {
            return channel;
        }
    }
    
    return nil;
}

- (NSMutableDictionary*)_itemWithItemId:(NSString*)itemId items:(NSArray*)items
{
    NSEnumerator*           enumerator;
    NSMutableDictionary*    item;
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        NSString*   tmpItemId;
        tmpItemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if ([itemId isEqualToString:tmpItemId]) {
            return item;
        }
    }
    
    return nil;
}

- (void)_copyChannelInfo:(NSDictionary*)channel toNewChannel:(NSMutableDictionary*)newChannel
{
    // Channel title
    NSString*   title;
    title = [channel objectForKey:@"title"];
    if (!title || [title length] == 0) {
        title = [NSString stringWithFormat:@"(%@)", NSLocalizedString(@"Untitled", nil)];
    }
    title = [title stringByTrimmingCharactersInSet:[NSCharacterSet newLineCharacterSet]];
    [newChannel setObject:title forKey:@"title"];
    
    // Channel link
    NSString*   link;
    link = [channel objectForKey:@"link"];
    if (link && [link length] > 0) {
        [newChannel setObject:link forKey:@"link"];
    }
    
    // Channel date
    NSDate* date;
    date = [channel objectForKey:@"date"];
    if (date) {
        [newChannel setObject:date forKey:@"date"];
    }
}

- (void)_removeOldArticlesWithChannel:(NSMutableDictionary*)channelDict
{
    // For never
    if (SRSDRemove == 500) { // SRRSSRemoveNever
        return;
    }
    
    // Decide day for removing
    int day = 1;
    if (SRSDRemove == 1) { // SRRSSRemoveOneWeek
        day = 7;
    }
    else if (SRSDRemove == 2) { // SRRSSRemoveOneMonth
        day = 30;
    }
    
    // Create remove date
    NSDate* date;
    date = [NSDate dateWithTimeIntervalSinceNow:-1 * day * 60 * 60 * 24];
    
    // Check items
    NSMutableArray* items;
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    items = [channelDict objectForKey:@"items"];
    enumerator = [items reverseObjectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get item date
        NSDate* itemDate;
        itemDate = [item objectForKey:@"date"];
        
        if ([itemDate earlierDate:date] == itemDate) {
            // Remove it
            [items removeObject:item];
        }
    }
}

- (void)_mergeItemsWithChannel:(NSMutableDictionary*)oldChannelDict 
        intoChannel:(NSMutableDictionary*)newChannelDict numberOfNewItems:(int*)count
{
#if 1
    NSDictionary*   item;
    NSString*       itemId;
    NSEnumerator*   enumerator;
    
    // Collect new item IDs
    NSMutableArray* newItems;
    NSMutableSet*   newItemIdSet;
    newItems = [newChannelDict objectForKey:@"items"];
    newItemIdSet = [NSMutableSet set];
    enumerator = [newItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (itemId && ![newItemIdSet containsObject:itemId]) {
            [newItemIdSet addObject:itemId];
        }
    }
    
    // Collect old item IDs
    NSMutableArray* oldItems;
    NSMutableSet*   oldItemIdSet;
    oldItems = [oldChannelDict objectForKey:@"items"];
    oldItemIdSet = [NSMutableSet set];
    enumerator = [oldItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (itemId && ![oldItemIdSet containsObject:itemId]) {
            [oldItemIdSet addObject:itemId];
        }
    }
    
    // Count new items
    if (count) {
        enumerator = [newItemIdSet objectEnumerator];
        while (itemId = [enumerator nextObject]) {
            if (![oldItemIdSet containsObject:itemId]) {
                // Increment count
                *count = (*count) + 1;
            }
        }
    }
    
    // Merge old items into new items
    enumerator = [oldItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (itemId && ![newItemIdSet containsObject:itemId]) {
            // Add old item
            [newItems addObject:item];
        }
    }
#else
    NSString*   itemId;
    
    // Collect old item IDs
    NSMutableSet*   set;
    NSMutableArray* oldItems;
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    set = [NSMutableSet set];
    oldItems = [oldChannelDict objectForKey:@"items"];
    enumerator = [oldItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (itemId) {
            [set addObject:itemId];
        }
    }
    
    // Merge into new items
    NSMutableArray* items;
    items = [channelDict objectForKey:@"items"];
    enumerator = [items reverseObjectEnumerator];
    while (item = [enumerator nextObject]) {
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (itemId && ![set containsObject:itemId]) {
            [set addObject:itemId];
            
            // Insert item
            [oldItems insertObject:item atIndex:0];
            
            if (count) {
                *count = (*count) + 1;
            }
        }
    }
#endif
}

- (void)_addXMLFileDict:(NSDictionary*)XMLFileDict 
        withFeeds:(NSMutableArray*)feeds numberOfNewItems:(int*)count
{
    // Get document identifier
    NSString*   documentId;
    documentId = [XMLFileDict objectForKey:@"documentId"];
    if (!documentId) {
        return;
    }
    
    // Get XML file path
    NSString*   XMLFilePath;
    XMLFilePath = [XMLFileDict objectForKey:@"XMLPath"];
    if (!XMLFilePath) {
        return;
    }
    
    // Parse XML file
    NSMutableDictionary*    channel;
    channel = [SRSDRSSDocument parseWithContentsOfURL:[NSURL fileURLWithPath:XMLFilePath] options:0 error:NULL];
    if (!channel) {
        return;
    }
    
    // Decide channel path
    NSString*   fileName;
    NSString*   channelPath;
    fileName = [NSString stringWithFormat:@"%u.plist", [documentId hash]];
    channelPath = [[[self class] feedsFolderPath] stringByAppendingPathComponent:fileName];
    
    // Remove old articles
    [self _removeOldArticlesWithChannel:channel];
    
    // Merge with old channel
    NSData* data;
    if ([[NSFileManager defaultManager] fileExistsAtPath:channelPath]) {
        data = [NSData dataWithContentsOfFile:channelPath];
        
        NSMutableDictionary*    oldChannel;
        oldChannel = [NSPropertyListSerialization propertyListFromData:data 
                mutabilityOption:NSPropertyListMutableContainersAndLeaves 
                format:NULL 
                errorDescription:NULL];
        
        // Remove old articles
        [self _removeOldArticlesWithChannel:oldChannel];
        
        // Merge items
        [self _mergeItemsWithChannel:oldChannel intoChannel:channel numberOfNewItems:count];
    }
    else {
        if (count) {
            *count = [[channel objectForKey:@"items"] count];
        }
    }
    
    // Save channel
    data = [NSPropertyListSerialization dataFromPropertyList:channel 
            format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL];
    if (data) {
        [data writeToFile:channelPath atomically:YES];
    }
    
    // Get channel from feeds
    NSMutableDictionary*    feedChannel;
    feedChannel = [self _channelWithDocumentId:documentId feeds:feeds];
    if (!feedChannel) {
        // Create channel
        feedChannel = [NSMutableDictionary dictionary];
        [feedChannel setObject:@"channel" forKey:@"type"];
        [feedChannel setObject:documentId forKey:@"documentId"];
        [feedChannel setObject:[XMLFilePath lastPathComponent] forKey:@"XMLFileName"];
        
        // Add channel
        [feeds addObject:feedChannel];
    }
    
    // Copy channel info
    [self _copyChannelInfo:channel toNewChannel:feedChannel];
    
    // Get feed items
    NSArray*        feedItems = nil;
    NSMutableArray* items;
    feedItems = [feedChannel objectForKey:@"items"];
    items = [NSMutableArray array];
    
    // Create items
    NSEnumerator*           enumerator;
    NSMutableDictionary*    item;
    enumerator = [[channel objectForKey:@"items"] objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get item ID
        NSString*   itemId;
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        if (!itemId) {
            continue;
        }
        
        // Get feed item
        NSMutableDictionary*    feedItem;
        feedItem = [self _itemWithItemId:itemId items:feedItems];
        
        // Create item
        NSMutableDictionary*    newItem;
        newItem = [NSMutableDictionary dictionary];
        [newItem setObject:@"item" forKey:@"type"];
        [newItem setObject:documentId forKey:@"documentId"];
        
        BOOL    isPreviewed = NO;
        if (feedItem) {
            NSNumber*   number;
            number = [feedItem objectForKey:@"isPreviewed"];
            if (number) {
                isPreviewed = [number boolValue];
            }
        }
        [newItem setObject:[NSNumber numberWithBool:isPreviewed] forKey:@"isPreviewed"];
        
        // Item title
        NSString*   itemTitle;
        itemTitle = [item objectForKey:@"title"];
        if (!itemTitle || [itemTitle length] == 0) {
            itemTitle = [NSString stringWithFormat:@"(%@)", NSLocalizedString(@"Untitled", nil)];
        }
        itemTitle = [itemTitle stringByTrimmingCharactersInSet:[NSCharacterSet newLineCharacterSet]];
        [newItem setObject:itemTitle forKey:@"title"];
        
        // Item link
        NSString*   itemLink;
        itemLink = [item objectForKey:@"link"];
        if (itemLink && [itemLink length] > 0) {
            [newItem setObject:itemLink forKey:@"link"];
        }
        
        // Item date
        NSDate* itemDate;
        itemDate = [item objectForKey:@"date"];
        if (itemDate) {
            [newItem setObject:itemDate forKey:@"date"];
        }
        
        // Add item
        [items addObject:newItem];
    }
    
    // Add items
    [feedChannel setObject:items forKey:@"items"];
}

- (void)addXMLFileDicts:(NSArray*)XMLFileDicts numberOfNewItems:(int*)count
{
    // Load feeds.plist
    NSMutableArray* feeds;
    feeds = [self loadFeeds];
    
    // Clear count
    if (count) {
        *count = 0;
    }
    
    // Add XML file dicts
    NSEnumerator*   enumerator;
    NSDictionary*   XMLFileDict;
    enumerator = [XMLFileDicts objectEnumerator];
    while (XMLFileDict = [enumerator nextObject]) {
        NSAutoreleasePool*  pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        [self _addXMLFileDict:XMLFileDict withFeeds:feeds numberOfNewItems:count];
        
        [pool release];
    }
    
    // Save feeds.plist
    [self saveFeeds:feeds];
}

//--------------------------------------------------------------//
#pragma mark -- XML file for feed --
//--------------------------------------------------------------//

- (NSString*)tmpXMLFilePathWithURL:(NSURL*)feedURL
{
    // Check in dict
    NSString*   path;
    path = [_filePathDict objectForKey:[feedURL absoluteString]];
    if (path) {
        return path;
    }
    
    // Create path
    NSString*   uuidString;
    uuidString = SRCreateUUID();
    path = [[self class] feedsFolderPath];
    if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil];
    }
    path = [path stringByAppendingPathComponent:@"tmp"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil];
    }
    path = [path stringByAppendingPathComponent:uuidString];
    path = [path stringByAppendingPathExtension:@"xml"];
    
    // Register file path
    [_filePathDict setObject:path forKey:[feedURL absoluteString]];
    
    return path;
}

- (NSString*)XMLFilePathWithDocument:(SRSDRSSDocument*)document
{
    return [self XMLFilePathWithDocumentID:[document identifier]];
}

- (NSString*)XMLFilePathWithDocumentID:(NSString*)identifier
{
    NSString*   fileName;
    fileName = [NSString stringWithFormat:@"%u.xml", [identifier hash]];
    return [[[self class] feedsFolderPath] stringByAppendingPathComponent:fileName];
}

//--------------------------------------------------------------//
#pragma mark -- File management --
//--------------------------------------------------------------//

- (void)removeAllTmpFiles
{
    // Get path
    NSString*   dirPath;
    dirPath = [[self class] feedsFolderPath];
    dirPath = [dirPath stringByAppendingPathComponent:@"tmp"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {
        NSDirectoryEnumerator*  enumerator;
        NSString*               file;
        enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dirPath];
        while (file = [enumerator nextObject]) {
            // Remove .xml file
            if ([[file pathExtension] isEqualToString:@"xml"] && 
                ![file hasPrefix:@"inner"])
            {
                NSString*   path;
                path = [dirPath stringByAppendingPathComponent:file];
                [[NSFileManager defaultManager] removeFileAtPath:path handler:NULL];
            }
        }
    }
}

- (NSMutableArray*)loadFeeds
{
    // Load feeds.plist
    NSString*       feedsFolder;
    NSString*       path;
    NSMutableArray* feeds = nil;
    feedsFolder = [[self class] feedsFolderPath];
    path = [[self class] feedsFilePath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
        feeds = [NSPropertyListSerialization propertyListFromData:[NSData dataWithContentsOfFile:path] 
                mutabilityOption:NSPropertyListMutableContainersAndLeaves 
                format:NULL 
                errorDescription:NULL];
    }
    
    if (!feeds) {
        feeds = [NSMutableArray array];
    }
    
    return feeds;
}

- (void)saveFeeds:(NSArray*)feeds
{
    // Save feeds.plist
    NSString*   feedsFolder;
    NSString*   path;
    feedsFolder = [[self class] feedsFolderPath];
    path = [[self class] feedsFilePath];
    if (![[NSFileManager defaultManager] fileExistsAtPath:feedsFolder]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:feedsFolder attributes:nil];
    }
    [feeds writeToFile:path atomically:YES];
}

@end
