/*
 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 "DictionaryDocument.h"
#import "Accessors.h"
#import "QuizController.h"
#import "Import.h"
#import "WordConsolidation.h"
#import "OACoreData.h"
#import "OAKeyboardLayout.h"
#import "OACategories.h"
#import "OADebug.h"
#import "OAMacros.h"
#import "OAValueTransformers.h"
#import "VersionImport.h"

#define NUMWORDS 2

#define EQUIVALENT @"equivalent"
#define ARTICLE @"article"

@interface DictionaryDocument (private)
- (void) purgeOldMistakes;
@end

@implementation DictionaryDocument

/*
- _handleDocumentFileChanges:(id)arg
{
    return [super _handleDocumentFileChanges:arg];
}
*/

#pragma mark -
#pragma mark Boilerplate

- (id)initWithType:(NSString *)type error:(NSError **)error {
    
    self = [super initWithType:type error:error];
    if (self != nil) {
        // Initialize the file w/ a Dictionary object and two Language objects.
        // initWithType:error: is only called when a document is newly
        // created (not opened from disk), so do it here.
        
        NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
        [NSEntityDescription insertNewObjectForEntityForName:@"Dictionary" 
                                      inManagedObjectContext:managedObjectContext];
                           
        // Add the "equivalent" and "article" relationships by default
        [self createRelationshipNamed:EQUIVALENT
                        interlanguage:YES
                            immutable:YES
                            automatic:NO];

        [self createRelationshipNamed:ARTICLE
                        interlanguage:NO
                            immutable:YES
                            automatic:YES];
                                           
        // Avoid undo registration for this insertion
        [managedObjectContext processPendingChanges];
        [[managedObjectContext undoManager] removeAllActions];
        [self updateChangeCount:NSChangeCleared];
        
        /*
        // For debugging change count:
        NSNotificationCenter *notcenter = [NSNotificationCenter defaultCenter];
        [notcenter addObserver:self 
                      selector:@selector(undoEvent:) 
                          name:NSUndoManagerDidUndoChangeNotification 
                        object:[self undoManager]];
        [notcenter addObserver:self 
                      selector:@selector(undoEvent:) 
                          name:NSUndoManagerDidRedoChangeNotification 
                        object:[self undoManager]];
        [notcenter addObserver:self 
                      selector:@selector(undoEvent:) 
                          name:NSUndoManagerCheckpointNotification
                        object:[self undoManager]];
         */
    }
    OALog (@"initWithType");
    return self;
}

- (id)initWithContentsOfURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
    id res = [super initWithContentsOfURL:absoluteURL ofType:typeName error:outError];
    OALog (@"initWithContentsOfURL: check version");
    return res;
}

- (void) dealloc 
{
    NSNotificationCenter *notcenter = [NSNotificationCenter defaultCenter];
    [notcenter removeObserver:self];
    [a_relationshipForArticles release];
    [a_relationshipForEquivalence release];
    [_wordConsolidation release];
    [super dealloc];
}

- (void) awakeFromNib
{
    OALog (@"awakeFromNib");
    
    // this really doesn't need to be invoked every time, but I don't think it hurts to do so:
    OARegisterValueTransformers();

    if (_wordConsolidation == nil)
        _wordConsolidation = [[WordConsolidation alloc] initWithDictionaryDocument:self];
}

- (NSString *)windowNibName 
{
    return @"Dictionary";
}

- (NSWindow*) mainWindow
{
    return m_mainWindow;
}

#pragma mark -
#pragma mark Debugging

/*
- (void) undoEvent:(NSNotification*)notification
{
    OALog (@"Undo Event: %@", [notification name]);
}
 */

- (NSManagedObject*)relationshipForArticles
{
    if (!a_relationshipForArticles) {
        NSManagedObjectContext *mctx = [self managedObjectContext];
        a_relationshipForArticles = [[mctx objectOfEntityType:E_RELATIONSHIP 
                                      matchingPredicateFormat:@"immutable = YES AND name = %@", ARTICLE] retain];
    }
    return a_relationshipForArticles;
}

- (NSManagedObject*)relationshipForEquivalence
{
    if (!a_relationshipForEquivalence) {
        NSManagedObjectContext *mctx = [self managedObjectContext];
        a_relationshipForEquivalence = 
            [[mctx objectOfEntityType:E_RELATIONSHIP 
              matchingPredicateFormat:@"immutable = YES AND name = %@", EQUIVALENT] retain];
    }
    return a_relationshipForEquivalence;
}

