/** <title>NSTableHeaderView</title>

   Copyright (C) 1999 Free Software Foundation, Inc.

   Author: Nicola Pero <n.pero@mi.flashnet.it>
   Date: December 1999
   First actual coding.

   Author: Nicola Pero <nicola@brainstorm.co.uk>
   Date: August 2000, Semptember 2000
   Selection and resizing of Columns.

   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; see the file COPYING.LIB.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/

#import <Foundation/NSArray.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSUserDefaults.h>
#import "AppKit/NSTableHeaderCell.h"
#import "AppKit/NSTableHeaderView.h"
#import "AppKit/NSTableColumn.h"
#import "AppKit/NSTableView.h"
#import "AppKit/NSWindow.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSApplication.h"
#import "AppKit/NSColor.h"
#import "AppKit/NSScrollView.h"
#import "AppKit/NSGraphics.h"
#import "GSGuiPrivate.h"
#import "GNUstepGUI/GSTheme.h"

/*
 * Number of pixels in either direction that will be counted as a hit 
 * on the column border and trigger a column resize.
 */
#define mouse_sensitivity 4

@interface NSTableView (GNUstepPrivate)
- (void) _userResizedTableColumn: (int)index
                           width: (float)width;
- (float *) _columnOrigins;
- (void) _mouseDownInHeaderOfTableColumn: (NSTableColumn *)tc;
- (void) _clickTableColumn: (NSTableColumn *)tc;
@end

@implementation NSTableHeaderView

/*
 *
 * Class methods
 *
 */
+ (void) initialize
{
  if (self == [NSTableColumn class])
    [self setVersion: 1];
}

/*
 *
 * Instance methods
 *
 */

/*
 * Initializes an instance
 */

// TODO: Remove this method, if not really needed
- (id)initWithFrame:(NSRect)frameRect
{
  self = [super initWithFrame: frameRect];
  if (self == nil)
      return nil;

  _tableView = nil;
  _resizedColumn = -1;
  return self;
}

- (BOOL) isFlipped
{
  return YES;
}

/*
 * Setting the table view 
 */
- (void)setTableView: (NSTableView*)aTableView
{
  // We do not RETAIN aTableView but aTableView is supposed 
  // to RETAIN us.
  _tableView = aTableView;

}
- (NSTableView*)tableView
{
  return _tableView;
}
/*
 * Checking altered columns 
 */
- (int) draggedColumn
{
  // TODO
  return -1;
}
- (float) draggedDistance
{
  // TODO
  return -1;
}
- (int) resizedColumn
{
  return _resizedColumn;
}
/*
 * Utility methods 
 */
- (int) columnAtPoint: (NSPoint)aPoint
{
  if (_tableView == nil)
    return -1;

  /* Ask to the tableview, which is caching geometry info */
  aPoint = [self convertPoint: aPoint toView: _tableView];
  aPoint.y = [_tableView bounds].origin.y;
  return [_tableView columnAtPoint: aPoint];
}

- (NSRect)headerRectOfColumn: (int)columnIndex
{
  NSRect rect;

  if (_tableView == nil)
    return NSZeroRect;

  /* Ask to the tableview, which is caching geometry info */
  rect = [_tableView rectOfColumn: columnIndex];
  rect = [self convertRect: rect fromView: _tableView];
  rect.origin.y = _bounds.origin.y;
  rect.size.height = _bounds.size.height;
  
  return rect;
}

/*
 * Overidden Methods
 */
- (void)drawRect: (NSRect)aRect
{
  [[GSTheme theme] drawTableHeaderRect: aRect
		   inView: self];
}

/**
 * In -mouseDown we intercept the mouse event to handle the
 * colum resize and rearrangement. Resizing or moving columns
 * will do a live resize/move of the columns by default. Users can revert to
 * a "ghost" resize/move indicator by doing:
 * defaults write NSGlobalDomain GSUseGhostResize YES
 */
