//
//  FieldTableManager.m
//  longspear
//
//  Created by Niko Matsakis on Sat Jun 12 2004.
//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.
//

#import "FieldTableManager.h"
#import "Actions.h"
#import "Config.h"
#import "NSArrayExtensions.h"

@implementation FieldTableManager

- initWithColumns:(NSString*)columns
             data:data
      NSTableView:(NSTableView*)view
         readOnly:(BOOL)ro
           notify:notify
         delegate:delegate
{
    if ( (self = [super init]) ) {
        _columnSet = [columns retain];
        _data = [data retain];
        _readOnly = ro;
        
        // use weak refs to avoid cycles:
        _delegate = delegate;
        _view = view;
        
        // create a private drag type since 
        // we don't allow dragging between tables:
        _dragType = [[NSString stringWithFormat:@"LongSpear-FTM-%p", self] retain];
        
        [_view setDataSource:self];
        [_view setDelegate:self];
        if (!_readOnly) [_view registerForDraggedTypes:[NSArray arrayWithObject:_dragType]];
        [self createColumns];
        [self dataChanged:self];
        
        // Add ourselves as an observer of the array that get our data from.
        // If it changes, we want to indicate that our data is obsolete.
        [NotificationCenter() addObserver:self
                                 selector:@selector(dataChanged:)
                                     name:kCHANGED
                                   object:_data];
                                   
        [NotificationCenter() addObserver:self
                                 selector:@selector(configChanged:)
                                     name:kCONFIGCHANGED
                                   object:nil];            

        // Also observe the class of entities we are governing.
        if (notify) {
            [NotificationCenter() addObserver:self
                                     selector:@selector(dataChanged:)
                                         name:kCHANGED
                                       object:notify];
        }
    }
    return self;
}

- (void) dealloc
{
    [NotificationCenter() removeObserver:self];
    [_columnSet release];
    [_dragType release];
    [_columnSet release];
    [_data release];
    free (_columnMaxWidths);
    [super dealloc];
}

/* adjusts the columns on the table to match those in the
given column set configuration.  does not set widths to anything
reasonable or anything like that, just creates columns with
the right headers and titles.   The widths will be adjusted
by other methods. */
- (void) createColumns
{
    NSArray *cset;
    NSEnumerator *enumerator;
    NSTableColumn *column;
    NSString *colname;

    /* remove existing columns */
    enumerator = [[[_view tableColumns] copy] objectEnumerator];
    while ((column = [enumerator nextObject]))
        [_view removeTableColumn:column];
    
    cset = ColumnSet (_columnSet);
    enumerator = [cset objectEnumerator];
    while ((colname = [enumerator nextObject])) {
        column = [[[NSTableColumn alloc] initWithIdentifier:colname] autorelease];
        [[column headerCell] setStringValue:colname];
        [column setMinWidth:0];
        [column setMaxWidth:1000000];
        [column setEditable:!_readOnly];
        [_view addTableColumn:column];
    }
}

- (void) configChanged:object
{
    [self createColumns];
    [self dataChanged:object];
}

- (void) dataChanged:object
{
    /* called when a row is added or deleted */
    
    // Compute the max widths of each column
    // from scratch since we don't know what
    // changed precisely.
    [self computeColumnMaxWidths];
    
    // if the data changed, we may need to adjust the 
    // widths of the columns and the like
    [self adjustColumnWidths];
    
    // also our display is out of date
    [_view reloadData];    
}

/* AUTOSIZING OF COLUMNS */

/* This routine walks over the data and computes for each column
the maximum amount of space any item in the data could take.  
This is useful when sizing the data because we don't need to give
more space than that. We generally compute this information from
scratch each time the data array changes but otherwise maintain it
as the table is edited. */
- (void) computeColumnMaxWidths
{
    NSArray *cset;
    int ccnt;
    NSEnumerator *enumerator;
    Entity *entity;
    int i;
    
    cset = ColumnSet (_columnSet);
    ccnt = [cset count];
    
    assert (ccnt > 0);
    
    free (_columnMaxWidths);
    _columnMaxWidths = malloc (sizeof (float)*ccnt);

    for (i = 0; i < ccnt; i++) {
        // initialize the max widths with the width of the title
        NSArray *columns = [_view tableColumns];
        NSTableColumn *col = [columns objectAtIndex:i];
        NSCell *cell = [col headerCell];
        NSFont *font = [cell font];
        _columnMaxWidths[i] = [font widthOfString:[cell stringValue]];
    }
    
    enumerator = [_data objectEnumerator];
    while ((entity = [enumerator nextObject])) {
        for (i = 0; i < ccnt; i++) {
            NSString *text = [entity fieldAsString:[cset objectAtIndex:i]];
            NSTableColumn *col = [[_view tableColumns] objectAtIndex:i];
            NSFont *font = [[col dataCell] font];
            float width = [font widthOfString:text]; // deprecated but so convenient!
            if (width > _columnMaxWidths[i])
                _columnMaxWidths[i] = width;
        }
    }
    
    _totalSpace = 0;
    for (i = 0; i < ccnt; i++) {
        _totalSpace += _columnMaxWidths[i];
    }
}

