/*
 Flash: Flashcard program
 Copyright (C) 2005, Niko Matsakis (niko@alum.mit.edu)
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#import "Articles.h"
#import "OAMacros.h"
#import "Accessors.h"
#import "DictionaryDocument.h"
#import "OADebug.h"
#import "OACoreData.h"

@implementation Articles

- (void) awakeFromNib
{
    // listen for updates to the managed object context:
    NSManagedObjectContext *mctx = [_dictionary managedObjectContext];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self 
               selector:@selector(objectsChanged:) 
                   name:NSManagedObjectContextObjectsDidChangeNotification
                 object:mctx];
    _managedObjectContext = [mctx retain];
}

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

- (void) objectsChanged:(NSNotification*)notification
{
    NSSet *newkeys = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
    NSSet *updkeys = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    //NSSet *delkeys = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
    
    /*
    
     The purpose of this method is to keep the connections between words and their
     articles up to date in the face of changes on either side.
     
     Here is what we do:
     
     - When an Article is first created, we create an associated Word that contains
     the text of the article.  We then scan all existing Words for those Words W that
     start with this article and make a connection between the Article's Word and the
     other words in W.
     
     - When the text of a Word associated with an article changes, we clear any 
     connections from that word and rescan.
     
     - When a new word is created, we scan through the Articles to see if it
     should be associated with any of them.   Note that this ends up happening when the "text"
     attribute is set, and thus in the "updated keys" section.
     
     - When the text of a Word changes which is NOT associated with an article,
     we clear any Article-related connections from it and rescan it to see if
     any existing Articles match.
    
    */
    
    @try {
        OAIndent (@"objectsChanged: updating article connections");
        //OALog (@"Deleted keys: %d", [delkeys count]);
        
        @try {
            OAIndent (@"Updated words: %d keys", [updkeys count]);
            
            FOREACH (NSManagedObject *, word, updkeys) {
                NSString *entname = [[word entity] name];
                if (![entname isEqualToString:@"Word"])
                    continue; // we only monitor Words for changes
                if (![word readyForUseValue])
                    continue; // ignore any updates until the word has been fully initialized
                
                NSManagedObject *article = [word articleValue];
                if (article != nil) {
                    // add new ones
                    [self updateArticleConnections:article associatedWord:word];
                }
                else {
                    // UPDATED WORD: 
                    // - check whether the current connections are correct
                    // - delete any connections made to this word from an article
                    // - make any new connections as appropriate
                    [self updateWordConnections:word];
                }
            } ENDFOREACH;    
        }
        @finally {
            OAUndent ();
        }
        
        // For new articles, just create connections for it.
        @try {
            OAIndent (@"New Articles: %d keys", [newkeys count]);
            FOREACH (NSManagedObject *, article, newkeys) {
                NSString *entname = [[article entity] name];
                if (![entname isEqualToString:@"Article"])
                    continue;
                
                // It needs to have an associated Word with the initial text.
                NSManagedObject *word = [article wordValue];
                if (word == nil)
                    word = [_dictionary createWordWithText:@"New Article"
                                                promptWith:NO
                                                   article:article
                                                  language:[article languageValue]];
                
                [self updateArticleConnections:article associatedWord:word];
            } ENDFOREACH;
        } 
        @finally {
            OAUndent ();
        }
    }
    @finally {
        OAUndent ();
    }
}