- (NSManagedObject*)dictionaryObject
{
    NSManagedObjectContext *mctx = [self managedObjectContext];
    NSManagedObject *res = [mctx objectOfEntityType:E_DICTIONARY];
    NSAssert(res, @"Failed to find dictionary??");
    return res;
}

#pragma mark -
#pragma mark Error Presentation

// Intercept validation errors with willPresentError: so that we can handle their display
- (NSError *)willPresentError:(NSError *)inError 
{
	// The error is a Core Data validation error if its domain is NSCocoaErrorDomain and it is between
	// the minimum and maximum for Core Data validation error codes.
	if ([[inError domain] isEqualToString:NSCocoaErrorDomain]) {
		int errorCode = [inError code];
		if ( errorCode >= NSValidationErrorMinimum && errorCode <= NSValidationErrorMaximum) {
            
			// If there are multiple validation errors, inError will be a NSValidationMultipleErrorsError
			// and all the validation errors will be in an array in the userInfo dictionary for key NSDetailedErrorsKey
			id detailedErrors = [[inError userInfo] objectForKey:NSDetailedErrorsKey];
			if (detailedErrors != nil) {
                
				// For this example we are only presenting the error messages for up to 3 validation errors at a time.
				// We are simply passing the NSLocalizedDescription for each error to the user, but one could instead
				// construct a customized, user-friendly error here. The error codes and userInfo dictionary
				// keys for validation errors are listed in <CoreData/CoreDataErrors.h>.
				
				unsigned numErrors = [detailedErrors count];							
				NSMutableString *errorString = [NSMutableString stringWithFormat:@"%u validation errors have occurred", numErrors];
				if (numErrors > 3)
					[errorString appendFormat:@".\nThe first 3 are:\n"];
				else
					[errorString appendFormat:@":\n"];
				
				unsigned i;
				for (i = 0; i < (numErrors > 3 ? 3 : numErrors); ++i) {
					[errorString appendFormat:@"%@\n", [[detailedErrors objectAtIndex:i] localizedDescription]];
				}
				
				NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[inError userInfo]];
				[userInfo setObject:errorString forKey:NSLocalizedDescriptionKey];
				
				return [NSError errorWithDomain:[inError domain] code:[inError code] userInfo:userInfo];
				
			} else {
				// As there is only one validation error, we are returning it verbatim to the user.
				return inError;
			}
		}
	}
	return inError;
}

#pragma mark -
#pragma mark Random NSArrayController sources

// This is accessed via the bindings to give labels to the integer that stores the
// quiz direction.
- (NSArray*) quizDirectionLabels
{
    return [NSArray arrayWithObjects:@"Normal", @"Inverse", @"Two Way", nil];
}

- (NSArray*) keyboardLayouts
{
    return [OAKeyboardLayout allKeyboardLayouts];
}

#pragma mark -
#pragma mark Import

- (IBAction) startImport:sender
{
    Import *import = [[Import alloc] initWithDocument:self];
    [import startImport];
}

- (IBAction) startImportFromOldVersion:sender
{
    NSOpenPanel *op = [NSOpenPanel openPanel];
    [op setAllowsMultipleSelection:NO];
    int result = [op runModalForTypes:nil];
    if (result == NSCancelButton) 
        return;
    
    NSString *fileName = [[op filenames] objectAtIndex:0];
    [OldVersionImport transferFromFile:fileName
                        intoDictionary:self
                        whenDoneObject:self
                      whenDoneSelector:@selector(importComplete:)];
}

- (void) importComplete:(NSError*)error
{
}

#pragma mark -
#pragma mark Object Creation

- (NSManagedObject*) wordWithText:(NSString*)text language:(NSManagedObject*)language
{
    NSAssert1(text != nil, @"wordWithText:language: invoked with nil word, language %@", [language nameValue]);
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"text = %@ AND language = %@", text, language];
    NSManagedObject *word = [[self managedObjectContext] objectOfEntityType:@"Word" 
                                                                  matchingPredicate:pred];
    if (word) 
        return word;
    
    word = [self createWordWithText:text promptWith:YES article:nil language:language];
    return word;
}

