#import "DWCachedString.h"
#import "DWIconCache.h"
#import "DWMultiLevelCell.h"
#import "DWTableController.h"
#import "DWCustomizableTableView.h"
#import "Profile.h"


@implementation DWTableController

/* Initialization */
    
    - (void) awakeFromNib; {
        [self init];
        [self performSelector: @selector(setTable:) withObject: tableView afterDelay: 0];
    }
    
    - (id) init; {
        self = [super init];
        objects = [[NSMutableArray alloc] init];
        columns = [[NSMutableArray alloc] init];
        return self;
    }
    
    - (void) dealloc; {
        [objects release];
        [columns release];
        [super dealloc];
    }
    

#pragma mark -
/* TableView */

    - (float) rowHeight; {
        return 16;
    }
    
    - (id) columnOrderDefaultsName; {
        return [self className];
    }

    - (id) columnSortDefaultsName; {
        return [self className];
    }
    
    - (void) setTableView: (id) theView; 
    {
        tableView = theView;
        
        if ([[tableView delegate] isKindOfClass: [DWTableController class]]) {
            [[tableView delegate] setTableView: nil];
        }

        if ([[tableView dataSource] isKindOfClass: [DWTableController class]]) {
            [[tableView dataSource] setTableView: nil];
        }
        
        [tableView setDataSource: self];
        [tableView setDelegate: self];
        [tableView sizeToFit];

        [tableView setAutosaveName: [self columnOrderDefaultsName]];

        [tableView setAllowsMultipleSelection: YES];
        [tableView setRowHeight: [self rowHeight]];
        
        [tableView setTarget: self];
        [tableView setDoubleAction: @selector(doubleAction:)];

        [self loadDefaults];
        [tableView noteNumberOfRowsChanged];
    }

    - (void) doubleAction: (id) sender; {
        /* subclasses provide behaviour */
    }


#pragma mark -
/* Sorting */

    
    int defaultTableCompare(DWTableObject* a, DWTableObject* b, id context[]) {
        unsigned sa = a->sequence;
        unsigned sb = b->sequence;
        if (sa > sb) return NSOrderedDescending;
        if (sa < sb) return NSOrderedAscending;
        return NSOrderedSame;
    }

    static inline int compareWithFlags(id va, id vb, int flags)
    {
        if (DWFloatSorted & flags) 
        {
            float a = [va floatValue];
            float b = [vb floatValue];

            if (a<b) return NSOrderedAscending;
            else if (a>b) return NSOrderedDescending;
            else return NSOrderedSame;
        } 
        else if (DWStringSorted & flags) 
        {
            return [(NSString*)va caseInsensitiveCompare: (NSString*)vb];
        } 
        else
        {
            return [(NSString*)va compare: (NSString*)vb];
        }
    }

    int compare(id a, id b, id context[])
    {
        NSComparisonResult result;
        
        DWTableColumn* sortedColumn = (DWTableColumn*)context[0];
        DWTableController* this = (DWTableController*)context[1];

        id va, vb;
        
        /* try the intended column */

        if (sortedColumn->comparator) {
            result = ((int(*)(id,id))sortedColumn->comparator)(a,b);
        } else {
            va = [this sortedValueOfObject: a forColumn: sortedColumn];
            vb = [this sortedValueOfObject: b forColumn: sortedColumn];
            
            result = compareWithFlags(va, vb, sortedColumn->flags);
        }
        
        /* try others */

        if (result == NSOrderedSame) 
        {
            unsigned int flags;
            int i, count = [this->columns count];

            for (i=0; i<count && result == NSOrderedSame; i++) 
            {	
                DWTableColumn* column = [this->columns objectAtIndex: i];
                
                flags = column->flags;

                if (sortedColumn == column) continue;
                if (flags & DWNotSortable)  continue;

                if (column->comparator) {
                    result = ((int(*)(id,id))column->comparator)(a,b);
                } else {
                    va = [this sortedValueOfObject: a forColumn: column];
                    vb = [this sortedValueOfObject: b forColumn: column];
    
                    result = compareWithFlags(va, vb, flags);
                }
            }
        }
        
        if (result == NSOrderedSame) {
            result = defaultTableCompare(a,b,nil);
        }
    
        return result;
    }

    - (void) sort; 
    {
        //[Profile start];
        
        if (sortedColumn) {
            id context[] =  {sortedColumn, self};
            [objects sortUsingFunction: (int(*)(id,id,void*))compare context: context];
        } else {
            [objects sortUsingFunction: (int(*)(id,id,void*))defaultTableCompare context: nil];
        }
        
        //[Profile end];
    }


