
// NCCompletionDictionary.m
//
// Written by Norbert Heger <bertl@hal.kph.tuwien.ac.at>
// Copyright (c)1997
//
// This file is distributed under the terms of the
// GNU General Public License.

#import "NCCompletionDictionary.h"
#import "NCRuntimeHacks.h"
#import "NCTextView.h"

#define FILENAME @"~/Library/KeyBindings/CompletionKeyBinding.dict"

@implementation NCCompletionDictionary

static NSString *filename = nil;
static NSMutableDictionary *completionDictionary = nil;
static NSMutableArray *undoArray = nil;
static NSMutableArray *redoArray = nil;
static NSObject *nilObject = nil;

// ----------------------------------------------------------------------------------------
// Class methods
// ----------------------------------------------------------------------------------------

+ (NSDictionary *)dictionary { return completionDictionary; }
+ (void)load { [[self alloc] init]; }

+ (void)createDirectoryIfNecessary:(NSString *)path
{
    NSFileManager *manager = [NSFileManager defaultManager];
    BOOL isDirectory;
    BOOL fileExists = [manager fileExistsAtPath:path isDirectory:&isDirectory];

    if (!fileExists) {
        [manager createDirectoryAtPath:path attributes:nil];
    } else if (!isDirectory) {
        NSString *shortPath = [path stringByAbbreviatingWithTildeInPath];
        NSRunAlertPanel(@"Alert", @"%@ isn't a directory", @"OK", nil, nil, shortPath);
    }
}

+ (void)initialize
{
    if (self == [NCCompletionDictionary class]) {
        filename = [[FILENAME stringByExpandingTildeInPath] retain];
        [self createDirectoryIfNecessary:[filename stringByDeletingLastPathComponent]];
        completionDictionary = [[NSMutableDictionary alloc] init];
        [completionDictionary addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:filename]];

        undoArray = [[NSMutableArray alloc] init];
        redoArray = [[NSMutableArray alloc] init];
        nilObject = [[NSObject alloc] init];
    }
}

// ----------------------------------------------------------------------------------------
// Initializing the instance
// ----------------------------------------------------------------------------------------

- (void)initMenu
{
    NSString *title = @"Completion";
    SEL action = @selector(showPanel:);

    NSMenu *submenu = [[NSMenu alloc] initWithTitle:title];
    NSMenu *toolsMenu = [[[NSApp mainMenu] itemWithTitle:@"Tools"] target];
    NSMenuItem *subMenuItem = [toolsMenu addItemWithTitle:title action:0 keyEquivalent:@""];

    [[submenu addItemWithTitle:@"Show Panel..." action:action keyEquivalent:@"E"] setTarget:self];
    [[submenu addItemWithTitle:@"Import from Edit" action:@selector(import:) keyEquivalent:@""] setTarget:self];

    [toolsMenu setSubmenu:submenu forItem:subMenuItem];
}

- (void)updateSortedKeys
{
    [sortedKeys release];
   	sortedKeys = [[completionDictionary allKeys] sortedArrayUsingSelector:@selector(compare:)];
    [sortedKeys retain];
}

- (id)init
{
    [super init];
    [self initMenu];
    [self updateSortedKeys];

    [NSBundle loadNibNamed:@"NCCompletionDictionary" owner:self];
    [window setFrameAutosaveName:@"NCCompletionDictionary"];
    [window setFrameUsingName:@"NCCompletionDictionary"];

    [textView setClass:[NCTextView class]]; // PB BugFix

    return self;
}

// ----------------------------------------------------------------------------------------
// Writing changes to file
// ----------------------------------------------------------------------------------------

- (void)commitChanges
{
    [self updateSortedKeys];
    [browser reloadColumn:0];
    [completionDictionary writeToFile:filename atomically:YES];
    [window setDocumentEdited:NO];
}

// ----------------------------------------------------------------------------------------
// Hmm, there's no documented way to create multiple selections...
// ----------------------------------------------------------------------------------------

- (void)selectKeys:(NSArray *)keys
{
    NSMatrix *matrix = [browser matrixInColumn:0];
    unsigned int i, count = [keys count];

    keys = [keys sortedArrayUsingSelector:@selector(compare:)];
    for (i = 0; i < count; i++) {
        NSString *key = [keys objectAtIndex:i];
        if (i == 0) {
            [browser setPath:key];
        } else {
            int index = [sortedKeys indexOfObject:key];
            if (index >= 0) { [[matrix cellAtRow:index column:0] set]; }
        }
    }
    [window makeFirstResponder:browser];
    [self select:self];
}

// ----------------------------------------------------------------------------------------
// Undo / Redo
// ----------------------------------------------------------------------------------------

- (void)preserveObjectsForKeys:(NSArray *)keys inArray:(NSMutableArray *)array
{
    NSArray *objects = [completionDictionary objectsForKeys:keys notFoundMarker:nilObject];
    [array addObject:[NSDictionary dictionaryWithObjects:objects forKeys:keys]];
}

- (void)preserveObjectsForKeys:(NSArray *)keys
{
    [self preserveObjectsForKeys:keys inArray:undoArray];
    [redoArray removeAllObjects];
}

- (void)performUndo:(BOOL)undo
{
    NSMutableArray *array = undo ? undoArray : redoArray;

    if ([array count] > 0) {
        NSDictionary *dictionary = [array lastObject];
        NSArray *keys = [dictionary allKeys];
		unsigned int i, count = [keys count];

        [self preserveObjectsForKeys:keys inArray:(undo ? redoArray : undoArray)];

		for (i = 0; i < count; i++) {
            NSString *key = [keys objectAtIndex:i];
            id object = [dictionary objectForKey:key];

            if (object == nilObject) { [completionDictionary removeObjectForKey:key]; }
            else { [completionDictionary setObject:object forKey:key]; }
        }
        [self commitChanges];
        [self selectKeys:keys];
        [array removeLastObject];

    } else { NSBeep(); }
}

