//
//  NJRTableDelegate.m
//  Pester
//
//  Created by Nicholas Riley on Sun Oct 27 2002.
//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
//

#import "NJRTableDelegate.h"
#import "NSTableView-NJRExtensions.h"

#pragma mark sorting support

typedef struct { NSString *key; BOOL descending; } SortContext;

// Sort array of itemNums, by looking up the itemNum in the dictionary of objects.
// based on code of Ondra Cada <ocs@ocs.cz> on cocoa-dev list

int ORDER_BY_CONTEXT(id left, id right, void *ctxt) {
    SortContext *context = (SortContext *)ctxt;
    int order = 0;
    id key = context->key;
    if (0 != key) {
        id first, second;	// the actual objects to compare

        if (context->descending) {
            first  = [right valueForKey: key];
            second = [left  valueForKey: key];
        } else {
            first  = [left  valueForKey: key];
            second = [right valueForKey: key];
        }

        if ([first respondsToSelector: @selector(caseInsensitiveCompare:)]) {
            order = [first caseInsensitiveCompare:second];
        } else { // sort numbers or dates
            order = [(NSNumber *)first compare:second];
        }
    }
    return order;
}

@interface NJRTableDelegate (Private)

- (void)_positionTypeSelectDisplay;
- (void)_sortByColumn:(NSTableColumn *)inTableColumn;

@end

@implementation NJRTableDelegate

#pragma mark initialize-release

- (void)awakeFromNib;
{
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_positionTypeSelectDisplay) name: NSViewFrameDidChangeNotification object: tableView];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [sortingColumn release];
    [sortingKey release];
    [reorderedData release];
    [super dealloc];
}

#pragma mark accessing

- (void)setSortingColumn:(NSTableColumn *)inNewValue;
{
    [inNewValue retain];
    [sortingColumn release];
    sortingColumn = inNewValue;
}

- (void)setSortingKey:(NSString *)inNewValue;
{
    [inNewValue retain];
    [sortingKey release];
    sortingKey = inNewValue;
}

#pragma mark sorting

- (NSString *)_sortContextDefaultKey;
{
    NSString *autosaveName = [tableView autosaveName];
    if (autosaveName != nil)
        return [NSString stringWithFormat: @"NJRTableDelegate SortContext %@", autosaveName];
    else
        return nil;
}

- (void)_sortData;
{
    SortContext ctxt = { sortingKey, sortDescending };
    NSString *sortContextKey = [self _sortContextDefaultKey];

    if (sortContextKey != nil) {
        [[NSUserDefaults standardUserDefaults] setObject:
            [NSDictionary dictionaryWithObjectsAndKeys: sortingKey, @"sortingKey", [NSNumber numberWithBool: sortDescending], @"sortDescending", nil]
                                                    forKey: [self _sortContextDefaultKey]];
    }
    
    // sort the NSMutableArray
    [reorderedData sortUsingFunction: ORDER_BY_CONTEXT context: &ctxt];
    [tableView reloadData];
}

- (void)_sortByColumn:(NSTableColumn *)inTableColumn;
{
    NSSet *oldSelection = [self selectedItems];
    if (sortingColumn == inTableColumn) {
        // User clicked same column, change sort order
        sortDescending = !sortDescending;
        // Possible optimization: Don't actually re-sort if you just change the sorting direction; instead, just display either the nth item or the (count-1-n)th item depending on ascending/descending.)
    } else {
        // User clicked new column, change old/new column headers, save new sorting column, and re-sort the array.
        if (sortingColumn != nil) {
            [tableView setIndicatorImage: nil inTableColumn: sortingColumn];
            sortDescending = NO; // on initial sort, preserve previous sort order
        }
        [self setSortingKey: [inTableColumn identifier]];
        [self setSortingColumn: inTableColumn];
        [tableView setHighlightedTableColumn: inTableColumn];
    }
    [tableView setIndicatorImage: (sortDescending ? [NSTableView descendingSortIndicator] : [NSTableView ascendingSortIndicator]) inTableColumn: inTableColumn];
    [self _positionTypeSelectDisplay];
    // Actually sort the data
    [self _sortData];
    [self selectItems: oldSelection];
}