#pragma mark -
/* Objects */

    - (NSMutableArray*) objects; {
        return objects;
    }

    - (unsigned) count; {
        return [objects count];
    }
    
    - (void) addTableObject: (DWTableObject*) theObject; {
        [objects addObject: theObject];
        theObject->sequence = ++sequence;
    }
    
    - (void) setSequence: (DWTableObject*) theObject; {
        if (!theObject->sequence) theObject->sequence = ++sequence;
    }

    - (void) removeAllObjects; {
        [objects removeAllObjects];
        [tableView noteNumberOfRowsChanged];
    }

    #define objectAtRowIMP						\
        int count = [objects count];					\
        if (index < count) {						\
            if (!sortedColumn || sortedAscending) {			\
                return [objects objectAtIndex: index];			\
            } else {							\
                return [objects objectAtIndex: count - index - 1];	\
            }								\
        } else return nil;
    
    - (id) objectAtRow: (int) index; {
        objectAtRowIMP
    }
    
    static id inline objectAtRow(DWTableController* this, int index) {
        id objects = this->objects;
        id sortedColumn = this->sortedColumn;
        BOOL sortedAscending = this->sortedAscending;
        objectAtRowIMP
    }
    
    - (int) rowOfObject: (id) obj; {
        int index = [objects indexOfObject: obj];
        if (!sortedColumn || sortedAscending || index == NSNotFound) {
            return index;
        } else {
            return [objects count] - index - 1;
        }
    }


#pragma mark -
/* Selections */

    - (NSArray*) selectedObjects; {
        id o, i, e = [tableView selectedRowEnumerator];
        id result = [[[NSMutableArray alloc] init] autorelease];

        while ((i = [e nextObject])) {
            o = [self objectAtRow: [i intValue]];
            if (o)
                [result addObject: o];
        }
        return result;
    }

    - (BOOL) selectObject: (id) obj byExtendingSelection: (BOOL) extend; {
        int index = [self rowOfObject: obj];
        if (index != NSNotFound) {	
            [tableView selectRow: index byExtendingSelection: extend];
            return YES;
        } else return NO;
    }

    - (BOOL) selectObjects: (id) objs byExtendingSelection: (BOOL) extend; {
        id o, e = [objs objectEnumerator];
        BOOL atLeastOneSelected = NO;
        if (!extend) {
            [tableView deselectAll: self];
        }
        while ((o = [e nextObject])) {
            atLeastOneSelected = [self selectObject: o byExtendingSelection: YES] || atLeastOneSelected;
        }
        return atLeastOneSelected;
    }
    
    - (void) saveSelectedObjects; {
        [selected release];
        selected = [[self selectedObjects] retain];
    }
    
    - (void) restoreSelectedObjects; 
    {
        NSEnumerator* objs = [selected objectEnumerator];
        [tableView deselectAll: self];
        
        id obj;
        while ((obj = [objs nextObject])) {
            int current = [self rowOfObject: obj];
            [tableView selectRow: current byExtendingSelection: YES];
        }
        [selected removeAllObjects];
    }


#pragma mark -
/* Defaults */
        
    #define SORTEDCOLUMN [NSString stringWithFormat: @"kDWTableControllerSortedColumn_%@", [self columnSortDefaultsName]]
    #define SORTORDER    [NSString stringWithFormat: @"kDWTableControllerSortOrder_%@", [self columnSortDefaultsName]]

    - (void) loadDefaults; 
    {
        if (shouldSaveSortOrder)
        {
            sortedColumn = (DWTableColumn*)[tableView tableColumnWithIdentifier: [defaults stringForKey: SORTEDCOLUMN]];
            sortedAscending = [defaults boolForKey: SORTORDER];
            
            if (![sortedColumn isKindOfClass: [DWTableColumn class]] || !(sortedColumn->flags & DWNoIndicators)) {
                [tableView setIndicatorImage: [NSImage imageNamed: (sortedAscending) ? @"NSAscendingSortIndicator" : @"NSDescendingSortIndicator"] inTableColumn: sortedColumn];
            }
            [tableView setHighlightedTableColumn: sortedColumn];
        }
    }
    
    - (void) saveDefaults; 
    {
        if (shouldSaveSortOrder) 
        {
            [defaults setObject: [sortedColumn identifier] forKey: SORTEDCOLUMN];
            [defaults setObject: [NSNumber numberWithBool: sortedAscending] forKey: SORTORDER];
        }
    }	