- (void) touchWord:(NSManagedObject*)word
{
    [word setCreationDateValue:[NSDate date]];
}

- (void) connectWord:(NSManagedObject*)fromWord
              toWord:(NSManagedObject*)toWord
        relationship:(NSManagedObject*)relationship
{
    // Check if fromWord contains a relationship to toWord of the appropriate
    // type.
    NSPredicate *matching = [NSPredicate predicateWithFormat:@"to == %@ AND type == %@", toWord, relationship];    
    NSSet *fromConn = [fromWord connectionsFromValue];
    if ([fromConn containsObjectMatchingPredicate:matching])
        return;
    
    // If not, create a connection.
    [self createConnectionFromWord:fromWord
                            toWord:toWord
                      relationship:relationship];
}

- (void) createAndConnectWords:(NSString**)texts
                     languages:(NSManagedObject**)langs
                  relationship:(NSManagedObject*)relationship
{
    if (![[relationship interlanguageValue] boolValue]) {
        OALog (@"Relationship not interlanguage");
        langs[1] = langs[0];
    }
    
    /* For each word, add it to the dictionary */
    NSManagedObject *words[NUMWORDS] = {nil, nil};
    for (int i = 0; i < NUMWORDS; i++) {
        NSManagedObject *lang = langs[i];
        NSString *text = texts[i];
        
        /* don't allow whitespace */
        text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        
        /* check for existing word, creating one if necessary */
        words[i] = [self wordWithText:text language:lang];
        
        OALog (@"words[%d] == %@", i, words[i]);
    }
    
    [self connectWord:words[0] toWord:words[1] relationship:relationship];
}


- (NSManagedObject*) createRelationshipNamed:(NSString*)name
                               interlanguage:(BOOL)interlanguage
                                   immutable:(BOOL)immutable
                                   automatic:(BOOL)automatic
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];

    NSManagedObject *equiv = [NSEntityDescription
                insertNewObjectForEntityForName:@"Relationship"
                         inManagedObjectContext:managedObjectContext];                         
    [equiv setValue:name forKey:@"name"];
    [equiv setValue:[NSNumber numberWithBool:interlanguage] forKey:@"interlanguage"];
    [equiv setValue:[NSNumber numberWithBool:immutable] forKey:@"immutable"];
    [equiv setValue:[NSNumber numberWithBool:automatic] forKey:@"automatic"];
    
    return equiv;
}


- (NSManagedObject*) createWordWithText:(NSString*)text
                             promptWith:(BOOL)promptwith
                                article:(NSManagedObject*)article
                               language:(NSManagedObject*)language
{
    NSManagedObject *word = [NSEntityDescription
                insertNewObjectForEntityForName:@"Word"
                         inManagedObjectContext:[self managedObjectContext]];
    [word setValue:text forKey:@"text"];
    [word setValue:[NSDate date] forKey:@"creationDate"];
    [word setValue:language forKey:@"language"];
    [word setValue:[NSNumber numberWithBool:promptwith] forKey:@"promptWith"];
    if (article) [word setValue:article forKey:@"article"];
    
    /* Now we must give it a unique ID; this is a 64 bit
    integer.  We just grab the next one from the Dictionary.  These
    IDs are never re-used, so we increment the counter in the
    dictionary as well. */
    NSManagedObject *dict = [self dictionaryObject];
    NSNumber *masterid = [dict valueForKey:@"masterId"];
    
    /* Set id for this word */
    [word setValue:masterid forKey:@"id"];
    OALog (@"Set master ID to be %@", masterid);
    
    /* Increment master id */
    long long value = [masterid longLongValue]+1;
    [dict setValue:[NSNumber numberWithLongLong:value]
            forKey:@"masterId"];
            
    /* set this flag when all properties are set */
    [word setReadyForUseValue];
            
    return word;
}

- (void) createConnectionFromWord:(NSManagedObject*)frword
                           toWord:(NSManagedObject*)toword
                     relationship:(NSManagedObject*)relationship
{
    NSManagedObject *conn =  [NSEntityDescription 
    insertNewObjectForEntityForName:@"Connection"
             inManagedObjectContext:[self managedObjectContext]];    
    [conn setFromValue:frword];
    [conn setToValue:toword];
    [conn setTypeValue:relationship];
}

@end