- (void) updateArticleConnections:(NSManagedObject*)article associatedWord:(NSManagedObject*)artword
{
    // Obtain the built-in relationship "article"
    NSManagedObject *artrel = [_dictionary relationshipForArticles];
    NSString *arttext = [artword textValue];
    NSString *artprefix = [NSString stringWithFormat:@"%@ ", [artword textValue]];

    // Check whether the text of the article changed since it was last updated.
    // If not, no work to do.
    if ([arttext isEqual:[artword textAtLastArticleUpdateValue]])
        return;
        
    @try {
        OAIndent (@"Updating words associated with article %@", arttext);

        // Determine what words use this article and what words *should* use this article
        NSManagedObjectContext *mctx = [_dictionary managedObjectContext];
        NSArray *apprwords = [mctx objectsOfEntityType:E_WORD 
                               matchingPredicateFormat:@"text BEGINSWITH %@ AND language == %@",
            artprefix, [article languageValue]];
        
        // Delete all old connections; we know that single the text of the article
        // changed they will no longer be appropriate.
        @try {
            OAIndent (@"Deleting previous associations");
            FOREACH (NSManagedObject *, conn, [artword connectionsFromValue]) {
                [_managedObjectContext deleteObject:conn];
            } ENDFOREACH;
            FOREACH (NSManagedObject *, conn, [artword connectionsToValue]) {
                [_managedObjectContext deleteObject:conn];
            } ENDFOREACH;
        }
        @finally {
            OAUndent ();
        }
        
        // Now add new ones
        @try {
            OAIndent (@"Associating with new words");
            FOREACH (NSManagedObject *, word, apprwords) {
                OALog (@"Adding new connection %@", [word textValue]);
                [_dictionary connectWord:word
                                  toWord:artword
                            relationship:artrel];
            } ENDFOREACH;
        }
        @finally {
            OAUndent ();
        }
        
        // Update "textAtLastArticleUpdate" to reflect that we updated
        // the articles.
        OALog (@"Updating textAtLastArticleUpdate value");
        [artword setTextAtLastArticleUpdateValue:arttext];
    }
    @finally {
        OAUndent ();
    }
}

- (void) updateWordConnections:(NSManagedObject*)word
{
    NSManagedObjectContext *mctx = [_dictionary managedObjectContext];
    NSManagedObject *artrel = [_dictionary relationshipForArticles];
    NSString *wordtext = [word textValue];
    
    // Check whether the text of the word has changed since we last 
    // updates its articles.
    if ([wordtext isEqual:[word textAtLastArticleUpdateValue]])
        return;
        
    @try {
        OAIndent (@"Updating articles associated with word %@", wordtext);
 
        // Find out which articles we *should* have; unfortunately, I can't see a way
        // to do this with a predicate since the article's value must be mutated.
        NSSet *allarticles = [[word languageValue] valueForKey:@"articles"];
        NSMutableSet *apprset = [NSMutableSet setWithCapacity:[allarticles count]];
        FOREACH (NSManagedObject *, art, allarticles) {
            NSManagedObject *artword = [art wordValue];
            NSString *artwordtext = [NSString stringWithFormat:@"%@ ", [artword textValue]];
            if ([wordtext hasPrefix:artwordtext])
                [apprset addObject:artword];
        } ENDFOREACH;
        
        // Now go through our current connections and delete any that are no longer
        // approrpiate.
        @try {
            OAIndent (@"Deleting previous associations");
            NSArray *tokill = [mctx objectsOfEntityType:@"Connection" 
                                matchingPredicateFormat:@"from == %@ AND to.article != nil", word];
            FOREACH (NSManagedObject *, conn, tokill) {
                // Determine which (from vs to) points to the article
                NSManagedObject *artword = [conn toValue];
                
                // Check whether article is needed.
                if (![apprset containsObject:artword])
                    [_managedObjectContext deleteObject:conn];
                else
                    OALog (@"Not purging connection to %@ because still appr", [artword textValue]);
            } ENDFOREACH;
            
            // Now remove those we already have from 'artword'
            FOREACH (NSManagedObject *, conn, tokill) {
                [apprset removeObject:[conn toValue]];
            } ENDFOREACH;
        } 
        @finally {
            OAUndent ();
        }

        // And add new connections for each appropriate article
        FOREACH (NSManagedObject *, artword, apprset) {
            OALog (@"associated with new article %@", [artword textValue]);
            [_dictionary connectWord:word
                              toWord:artword
                        relationship:artrel];
        } ENDFOREACH;
        
        // Update "textAtLastArticleUpdate" to reflect that we updated
        // the articles.
        OALog (@"Updating textAtLastArticleUpdate value");
        [word setTextAtLastArticleUpdateValue:wordtext];
    } 
    @finally {
        OAUndent ();
    }
    
}

@end