#pragma mark -
/* Public API */

    - (void) addColumn: (DWTableControllerAttributes) attributes;
    {
        DWTableColumn* column = [[DWTableColumn alloc] initWithIdentifier: attributes.identifier];

        [column setTableView: 		tableView];
        [column setFlags: 		attributes.flags];
        [column setMenuTitle: 		attributes.name];
        [column setSelector: 		attributes.sel];
        [column setSortSelector: 	attributes.sortSel];
        [column setPrimary: 		attributes.primary];
        [column setSecondary:	 	attributes.secondary];
        [column setComparator: 		attributes.comparator];
        [column setMinWidth: 		attributes.minWidth];
        [column setMaxWidth: 		attributes.maxWidth];
        [column setResizable: (attributes.minWidth != attributes.maxWidth) ? YES : NO];
        [column setEditable: 		NO];

        if (attributes.cell)
            [column setDataCell: attributes.cell];
        else
            [column setDataCell: [[DWTableCell alloc] init]];

        [[column headerCell] setStringValue: (attributes.shortName) ? attributes.shortName : attributes.name];
        [[column headerCell] setAlignment: (DWCenterAligned & attributes.flags) ? NSCenterTextAlignment : NSLeftTextAlignment];
        [[column dataCell] setWraps: NO];
        [attributes.cell setFont: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
        
        if (DWCenterAligned & attributes.flags) {
            [[column dataCell] setAlignment: NSCenterTextAlignment];
        }

        [columns addObject: column];
    }
    

/* Subclasses */
    
    static id inline valueOfObjectForColumn(id obj, DWTableColumn* column) {
        SEL s = column->sel;
        if (s) {
            return [obj performSelector: s];
        } else {
            return obj;
        }
    }

    - (id) sortedValueOfObject: (id) obj forColumn: (DWTableColumn*) column; {
        SEL s = column->sortSel;
        if (s) {
            return [obj performSelector: s];
        } else {
            return valueOfObjectForColumn(obj, column);
        }
    }
    
    
#pragma mark -
/* NSTableView Data Source */

    - (int) numberOfRowsInTableView: (NSTableView*) aTableView; {
        return [objects count];
    }
    
    - (id) tableView: (id) theTable objectValueForTableColumn: (id) col row: (int) row; {
        id obj = objectAtRow(self, row);
        id result = valueOfObjectForColumn(obj,col);
        if (result) return result;
        else return @"";
    }


/* NSTableView Delegate */

    - (void) clearIndicatorsInTable: (NSTableView*) table; {
        NSArray* cols = [table tableColumns];
        int i, count = [cols count];
        for (i=0; i<count; i++) {
            [table setIndicatorImage: nil inTableColumn: [cols objectAtIndex: i]];
        }
    }

    - (void) tableView: (id) theTable didClickTableColumn: (id) theColumn;
    {
        DWTableColumn* col = (DWTableColumn*) theColumn;
        if (DWNotSortable & col->flags) return;
        
        [self saveSelectedObjects];

        if (col == sortedColumn) {	/* switch sort order */

            if ((!sortedAscending && !(DWSortAscendingFirst & col->flags)) || (sortedAscending && (DWSortAscendingFirst & col->flags))) {

                sortedAscending = !sortedAscending;
                if (!(col->flags & DWNoIndicators))
                    [theTable setIndicatorImage: [NSImage imageNamed: (sortedAscending) ? @"NSAscendingSortIndicator" : @"NSDescendingSortIndicator"] inTableColumn: col];
            
            } else {

                [self clearIndicatorsInTable: theTable];
                [theTable setHighlightedTableColumn: nil];
                sortedColumn = nil;
                [self sort];

            }

        } else {            		/* sort by new column */

            sortedColumn = col;
            if (DWSortAscendingFirst & col->flags) sortedAscending = YES;
            else		                   sortedAscending = NO;

            [self clearIndicatorsInTable: theTable];

            if (!(col->flags & DWNoIndicators)) {
                
                //NSLog(@"%@, %f", NSStringFromSize([[col headerCell] cellSize]), [col width]);
                
                [theTable setIndicatorImage: [NSImage imageNamed: (sortedAscending) ? @"NSAscendingSortIndicator" : @"NSDescendingSortIndicator"] inTableColumn: col];

                //NSLog(@"%@", NSStringFromSize([[col headerCell] cellSize]));
            }
            
            [theTable setHighlightedTableColumn: col];
            [self sort];
        }

        [self restoreSelectedObjects];
        [self saveDefaults];
        [tableView setNeedsDisplay: YES];
    }


/* NSTableView Delegate */
   
    - (void) tableViewColumnDidMove: (id) theNotification; {
        [tableView setNeedsDisplay: YES];
    }

            
/* DWCustomizable Data Source */

    - (NSArray*) defaultColumnsInTableView: (DWCustomizableTableView*) theTable; 
    {
        id result = [[NSMutableArray alloc] init];

        int i, count = [columns count];
        for (i=0; i<count; i++) {
            DWTableColumn* col = (DWTableColumn*)[columns objectAtIndex: i];
            if (DWDefault & col->flags)
                [result addObject: col];
        }

        return [result autorelease];
    }
    
    - (NSArray*) columnsInTableView: (DWCustomizableTableView*) theTable; 
    {
        id result = [[NSMutableArray alloc] init];
        int i, count = [columns count];
        for (i=0; i<count; i++) {
            [result addObject: [columns objectAtIndex: i]];
        }
        return [result autorelease];
    }

    - (BOOL) isColumn: (DWTableColumn*) col requiredInTableView: (DWCustomizableTableView*) theTable; {
        return DWRequired & col->flags;
    }

@end