- (void)undo:(id)sender { [self performUndo:YES]; }
- (void)redo:(id)sender { [self performUndo:NO]; }

- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
    SEL action = [anItem action];
    if (action == @selector(undo:)) { return [undoArray count] > 0; }
    if (action == @selector(redo:)) { return [redoArray count] > 0; }
    return YES;
}

// ----------------------------------------------------------------------------------------
// Controller
// ----------------------------------------------------------------------------------------

- (void)showPanel:(id)sender
{
    [window makeKeyAndOrderFront:self];
}

- (void)set:(id)sender
{
    NSString *key = [keyTextField stringValue];

    [self preserveObjectsForKeys:[NSArray arrayWithObject:key]];
    [completionDictionary setObject:[[textView string] copy] forKey:key];
    [self commitChanges];
    [browser setPath:key];
    [keyTextField selectText:self];
}

- (void)doRemove
{
    NSArray *cells = [browser selectedCells];
    NSMutableArray *keys = [NSMutableArray array];
	unsigned int i, count = [cells count];

    for (i = 0; i < count; i++) {
        [keys addObject:[[cells objectAtIndex:i] stringValue]];
    }
    [self preserveObjectsForKeys:keys];
    [completionDictionary removeObjectsForKeys:keys];
    [self commitChanges];
    [keyTextField selectText:self];
}

- (void)remove:(id)sender
{
    if ([[browser selectedCells] count] > 0) { [self doRemove]; }
    else { NSBeep(); }
}

- (void)select:(id)sender
{
    NSArray *cells = [browser selectedCells];

    if ([cells count] == 1) {
        NSString *key = [[cells lastObject] stringValue];
        [keyTextField setStringValue:key];
        [textView setString:[completionDictionary objectForKey:key]];
    } else {
        [keyTextField setStringValue:@""];
		[textView setString:@""];
    }
    [window setDocumentEdited:NO];
}

// ----------------------------------------------------------------------------------------
// Import from .editdict
// ----------------------------------------------------------------------------------------

- (NSDictionary *)dictionaryFromEdit
{
    NSString *path = [@"~/.editdict" stringByExpandingTildeInPath];
    NSString *contents = [NSString stringWithContentsOfFile:path];
    NSMutableDictionary *dictionary = nil;
    
    if (contents != nil) {
        const char *cString = [contents cString];
        const char *p = cString;

        dictionary = [NSMutableDictionary dictionary];

        while (*p != 0) {
            NSString *key;
            NSMutableString *value = [NSMutableString string];
            const char *keyStart = p;

            while (*p != 0 && *p != '\t') p++;
            key = [NSString stringWithCString:keyStart length:(p - keyStart)];
            if (*p != 0) {
                BOOL escaped = NO; char c;
                p++;
                while ((c = *p++) != 0) {
                    if (c == '\\') {
                        escaped = !escaped;
                    } else {
                        if (c == '\n' && !escaped) { break; }
                        escaped = NO;
                    }
                    if (!escaped) { [value appendFormat:@"%c", c]; }
                }
                if ([key length] && [value length]) {
                    [dictionary setObject:value forKey:key];
                }
            }
        }
    }
    return dictionary;
}

- (void)importFromEdit
{
    NSDictionary *dictionary = [self dictionaryFromEdit];
    if (dictionary != nil) {
        [self preserveObjectsForKeys:[dictionary allKeys]];
        [completionDictionary addEntriesFromDictionary:dictionary];
        [self commitChanges];
        [self selectKeys:[dictionary allKeys]];
    }
}

- (void)import:(id)sender
{
    NSString *message = @"Do you really want to import the Completion Dictionary settings from ~/.editdict?";

    if (NSRunAlertPanel(@"Import", message, @"Import", @"Cancel", nil) == NSAlertDefaultReturn) {
        [self importFromEdit];
    }
}

// ----------------------------------------------------------------------------------------
// NSText Delegate
// ----------------------------------------------------------------------------------------

- (void)controlTextDidChange:(NSNotification *)aNotification
{
    if ([aNotification object] == keyTextField) {
        NSString *key = [keyTextField stringValue];
        if ([completionDictionary objectForKey:key] != nil) {
            [browser setPath:key];
        }
    }
}

- (void)textDidChange:(NSNotification *)aNotification
{
    if ([aNotification object] == textView) {
        [window setDocumentEdited:YES];
    }
}

// ----------------------------------------------------------------------------------------
// NSBrowser Delegate
// ----------------------------------------------------------------------------------------

- (int)browser:(NSBrowser *)sender numberOfRowsInColumn:(int)column
{
    return column == 0 ? [sortedKeys count] : 0;
}

- (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(int)row column:(int)column
{
    if (column == 0) {
        [cell setStringValue:[sortedKeys objectAtIndex:row]];
        [cell setLeaf:YES];
    }
}

// ----------------------------------------------------------------------------------------
// NSWindow Delegate
// ----------------------------------------------------------------------------------------

- (void)windowDidUpdate:(NSNotification *)aNotification
{
    [[buttonMatrix cellAtRow:0 column:0] setEnabled:[[browser selectedCells] count] > 0];
    [[buttonMatrix cellAtRow:0 column:1] setEnabled:[[keyTextField stringValue] length] > 0];
}

@end