- (void) mouseDown: (NSEvent*)event
{
  NSPoint location = [event locationInWindow];
  int clickCount;
  int columnIndex;
  NSTableColumn *currentColumn;

  clickCount = [event clickCount];

  /*  
  if (clickCount > 2)
    {
      return;
    }
  */  

  location = [self convertPoint: location fromView: nil];
  columnIndex = [self columnAtPoint: location];
  
  if (columnIndex == -1)
    {
      return;  
    }
  currentColumn = [[_tableView tableColumns]
                    objectAtIndex: columnIndex];


  if (clickCount == 2)
    {
      [_tableView _sendDoubleActionForColumn: columnIndex];
      //      return;
    }

  //  if (clickCount == 1)
    {
      NSRect rect = [self headerRectOfColumn: columnIndex];

      /* Safety check */
      if (_resizedColumn != -1)
        {
          NSLog(@"Bug: starting resizing of column while already resizing!");
          _resizedColumn = -1;
        }
      
      if ([_tableView allowsColumnResizing])
        {
          /* Start resizing if the mouse is down on the bounds of a column. */
          if (location.x >= NSMaxX(rect) - mouse_sensitivity)
            {
              if (columnIndex < [_tableView numberOfColumns])
                {
                  _resizedColumn = columnIndex;
                }
              else
                {
                  NSLog(@"Bug: Trying to resize column past the end of the table.");
                }
            }
          else if (location.x <= NSMinX(rect) + mouse_sensitivity) 
            {
              if (columnIndex > 0)
                {
                  _resizedColumn = columnIndex - 1;
                }
            }
        }

      /* Resizing */
      if (_resizedColumn != -1)
        {
          float p;
          NSEvent *e;
          BOOL lit;
          unsigned int eventMask;
          BOOL liveResize;

          /* Width of the highlighted area. */
          const float divWidth = 4;
          /* Coordinates of visible part of table */
          float minVisCoord = NSMinX([self visibleRect]);
          float maxVisCoord = NSMaxX([self visibleRect]);
          
          NSPoint unconverted = [event locationInWindow];
          NSArray *columns = [_tableView tableColumns];
          /* Column on the left of resizing bound */
          NSTableColumn *column = [columns objectAtIndex: _resizedColumn];
          const float columnMinX = NSMinX([self headerRectOfColumn: _resizedColumn]);
          const float columnMinWidth = [column minWidth];
          const float columnMaxWidth = [column maxWidth];
          float newColumnWidth = [column width];
          float newColumnMaxX;
          NSRect oldHighlightRect;
          NSRect highlightRect = [self visibleRect];
          highlightRect.size.width = divWidth;
          
          /* Mouse position */
          /* YES if some highlighting was done and needs to be undone */
          lit = NO;
          eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask;
          liveResize = ![[NSUserDefaults standardUserDefaults] boolForKey: @"GSUseGhostResize"];
          
          if ([column isResizable] == NO)
            {
              _resizedColumn = -1;
              return;
            }

          /* Do we need to check that we already fit into this area ? 
             We should */

          if (!liveResize)
            {
              oldHighlightRect = NSZeroRect;
              [self lockFocus];
              [[NSColor lightGrayColor] set];
            }
            
          [[NSRunLoop currentRunLoop] limitDateForMode: NSEventTrackingRunLoopMode];
          
          [NSEvent startPeriodicEventsAfterDelay: 0.05 withPeriod: 0.05];
          e = [NSApp nextEventMatchingMask: eventMask
                     untilDate: [NSDate distantFuture]
                     inMode: NSEventTrackingRunLoopMode
                     dequeue: YES];

          while ([e type] != NSLeftMouseUp)
            {
              if ([e type] != NSPeriodic)
                {
                  unconverted = [e locationInWindow];
                }
                  
              p = [self convertPoint: unconverted fromView: nil].x;        
              minVisCoord = NSMinX([self visibleRect]);
              maxVisCoord = NSMaxX([self visibleRect]);
                
              /* newColumnWidth is always positive, since we always resize
                 the column to the left of the mouse pointer */
              newColumnWidth = (p - columnMinX);
              newColumnWidth = MAX(MIN(newColumnWidth, columnMaxWidth), columnMinWidth);
              newColumnMaxX = columnMinX + newColumnWidth;
              
              if (liveResize && [column width] != newColumnWidth)
                {
                  [_tableView _userResizedTableColumn: _resizedColumn
                              width: newColumnWidth];
                }
              else if (!liveResize)
                {
                  highlightRect.origin.x = newColumnMaxX;

                  /* Only draw the divider if it is not the same as the currently
                     drawn one, and lies within the header view area */
                  if (!NSEqualRects(oldHighlightRect,highlightRect) && 
                       highlightRect.origin.x > [self visibleRect].origin.x &&
                       highlightRect.origin.x + divWidth < [self visibleRect].origin.x + [self visibleRect].size.width)
                    {
                      if (lit)
                        {
                          NSHighlightRect(oldHighlightRect);
                        }
                      NSHighlightRect(highlightRect);
                      [_window flushWindow];                         
                      lit = YES;
                      oldHighlightRect = highlightRect;
                    }
                }

              /* Scroll the tableview, if needed, so the user's desired new 
                 column edge position lies at the edge of the visible part of 
                 the table */
              if ((p > maxVisCoord && newColumnMaxX > maxVisCoord)
                || (p < minVisCoord && newColumnMaxX < minVisCoord))
                {
                  NSRect tvRect = [_tableView visibleRect];   
              
                  if (!liveResize && lit)
                    {
                      NSHighlightRect(oldHighlightRect);
                      lit = NO;
                      [_window flushWindow]; 
                    }
                  
                  if (p > maxVisCoord) /* resizing to the right */
                    tvRect.origin.x = newColumnMaxX - tvRect.size.width;
                  else                /* resizing to the left */
                    tvRect.origin.x = newColumnMaxX;
                  
                  [_tableView scrollPoint: tvRect.origin];
                }
                    
              e = [NSApp nextEventMatchingMask: eventMask
                         untilDate: [NSDate distantFuture]
                         inMode: NSEventTrackingRunLoopMode
                         dequeue: YES];
            }
          [NSEvent stopPeriodicEvents];

          if (!liveResize)
            {
              if (lit)
                {
                  NSHighlightRect(oldHighlightRect);
                  [_window flushWindow];
                }
              [self unlockFocus];
              
              /* The following tiles the table.  We use a private method 
                 which avoids tiling the table twice. */
              if ([column width] != newColumnWidth)
                {
                  [_tableView _userResizedTableColumn: _resizedColumn
                              width: newColumnWidth];
                }
            }

          /* Clean up */
          _resizedColumn = -1;
          return;
        }

      /* We are not resizing
         Let's launch a mouseDownInHeaderOfTableColumn message
      */
      {
        NSRect rect = [self headerRectOfColumn: columnIndex];
        [_tableView _mouseDownInHeaderOfTableColumn: 
                      [[_tableView tableColumns] 
                        objectAtIndex: columnIndex]];
        rect.origin.y++;
        rect.size.height--;
        [[currentColumn headerCell] setHighlighted: YES];

        [self lockFocus];
        [[currentColumn headerCell]
          highlight: YES
          withFrame: rect
          inView: self];
        [self unlockFocus];
        [_window flushWindow];
      }


      /* Dragging */
      /* Wait for mouse dragged events. 
         If mouse is dragged, move the column.
         If mouse is not dragged but released, select/deselect the column. */
      if ([_tableView allowsColumnReordering])
        {
          int i = columnIndex;
          int j = columnIndex;
          float minCoord; 
          float maxCoord; 
          float minVisCoord;
          float maxVisCoord;
          float *_cO;
          float *_cO_minus1;
          int numberOfColumns = [_tableView numberOfColumns];
          unsigned int eventMask = (NSLeftMouseUpMask 
                                    | NSLeftMouseDraggedMask 
                                    | NSPeriodicMask);
          unsigned int modifiers = [event modifierFlags];
          NSEvent *e;
          NSDate *distantFuture = [NSDate distantFuture];
          NSRect visibleRect = [self visibleRect];
          NSRect tvRect;
          NSRect highlightRect = NSZeroRect, oldRect = NSZeroRect;
          BOOL outside = NO;
          BOOL lit = NO;
          BOOL liveResize = ![[NSUserDefaults standardUserDefaults] boolForKey: @"GSUseGhostResize"];
          
          BOOL mouseDragged = NO;
          float p;
          NSPoint unconverted;
          minVisCoord = NSMinX (visibleRect);
          maxVisCoord = NSMaxX (visibleRect);
          {
            NSRect bounds = [self bounds];
            minCoord = NSMinX(bounds);
            maxCoord = NSMaxX(bounds);
          }
          {
            float *_c = [_tableView _columnOrigins];
            _cO_minus1 = malloc((numberOfColumns + 3) * sizeof(float));
            _cO = _cO_minus1 + 1;
            memcpy(_cO, _c, numberOfColumns * sizeof(float));
            _cO[numberOfColumns] = maxCoord;
            _cO[numberOfColumns + 1] = maxCoord;
            _cO[-1] = minCoord;
          }

          highlightRect.size.height = NSHeight (visibleRect);
          highlightRect.origin.y = NSMinY (visibleRect);

          if (!liveResize)
            {
              [self lockFocus];
              [[NSColor lightGrayColor] set];
            }
          [NSEvent startPeriodicEventsAfterDelay: 0.05
                   withPeriod: 0.05];
          e = [NSApp nextEventMatchingMask: eventMask 
                     untilDate: distantFuture
                     inMode: NSEventTrackingRunLoopMode 
                     dequeue: YES];

          while ([e type] != NSLeftMouseUp)
            {
              switch ([e type])
                {
                case NSLeftMouseDragged:
                  unconverted = [e locationInWindow];
                  p = [self convertPoint: unconverted fromView: nil].x;
                  if (mouseDragged == NO)
                    {
                      NSLog(@"TODO: Deselect the column");
                    }
                  mouseDragged = YES;
                  if (p < minVisCoord || p > maxVisCoord)
                    {
                      outside = YES;
                    }
                  else
                    {
                      outside = NO;
                      i = j;
                      if (p > (_cO[i] + _cO[i+1]) / 2)
                        {
                          while (p > (_cO[i] + _cO[i+1]) / 2)
                            i++;
                        }
                      else if (p < (_cO[i] + _cO[i-1]) / 2)
                        {
                          while (p < (_cO[i] + _cO[i-1]) / 2)
                            i--;
                        }
                      if (!liveResize)
                        {  
                          if (i != columnIndex
                              && i != columnIndex + 1)
                            {
                              j = i;
                              highlightRect.size.height = NSHeight (visibleRect);
                              highlightRect.origin.y = NSMinY (visibleRect);
                              highlightRect.size.width = 7;
                              if (i == numberOfColumns)
                                {
                                  highlightRect.origin.x = _cO[i] - 3;
                                }
                              else if (i == 0)
                                {
                                  highlightRect.origin.x = _cO[i] - 3;
                                }
                              else
                                {
                                  highlightRect.origin.x = _cO[i] - 3;
                                }
                              if (!NSEqualRects(highlightRect, oldRect))
                                {
                                  if (lit)
                                    NSHighlightRect(oldRect);
                                  NSHighlightRect(highlightRect);
                                  [_window flushWindow];
                                }
                              else if (!lit)
                                {
                                  NSHighlightRect(highlightRect);
                                  [_window flushWindow];
                                }
                              oldRect = highlightRect;
                              lit = YES;
                            }
                          else
                            {
                              i = columnIndex;
                              highlightRect.size.height = NSHeight (visibleRect);
                              highlightRect.origin.y = NSMinY (visibleRect);
                              highlightRect.origin.x = _cO[columnIndex];
                              highlightRect.size.width = 
                                _cO[columnIndex + 1] - _cO[columnIndex];
                            
                              if (!NSEqualRects(highlightRect, oldRect))
                                {
                                  if (lit)
                                    NSHighlightRect(oldRect);
                                  //  NSHighlightRect(highlightRect);
                                  [_window flushWindow];
                                }
                              else if (!lit)
                                {
                                  //  NSHighlightRect(highlightRect);
                                  // [_window flushWindow];
                                }
                              // oldRect = highlightRect;
                              oldRect = NSZeroRect;
                              lit = NO; //lit = YES;
                            }
                         }
                       else if (liveResize)
                         {
                           if (i > columnIndex)
                             i--;
                           if (i != columnIndex)
                             {
                               [_tableView moveColumn: columnIndex
                                           toColumn: i];
                             }  
                           columnIndex = i;
                         }
                    }
                  break;
                case NSPeriodic:
                  if (outside == YES)
                    {
                      if (!liveResize)
                        {
                          if (lit)
                            {
                              NSHighlightRect(oldRect);
                              [_window flushWindow];
                              lit = NO;
                              oldRect = NSZeroRect;
                            }
                        }
                      p = [self convertPoint: unconverted
                                fromView: nil].x;
                      tvRect = [_tableView visibleRect];
                      if (p > maxVisCoord)
                        {
                          if (p > maxCoord)
                            tvRect.origin.x += (p - maxVisCoord)/8;
                          else
                            tvRect.origin.x += (p - maxVisCoord)/2;
                        }
                      else if (p < minVisCoord)
                        {
                          if (p < minCoord)
                            tvRect.origin.x += (p - minVisCoord)/8;
                          else
                            tvRect.origin.x += (p - minVisCoord)/2;
                        }
                      else // TODO remove this condition
                        {
                          NSLog(@"not outside !");
                        }
                      [_tableView scrollPoint: tvRect.origin];
                      visibleRect = [self visibleRect];
                      minVisCoord = NSMinX (visibleRect);
                      maxVisCoord = NSMaxX (visibleRect);
                    }
                  break;
                default:
                  break;
                }
              e = [NSApp nextEventMatchingMask: eventMask 
                         untilDate: distantFuture
                         inMode: NSEventTrackingRunLoopMode 
                         dequeue: YES]; 
            }
            
          if (!liveResize)
            {
              if (lit)
                {
                  NSHighlightRect(highlightRect);
                  [_window flushWindow];
                }
              [self unlockFocus];
            }

          [NSEvent stopPeriodicEvents];       
            
          if (mouseDragged == NO)
            {
              [_tableView _selectColumn: columnIndex modifiers: modifiers];
              [_tableView _clickTableColumn: currentColumn];

              [self setNeedsDisplay: YES];;
            }
          else // mouseDragged == YES
            {
              {
                NSRect rect = [self headerRectOfColumn: columnIndex];
                [_tableView _mouseDownInHeaderOfTableColumn: 
                              [[_tableView tableColumns] 
                                objectAtIndex: columnIndex]];
                rect.origin.y++;
                rect.size.height--;
                [[currentColumn headerCell]
                  setHighlighted: NO];
                
                [self lockFocus];
                [[currentColumn headerCell] 
                  highlight: NO
                  withFrame: rect
                  inView: self];
                [self unlockFocus];
                [_window flushWindow];
              }
              if (i > columnIndex)
                i--;
              if (i != columnIndex)
                {
                  [_tableView moveColumn: columnIndex
                              toColumn: i];
                }
            }
          free(_cO_minus1);
          return;
        }
      else
        {
          NSRect cellFrame = [self headerRectOfColumn: columnIndex];
          NSApplication *theApp = [NSApplication sharedApplication];
          unsigned int modifiers = [event modifierFlags];
          NSPoint location = [event locationInWindow];
          NSPoint point = [self convertPoint: location fromView: nil];

          if (![self mouse: point inRect: cellFrame])
            {
              NSLog(@"not in frame, what's happening ?");
              return;
            }

          event = [theApp nextEventMatchingMask: NSLeftMouseUpMask
                          untilDate: [NSDate distantFuture]
                          inMode: NSEventTrackingRunLoopMode
                          dequeue: NO];
          

          location = [event locationInWindow];
          
          point = [self convertPoint: location fromView: nil];
          
          if (![self mouse: point inRect: cellFrame])
            {
              NSDebugLLog(@"NSCell", 
                          @"tableheaderview point not in cell frame\n");
              {
                NSRect rect = [self headerRectOfColumn: columnIndex];
                [_tableView _mouseDownInHeaderOfTableColumn: 
                              [[_tableView tableColumns] 
                                objectAtIndex: columnIndex]];
                rect.origin.y++;
                rect.size.height--;
                [[currentColumn headerCell]
                  setHighlighted: NO];
                
                [self lockFocus];
                [[currentColumn headerCell] 
                  highlight: NO
                  withFrame: rect
                  inView: self];
                [self unlockFocus];
                [_window flushWindow];
              }

            }
          else
            {
              [_tableView _selectColumn: columnIndex modifiers: modifiers];
              [_tableView _clickTableColumn: currentColumn];

              [self setNeedsDisplay: YES];
              /*              
              if ([_tableView highlightedTableColumn] != currentColumn)
                {
                  NSRect rect = [self headerRectOfColumn: columnIndex];
                  
                  // [_tableView _mouseDownInHeaderOfTableColumn: 
                  // [[_tableView tableColumns] 
                  // objectAtIndex: columnIndex]];

                  rect.origin.y++;
                  rect.size.height--;
                  NSLog(@"highlight");
                  [[currentColumn headerCell] setHighlighted: NO];
                  
                  [[currentColumn headerCell] 
                    highlight: NO
                    withFrame: rect
                    inView: self];
                  [_window flushWindow];
                }
              */
            }
        }
    }
}

/*
 * Encoding/Decoding
 */

- (void) encodeWithCoder: (NSCoder*)aCoder
{
  [super encodeWithCoder: aCoder];

  /* Nothing else to encode in NSTableHeaderView:
       - _tableView is set by the parent NSTableView
       - _resizedColumn is reset on decoding anyway
     */
}

- (id) initWithCoder: (NSCoder*)aDecoder
{
  self = [super initWithCoder: aDecoder];
  if (self == nil)
      return nil;

  _tableView = nil;
  _resizedColumn = -1;

  return self;
}

@end

