//=============================================================================
//
//		Copyright (C) 1995-1997 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScrollData.M
//
//		The (DATA) category of MiscTableScroll.	 Implements the management
//		of the cells of the table.
//
// FIXME *SELECTION*
//		The current selection architecture is imposing undesirable overhead
//		on a *highly* used function, -cellAt::.	 We need to come up with a
//		better way to handle this.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScrollData.M,v 1.17 97/06/10 05:39:17 sunshine Exp $
// $Log:		MiscTableScrollData.M,v $
//	Revision 1.17  97/06/10	 05:39:17  sunshine
//	v127: Uses new MiscAssert.
//	
//	Revision 1.16  97/02/05	 07:59:03  sunshine
//	v122: Now takes into account new "tracking" flag to ensure that cells have
//	correct highlight setting in lazy-mode.
//	
//	Revision 1.15  97/01/24	 08:13:40  sunshine
//	v0.119: Fixed bug: Wasn't resetting the "remembered" selection in
//	BorderView or TableView.  Now calls -resetSelection when appropriate.
//-----------------------------------------------------------------------------
#import <misckit/MiscTableScroll.h>
#import <misckit/MiscTableCell.h>
#import "MiscAssert.h"
#import "MiscTableBorder.h"
#import "MiscTableScrollPrivate.h"
extern "Objective-C" {
#import <appkit/Cell.h>
}
extern "C" {
#import <limits.h>
#import <stdlib.h>
#import <string.h>
}

#define EAGER_CELL_AT(R,C)		(cells[ (R) * num_cols + (C) ])

@implementation MiscTableScroll(DATA)