- (void) adjustColumnWidths
{
    NSRect bounds;
    float equalshare = 0;
    NSTableColumn *column=nil;
    float *colwidths, *proport;
    int i, ccnt;
    
    /* get list of column names */
    ccnt = [[_view tableColumns] count];
    bounds = [[_view superview] frame];
    bounds.size.width -= 10; // fudge factor to account for scrollbar
    equalshare = bounds.size.width / ccnt;
    
    /* start by giving each column maximum space it could take */
    colwidths = (float*) malloc (sizeof (float) * ccnt);
    proport = (float*) malloc (sizeof (float) * ccnt);
    for (i = 0; i < ccnt; i++) {
        colwidths[i] = _columnMaxWidths[i];
        proport[i] = colwidths[i] / _totalSpace;
    }
        
    /* if we are too wide, then each column will be penalized proprotionate to its
        total width */
    float extra = _totalSpace - bounds.size.width;
    if (extra > 0) {
        for (i = 0; i < ccnt; i++) {
            float adjust = extra * proport[i];
            colwidths[i] -= adjust;
        }
    }
    else if (extra < 0) {
        extra = -extra;
        for (i = 0; i < ccnt; i++) {
            float adjust = extra * proport[i];
            colwidths[i] += adjust;
        }
    }
    
    /* finally assign the columns a width */
    for (i = 0; i < ccnt; i++) {
        column = [[_view tableColumns] objectAtIndex:i];
        [column setWidth:colwidths[i]];
    }
    
    free (colwidths);
}

/* Simple Data Source Routines */
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    assert (aTableView == _view);
    return [_data count];
}

- (id)tableView:(NSTableView *)aTableView 
objectValueForTableColumn:(NSTableColumn *)aTableColumn 
            row:(int)rowIndex;
{
    assert (aTableView == _view);
    id field = [aTableColumn identifier];
    id ent = [_data objectAtIndex:rowIndex];
    NSString *res = [ent fieldAsString:field];
    
    if ([_delegate respondsToSelector:@selector(fieldTableManager:attributizeValue:forRow:field:font:object:)]) {
        NSFont *font = [[aTableColumn dataCell] font];
        return [_delegate fieldTableManager:self
                           attributizeValue:res
                                     forRow:rowIndex
                                      field:field
                                       font:font
                                     object:ent];
    }
    
    return res;
}

- (void)tableView:(NSTableView *)aTableView 
   setObjectValue:(id)anObject 
   forTableColumn:(NSTableColumn *)aTableColumn 
              row:(int)rowIndex
{
    assert (aTableView == _view);
    id field = [aTableColumn identifier];
    id ent = [_data objectAtIndex:rowIndex];    
    [Action setFieldNamed:field 
                       to:anObject
                       on:ent
                   target:_delegate];
}

    /* Drag-n-Drop */
    
// This is called when a drag operation begins
- (BOOL) tableView: (NSTableView *) tableView 
         writeRows: (NSArray*) rows 
      toPasteboard: (NSPasteboard*) pboard 
{
    NSLog (@"writing rows to pboard under drag type %@", _dragType);
    
    [pboard declareTypes:[NSArray arrayWithObjects:_dragType, nil] 
                   owner:self];
    
    [pboard setData:[NSArchiver archivedDataWithRootObject:[rows asIndexSet]]
            forType:_dragType];
    
    return YES;
}

- (NSDragOperation) tableView: (NSTableView*) tableView 
                 validateDrop: (id <NSDraggingInfo>) info 
                  proposedRow: (int) row 
        proposedDropOperation: (NSTableViewDropOperation) operation 
{
    assert (tableView == _view);
    NSLog (@"validating drop");
    // allows drags (for now) only within the battlestatus table
    if ([[[info draggingPasteboard] types] containsObject:_dragType]) {
        return NSDragOperationMove;
    }
    return NSDragOperationNone;
}

- (BOOL) tableView: (NSTableView*) tableView 
        acceptDrop: (id <NSDraggingInfo>) info 
               row: (int) row 
     dropOperation: (NSTableViewDropOperation) operation 
{
    if ([[[info draggingPasteboard] types] containsObject:_dragType]) {
        // Extract the rows that were selected.
        NSIndexSet *rows = [NSUnarchiver unarchiveObjectWithData:
            [[info draggingPasteboard] dataForType:_dragType]];
            
        // Get the list of objects we're moving; iterate down the
        // list and remove them, then add them back at their new
        // index. Note that we must adjust the destination if we just
        // removed the object from somewhere in front of its new destination.
        NSArray *extracted = [_data objectsAtIndices:rows];
        NSEnumerator *extractedenum = [extracted objectEnumerator];
        id creature;
        while ((creature = [extractedenum nextObject])) {
            int oldidx = [_data indexOfObject:creature];
            [Action removeObject:creature from:_data target:_delegate];
            if (oldidx < row)
                row --;
            [Action addObject:creature to:_data atIndex:row target:_delegate];
        }
        
        // Notify our delegate (if they care) about the objects we moved about
        if ([_delegate respondsToSelector:@selector(fieldTableManager:draggedObjects:toIndex:)]) {
            [_delegate fieldTableManager:self
                          draggedObjects:extracted 
                                 toIndex:row];
        }
        
        // Ensure that the dragged rows are selected, cause otherwise it is
        // quite strange
        [_view selectRow:row byExtendingSelection:NO];
        int i;
        for (i = 1; i < [extracted count]; i++) {
            [_view selectRow:row+i byExtendingSelection:YES];
        }
            
        return YES;
    }
    return NO;
}

- (NSArray*) entityList
{
    return _data;
}
    
- (void) setEntityList:(NSArray*)data
{
    // stop listening for the old data before we release it:
    [NotificationCenter() removeObserver:self name:kCHANGED object:_data];

    [_data release];
    _data = [data retain];
    [self dataChanged:nil];
}

- selectedItem
{
    int selrow = [_view selectedRow];
    if (selrow >= 0) 
        return [_data objectAtIndex:selrow];
    return nil;
}

- (NSArray*) selectedItems  /* returns all selected items */
{
    NSIndexSet *selset = [_view selectedRowIndexes];
    return [_data objectsAtIndices:selset];
}


@end