- (void)_initialSortData;
{
    NSString *sortContextKey = [self _sortContextDefaultKey];
    NSDictionary *sortContext;
    NSString *key;
    NSTableColumn *column;

    if (sortContextKey == nil) goto noContext;
    if ( (sortContext = [[NSUserDefaults standardUserDefaults] dictionaryForKey: sortContextKey]) == nil) goto noContext;
    if ( (key = [sortContext objectForKey: @"sortingKey"]) == nil) goto noContext;
    if ( (column = [tableView tableColumnWithIdentifier: key]) == nil) goto noContext;
    sortDescending = [[sortContext objectForKey: @"sortDescending"] boolValue];
    [self _sortByColumn: column];
    return;
    
noContext:
    sortDescending = NO;
    [self _sortByColumn: [[tableView tableColumns] objectAtIndex: 0]];
}

- (NSMutableArray *)reorderedDataForData:(NSArray *)data;
{
    if (reorderedData == nil) {
        reorderedData = [data mutableCopy];
        [self _initialSortData];
    } else {
        NSSet *oldSelection = [self selectedItems];
        [reorderedData release]; reorderedData = nil;
        reorderedData = [data mutableCopy];
        [self _sortData];
        [self selectItems: oldSelection];
    }
    return reorderedData;
}

#pragma mark type selection

- (void)_positionTypeSelectDisplay;
{
    [tableView resetTypeSelect]; // avoid extraneous matching
    if ([tableView typeSelectDisplay] != nil && sortingColumn != nil) {
        NSControl *typeSelectControl = [tableView typeSelectDisplay];
        if ([typeSelectControl isKindOfClass: [NSControl class]]) {
            NSView *superview = [typeSelectControl superview];
            NSRect columnRect = [superview convertRect: [tableView rectOfColumn: [tableView columnWithIdentifier: sortingKey]] fromView: tableView];
            // XXX support horizontal scroll bar/clipping (not for Pester, but eventually)
            NSRect tableScrollFrame = [[tableView enclosingScrollView] frame];
            NSRect selectFrame = [typeSelectControl frame];
            [superview setNeedsDisplayInRect: selectFrame]; // fix artifacts caused by moving view
            selectFrame.origin.x = columnRect.origin.x;
            selectFrame.size.width = columnRect.size.width;
            [typeSelectControl setAlignment: [[sortingColumn dataCell] alignment]];
            [typeSelectControl setFrame: selectFrame];
        }
    }
}

#pragma mark saving/restoring selection

- (NSSet *)selectedItems;
{
    NSMutableSet *result = [NSMutableSet set];
    NSEnumerator *e = [tableView selectedRowEnumerator];
    NSNumber *rowNum;

    while ( (rowNum = [e nextObject]) != nil) {
        id item = [reorderedData objectAtIndex: [rowNum intValue]];
        [result addObject: item];
    }
    return result;
}

- (void)selectItems:(NSSet *)inSelectedItems;
{
    NSEnumerator *e = [inSelectedItems objectEnumerator];
    id item;
    int savedLastRow = 0;

    [tableView deselectAll: nil];

    while ( (item = [e nextObject]) != nil ) {
        int row = [reorderedData indexOfObjectIdenticalTo: item];
        if (row != NSNotFound) {
            [tableView selectRow: row byExtendingSelection: YES];
            savedLastRow = row;
        }
    }
    [tableView scrollRowToVisible: savedLastRow];
}

@end

@implementation NJRTableDelegate (NJRTableViewDelegate)

- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)inTableColumn
{
    [[tableView window] makeFirstResponder: aTableView];
    [self _sortByColumn: inTableColumn];
}

- (void)tableViewColumnDidResize:(NSNotification *)notification;
{
    [self _positionTypeSelectDisplay];
}

- (void)tableViewColumnDidMove:(NSNotification *)notification;
{
    [self _positionTypeSelectDisplay];
}

- (void)tableView:(NSTableView *)aTableView selectRowMatchingString:(NSString *)matchString;
{
    // Look for a highlighted column, presuming we are sorted by that column, and search its values.
    NSTableColumn *col = [aTableView highlightedTableColumn];
    id dataSource = [aTableView dataSource];
    int i, rowCount = [reorderedData count];
    if (nil == col) return;
    if (sortDescending) {
        for ( i = rowCount - 1 ; i >= 0 ; i-- ) {
            NSComparisonResult order = [matchString caseInsensitiveCompare:
                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
            if (order != NSOrderedDescending) break;
        }
        if (i < 0) i = 0;
    } else {
        for ( i = 0 ; i < rowCount ; i++ ) {
            NSComparisonResult order = [matchString caseInsensitiveCompare:
                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
            if (order != NSOrderedDescending) break;
        }
        if (i >= rowCount) i = rowCount - 1;
    }
    // Now select row i -- either the one we found, or the first/last row if not found.
    [aTableView selectRow: i byExtendingSelection: NO];
    [aTableView scrollRowToVisible: i];
}

@end