//-----------------------------------------------------------------------------
// - lazyCellAt::
//-----------------------------------------------------------------------------
- (id)lazyCellAt:(int)row :(int)col
	{
	id del = [self responsibleDelegate:MiscDelegateFlags::DEL_CELL_AT];
	if (del != 0)
		{
		id c = [del tableScroll:self cellAt:row :col];
		if (c != 0 && [c respondsTo:@selector(setParameter:to:)])
			[c setParameter:NX_CELLHIGHLIGHTED to:([self tracking] &&
				row == [self clickedRow] && col == [self clickedCol])];
		return c;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - eagerCellAt::
//-----------------------------------------------------------------------------
- (id)eagerCellAt:(int)row :(int)col
	{
	return EAGER_CELL_AT( row, col );
	}


//-----------------------------------------------------------------------------
// - cellAt::
//-----------------------------------------------------------------------------
- (id)cellAt:(int)row :(int)col
	{
	id cell = 0;
	if ((unsigned int) row < (unsigned int) num_rows &&
		(unsigned int) col < (unsigned int) num_cols)
		{
		if (!lazy)
			cell = EAGER_CELL_AT( row, col );
		else
			cell = [self lazyCellAt:row:col];

		if ([cell respondsTo:@selector(setSelected:)])	// FIXME *SELECTION*
			{
			MiscCoord_V const vr = rowInfo.border->physicalToVisual(row);
			MiscCoord_V const vc = colInfo.border->physicalToVisual(col);
			[cell setSelected:colInfo.border->selectionSet().contains(vc) ||
							  rowInfo.border->selectionSet().contains(vr)];
			}
		}
	return cell;
	}


//-----------------------------------------------------------------------------
// tagAt::, intValueAt::, floatValueAt::, doubleValueAt::,
// stringValueAt::, titleAt::, stateAt::
//-----------------------------------------------------------------------------
#define MISC_CELL_VAL( DATA_TYPE, NAME, CMD )\
- (DATA_TYPE) NAME##At:(int)r :(int)c\
	{\
	id del = [self responsibleDelegate:MiscDelegateFlags::DEL_##CMD##_AT];\
	if (del != 0)\
		return [del tableScroll:self NAME##At:r:c];\
	id cell = (!lazy ? EAGER_CELL_AT(r,c) : [self lazyCellAt:r:c]);\
	if (cell != 0 && [cell respondsTo:@selector(NAME)])\
		return [cell NAME];\
	return 0;\
	}

MISC_CELL_VAL( int, tag, TAG )							// tagAt::
MISC_CELL_VAL( int, intValue, INT_VALUE )				// intValueAt::
MISC_CELL_VAL( float, floatValue, FLOAT_VALUE )			// floatValueAt::
MISC_CELL_VAL( double, doubleValue, DOUBLE_VALUE )		// doubleValueAt::
MISC_CELL_VAL( char const*, stringValue, STRING_VALUE ) // stringValueAt::
MISC_CELL_VAL( int, state, STATE )						// stateAt::
MISC_CELL_VAL( char const*, title, TITLE )				// titleAt::
#undef MISC_CELL_VAL


//-----------------------------------------------------------------------------
// - buffCount
//		For lazy-mode tables that perform multiple-buffering.  This can
//		avoid copying when accessing more than one cell at a time (such
//		as during sorting.)
//-----------------------------------------------------------------------------
- (int)buffCount
	{
	id del = [self responsibleDelegate:MiscDelegateFlags::DEL_BUFF_COUNT];
	if (del != 0)
		return [del tableScrollBuffCount:self];
	return 1;
	}


//-----------------------------------------------------------------------------
// - expandIfNeeded
//-----------------------------------------------------------------------------
- (void)expandIfNeeded
	{
	int const num_cells = max_rows * num_cols;
	if (num_cells > max_cells)
		{
		NXZone* const z = [self zone];
		int const nbytes = num_cells * sizeof(*cells);
		if (max_cells == 0)
			cells = (id*) NXZoneMalloc( z, nbytes );
		else
			cells = (id*) NXZoneRealloc( z, cells, nbytes );
		max_cells = num_cells;
		}
	}


//-----------------------------------------------------------------------------
// - extendMaxRows:
//-----------------------------------------------------------------------------
- (void)extendMaxRows:(int)new_max
	{
	int const old_max = max_rows;
	max_rows = new_max;

	[self expandIfNeeded];
	NXZone* const z = [self zone];
	MiscTableBorder* const bp = colInfo.border;
	id* p = cells + old_max * num_cols;
	for (int r = old_max; r < new_max; r++)
		for (int c = 0; c < num_cols; c++)
			*p++ = [bp->getPrototype_P(c) copyFromZone:z];
	}


//-----------------------------------------------------------------------------
// - lazyInsertColAt:
//-----------------------------------------------------------------------------
- (void)lazyInsertColAt:(int)n
	{
	colInfo.border->insertAt(n,n);		// FIXME: Check this.
	num_cols++;
	}


//-----------------------------------------------------------------------------
// - eagerInsertColAt:
//-----------------------------------------------------------------------------
- (void)eagerInsertColAt:(int)n
	{
	MiscAssert( 0 <= n );  MiscAssert( n <= num_cols );
	MiscAssert( 0 < num_cols );			// lazyInsert must increment num_cols.

	if (max_rows > 0 || num_rows > 0)
		{
		if (max_rows == 0)
			max_rows = num_rows;
		[self expandIfNeeded];
		int const num_new = num_cols;
		int const num_old = num_cols - 1;
		if (num_old > 0)				// Shift old, existing cells.
			{
			int const frag = num_old - n; // #cols right of insert col.
			id* src = cells + (max_rows * num_old) - frag;
			id* dst = cells + (max_rows * num_new) - frag;
			if (frag > 0)				// Last partial row.
				memmove( dst, src, frag * sizeof(*dst) );

			int const nbytes = num_old * sizeof(*dst);
			src -= num_old;
			dst -= num_new;
			while (src > cells)			// All full rows.
				{
				memmove( dst, src, nbytes );
				src -= num_old;
				dst -= num_new;
				}
			}
		int row = 0;
		NXZone* const z = [self zone];
		id const proto = colInfo.border->getPrototype_P(n);
		id* const plim = cells + (max_rows * num_new);
		id* p = cells + n;
		while (p < plim)				// Initialize cells in new column.
			{
			*p = [self reviveCell:[proto copyFromZone:z] at:row++ :n];
			p += num_new;
			}
		}
	}


//-----------------------------------------------------------------------------
// - insertColAt:
//-----------------------------------------------------------------------------
- (id)insertColAt:(int)n
	{
	if (0 <= n && n <= num_cols)
		{
		[self lazyInsertColAt:n];
		if (!lazy)
			[self eagerInsertColAt:n];
		[self constrainSize];
		[self resetSelection];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - addCol
//-----------------------------------------------------------------------------
- (id)addCol
	{
	return [self insertColAt: num_cols];
	}


//-----------------------------------------------------------------------------
// - lazyDeleteColAt:
//-----------------------------------------------------------------------------
- (void)lazyDeleteColAt:(int)n
	{
	colInfo.border->deleteAt_P(n);
	num_cols--;
	}


//-----------------------------------------------------------------------------
// - eagerDeleteColAt:
//-----------------------------------------------------------------------------
- (void)eagerDeleteColAt:(int)n
	{
	MiscAssert( 0 < num_cols );
	MiscAssert( 0 <= n );  MiscAssert( n < num_cols );
	if (max_rows > 0)
		{
		int const num_old = num_cols;
		int const num_new = num_cols - 1;
		id* dst = cells + n;
		id* src = dst + 1;
		id* const dst_lim = cells + (num_old * max_rows);
		int row = 0;
		while (dst < dst_lim)			// Destroy deleted cells.
			{
			if (row < num_rows)
				[[self retireCell:*dst at:row++:n] free];
			else
				[*dst free];
			dst += num_old;
			}
		if (num_new > 0)				// Shift remaining columns.
			{
			dst = cells + n;
			int const nbytes = num_new * sizeof(*dst);
			id* const src_lim = cells + (max_rows - 1) * num_old + n;
			while (src < src_lim)
				{
				memmove( dst, src, nbytes );
				dst += num_new;
				src += num_old;
				}
			if (n < num_new)			// Shift last partial row.
				memmove( dst, src, (num_new - n) * sizeof(*dst) );
			}
		else // (num_new <= 0)
			{
			max_rows = 0;
			}
		}
	}


//-----------------------------------------------------------------------------
// - deleteColAt:
//-----------------------------------------------------------------------------
- (id)deleteColAt:(int)n
	{
	if (0 <= n && n < num_cols)
		{
		if (!lazy)
			[self eagerDeleteColAt:n];
		[self lazyDeleteColAt:n];
		[self constrainSize];
		[self resetSelection];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - numCols
//-----------------------------------------------------------------------------
- (int)numCols
	{
	return num_cols;
	}


//-----------------------------------------------------------------------------
// - lazyInsertRowAt:
//-----------------------------------------------------------------------------
- (void)lazyInsertRowAt:(int)n
	{
	rowInfo.border->insertAt(n,n);		// FIXME.  Check this.
	num_rows++;
	}


//-----------------------------------------------------------------------------
// - eagerInsertRowAt:
//-----------------------------------------------------------------------------
- (void)eagerInsertRowAt:(int)n
	{
	MiscAssert( 0 < num_rows );			// lazy-insert must increment num_rows
	MiscAssert( 0 <= n );  MiscAssert( n < num_rows );

	if (num_cols > 0)
		{
		max_rows++;								// Incremental growth.
		MiscAssert( num_rows <= max_rows );
		[self expandIfNeeded];

		int const old_max = max_rows - 1;
		id* p = cells + n * num_cols;
		if (n < old_max)						// Shift other rows.
			{
			id* src = p;
			id* dst = src + num_cols;
			memmove( dst, src, (old_max - n) * num_cols * sizeof(*dst) );
			}
		NXZone* const z = [self zone];
		MiscTableBorder* const bp = colInfo.border;
		for (int c = 0;	 c < num_cols;	c++)	// Initialize new cells.
			*p++ = [self reviveCell:[bp->getPrototype_P(c) copyFromZone:z]
								 at:n:c];
		}
	}


//-----------------------------------------------------------------------------
// - insertRowAt:
//-----------------------------------------------------------------------------
- (id)insertRowAt:(int)n
	{
	if (0 <= n && n <= num_rows)
		{
		[self lazyInsertRowAt:n];
		if (!lazy)
			[self eagerInsertRowAt:n];
		[self constrainSize];
		[self resetSelection];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - eagerAddRow
//-----------------------------------------------------------------------------
- (void)eagerAddRow
	{
	MiscAssert( 0 < num_rows ); // lazy insertRowAt: must increment num_rows.
	if (num_cols > 0)
		{
		if (num_rows >= max_rows)
			{
			MiscAssert( num_rows < (INT_MAX >> 1) );
			int n = (max_rows != 0 ? max_rows : 16);
			while (n < num_rows)
				n <<= 1;
			[self extendMaxRows:n];
			}
		int const r = num_rows - 1;
		id* p = cells + (num_cols * r);
		for (int c = 0; c < num_cols; c++)
			*p++ = [self reviveCell:*p at:r:c];
		}
	}


//-----------------------------------------------------------------------------
// - addRow
//
// NOTE: We explicitly do NOT -constrainSize and -update here, because we
// expect that the user will call this routine many times successively.
//-----------------------------------------------------------------------------
- (id)addRow
	{
	[self lazyInsertRowAt:num_rows];
	if (!lazy)
		[self eagerAddRow];
	return self;
	}


//-----------------------------------------------------------------------------
// - lazyDeleteRowAt:
//-----------------------------------------------------------------------------
- (void)lazyDeleteRowAt:(int)n
	{
	rowInfo.border->deleteAt_P(n);
	num_rows--;
	}


//-----------------------------------------------------------------------------
// - eagerDeleteRowAt:
//-----------------------------------------------------------------------------
- (void)eagerDeleteRowAt:(int)n
	{
	MiscAssert( 0 < num_rows );
	MiscAssert( 0 <= n );  MiscAssert( n < num_rows );
	if (num_cols > 0)
		{
		MiscAssert( num_rows <= max_rows );
		max_rows--;
		id* const dst = cells + (n * num_cols );
		id* p = dst;
		id* const plim = p + num_cols;
		int col = 0;
		while (p < plim)		// Destroy the cells.
			{
			[[self retireCell:*p at:n:col++] free];
			p++;
			}
		if (n < max_rows)
			memmove( dst, plim, (max_rows - n) * num_cols * sizeof(*dst) );
		}
	}


//-----------------------------------------------------------------------------
// - deleteRowAt:
//-----------------------------------------------------------------------------
- (id)deleteRowAt:(int)n
	{
	if (0 <= n && n < num_rows)
		{
		if (!lazy)
			[self eagerDeleteRowAt:n];
		[self lazyDeleteRowAt:n];
		[self constrainSize];
		[self resetSelection];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - numRows
//-----------------------------------------------------------------------------
- (int)numRows
	{
	return num_rows;
	}


//-----------------------------------------------------------------------------
// - lazyRenewRows:
//-----------------------------------------------------------------------------
- (void)lazyRenewRows:(int)n
	{
	rowInfo.border->setCount(n);
	num_rows = n;
	}


//-----------------------------------------------------------------------------
// - eagerRenewRows:
//-----------------------------------------------------------------------------
- (void)eagerRenewRows:(int)n
	{
	MiscAssert( 0 <= n );
	if (num_cols > 0)
		{
		int const old_num_rows = num_rows;
		int const new_num_rows = n;
		if (n > max_rows)
			[self extendMaxRows:n];
		if (old_num_rows < new_num_rows)				// Growing
			{
			id* p = cells + old_num_rows * num_cols;
			for (int r = old_num_rows; r < new_num_rows; r++)
				for (int c = 0; c < num_cols; c++)
					*p++ = [self reviveCell:*p at:r:c];
			}
		else if (old_num_rows > new_num_rows)			// Shrinking
			{
			id* p = cells + new_num_rows * num_cols;
			for (int r = new_num_rows; r < old_num_rows; r++)
				for (int c = 0; c < num_cols; c++)
					*p++ = [self retireCell:*p at:r:c];
			}
		}
	}


//-----------------------------------------------------------------------------
// - renewRows:
//-----------------------------------------------------------------------------
- (id)renewRows:(int)n
	{
	[self clearSelection];
	if (0 <= n && n != num_rows)
		{
		if (!lazy)
			[self eagerRenewRows:n];
		[self lazyRenewRows:n];
		}
	[self constrainSize];
	[self update];
	return self;
	}


//-----------------------------------------------------------------------------
// - empty
//-----------------------------------------------------------------------------
- (id)empty
	{
	return [self renewRows:0];
	}


//-----------------------------------------------------------------------------
// - freeCells
//-----------------------------------------------------------------------------
- (void)freeCells
	{
	if (max_rows > 0 && num_cols > 0)
		{
		id* p = cells;
		id* const plim = p + (max_rows * num_cols);
		while (p < plim)
			[*p++ free];
		}
	if (cells != 0)
		NXZoneFree( [self zone], cells );
	cells = 0;
	max_cells = 0;
	max_rows = 0;
	}


//-----------------------------------------------------------------------------
// - emptyAndFreeCells
//-----------------------------------------------------------------------------
- (id)emptyAndFreeCells
	{
	[self empty];
	[self freeCells];
	return self;
	}


//-----------------------------------------------------------------------------
// - setLazy:
//-----------------------------------------------------------------------------
- (void)setLazy:(BOOL)flag
	{
	if (lazy != flag)
		{
		lazy = flag;
		if (lazy)
			[self emptyAndFreeCells];
		else
			[self eagerRenewRows:num_rows];
		}
	}


//-----------------------------------------------------------------------------
// - isLazy
//-----------------------------------------------------------------------------
- (BOOL)isLazy
	{
	return lazy;
	}


//-----------------------------------------------------------------------------
// Generic Slot methods
//-----------------------------------------------------------------------------
- (id)addSlot:(MiscBorderType)b
		{ return (b == MISC_COL_BORDER) ? [self addCol] : [self addRow]; }

- (id)border:(MiscBorderType)b insertSlotAt:(int)n
		{ return (b == MISC_COL_BORDER) ?
				[self insertColAt:n] : [self insertRowAt:n]; }

- (id)border:(MiscBorderType)b deleteSlotAt:(int)n
		{ return (b == MISC_COL_BORDER) ?
				[self deleteColAt:n] : [self deleteRowAt:n]; }

- (int)numSlots:(MiscBorderType)b
		{ return (b == MISC_COL_BORDER) ? [self numCols] : [self numRows]; }


//-----------------------------------------------------------------------------
// REVIVE
//-----------------------------------------------------------------------------
- (id)doReviveCell:(id)cell at:(int)row:(int)col
	{
	if (cell != 0)
		{
		if ([cell respondsTo:@selector(setOwner:)])
			[cell setOwner:self];

		if ([cell respondsTo:@selector(setUseOwnerFont:)])
			[cell setUseOwnerFont:YES];
		if ([cell respondsTo:@selector(setUseOwnerTextColor:)])
			[cell setUseOwnerTextColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerBackgroundColor:)])
			[cell setUseOwnerBackgroundColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerHighlightTextColor:)])
			[cell setUseOwnerHighlightTextColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerHighlightBackgroundColor:)])
			[cell setUseOwnerHighlightBackgroundColor:YES];

		if ([cell respondsTo:@selector(setOwnerFont:)])
			[cell setOwnerFont:[self font]];
		else if ([cell respondsTo:@selector(setFont:)])
			[cell setFont:[self font]];

		if ([cell respondsTo:@selector(setOwnerTextColor:)])
			[cell setOwnerTextColor:[self textColor]];
		else if ([cell respondsTo:@selector(setTextColor:)])
			[cell setTextColor:[self textColor]];

		if ([cell respondsTo:@selector(setOwnerBackgroundColor:)])
			[cell setOwnerBackgroundColor:[self backgroundColor]];
		else if ([cell respondsTo:@selector(setBackgroundColor:)])
			[cell setBackgroundColor:[self backgroundColor]];

		if ([cell respondsTo:@selector(setOwnerHighlightTextColor:)])
			[cell setOwnerHighlightTextColor:[self highlightTextColor]];
		else if ([cell respondsTo:@selector(setHighlightTextColor:)])
			[cell setHighlightTextColor:[self highlightTextColor]];

		if ([cell respondsTo:@selector(setOwnerHighlightBackgroundColor:)])
			[cell setOwnerHighlightBackgroundColor:
										[self highlightBackgroundColor]];
		else if ([cell respondsTo:@selector(setHighlightBackgroundColor:)])
			[cell setHighlightBackgroundColor:[self highlightBackgroundColor]];
		}
	return cell;
	}


- (id)reviveCell:(id)cell at:(int)row :(int)col
	{
	id del = [self responsibleDelegate:MiscDelegateFlags::DEL_REVIVE_CELL];
	if (del != 0)
		return [del tableScroll:self reviveCell:cell at:row :col];

	if (cell != 0 && [cell respondsTo:@selector(tableScroll:reviveAt: :)])
		return [cell tableScroll:self reviveAt:row :col];

	return [self doReviveCell:cell at:row :col];
	}


//-----------------------------------------------------------------------------
// RETIRE
//-----------------------------------------------------------------------------
- (id)doRetireCell:(id)cell at:(int)row:(int)col
	{
	if ([cell respondsTo:@selector(setTitle:)])
		[cell setTitle:0];
	else if ([cell respondsTo:@selector(setStringValueNoCopy:)])
		[cell setStringValueNoCopy:0];
	else if ([cell respondsTo:@selector(setStringValue:)])
		[cell setStringValue:0];
	return cell;
	}


- (id)retireCell:(id)cell at:(int)row:(int)col
	{
	id del = [self responsibleDelegate:MiscDelegateFlags::DEL_RETIRE_CELL];
	if (del != 0)
		return [del tableScroll:self retireCell:cell at:row :col];

	if (cell != 0 && [cell respondsTo:@selector(tableScroll:retireAt: :)])
		return [cell tableScroll:self retireAt:row :col];

	return [self doRetireCell:cell at:row :col];
	}

@end
