// MOMatrix.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.

// ABOUT MOKit
// by Mike Ferris (mike@lorax.com)
//
// MOKit is a collection of useful and general objects.  Permission is 
// granted by the author to use MOKit in your own programs in any way 
// you see fit.  All other rights pertaining to the kit are reserved by the 
// author including the right to sell these objects as objects,  as part 
// of a LIBRARY, or as SOURCE CODE.  In plain English, I wish to retain 
// rights to these objects as objects, but allow the use of the objects 
// as pieces in a fully functional program.  Permission is also granted to 
// redistribute the source code of MOKit for FREE as long as this copyright 
// notice is left intact and unchanged.  NO WARRANTY is expressed or implied.  
// The author will under no circumstances be held responsible for ANY 
// consequences from the use of these objects.  Since you don't have to pay 
// for them, and full source is provided, I think this is perfectly fair.

#import "MOKit/MOMatrix.h"
#import <objc/objc-runtime.h>

#define CLASS_VERSION 		0
#define CLASS_NAME			"MOMatrix"

@implementation MOMatrix

+ initialize
// Set the class version
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];
	}
	return self;
}

- setupStorage:(int)rowsHigh :(int)colsWide
// Set up our storage objects
{
	int i;
	ColumnSize newCSize;
	RowSize newRSize;
	
	columnSizes = [[Storage allocFromZone:[self zone]] initCount:0 
				elementSize:sizeof(ColumnSize) description:COLUMNSIZE_DESC];
	rowSizes = [[Storage allocFromZone:[self zone]] initCount:0 
				elementSize:sizeof(RowSize) description:ROWSIZE_DESC];
	
	newCSize.width = cellSize.width;
	newRSize.height = cellSize.height;
	for (i=0; i<colsWide; i++)  {
		newCSize.x = bounds.origin.x + (i*cellSize.width) + 
					(i*intercell.width);
		[columnSizes addElement:&newCSize];
	}
	for (i=0; i<rowsHigh; i++)  {
		newRSize.y = bounds.origin.y + (i*cellSize.height) + 
					(i*intercell.height);
		[rowSizes addElement:&newRSize];
	}
	
	return self;
}

- initFrame:(const NXRect *)frm mode:(int)aMode prototype:cellId 
			numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix.  Sets up our storage stuff.
{
	[super initFrame:frm mode:aMode prototype:cellId numRows:numRows 
				numCols:numCols];
	
	[self setupStorage:rowsHigh :colsWide];
	
	return self;
}

- initFrame:(const NXRect *)frm mode:(int)aMode cellClass:factoryId 
			numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix.  Sets up our storage stuff.
{
	[super initFrame:frm mode:aMode cellClass:factoryId numRows:numRows 
				numCols:numCols];
	
	[self setupStorage:rowsHigh :colsWide];
	
	return self;
}

- MO_copyRowSizes:rs andColSizes:cs zone:(NXZone *)zone
{
	rowSizes = [rs copyFromZone:zone];
	columnSizes = [cs copyFromZone:zone];
	return self;
}

- copyFromZone:(NXZone *)zone
{
	id obj = [super copyFromZone:zone];
	[obj MO_copyRowSizes:rowSizes andColSizes:columnSizes zone:zone];
	return obj;
}

- free
// free the storage
{
	[columnSizes free];
	[rowSizes free];
	return [super free];
}

- MO_moveColumnsRightBy:(NXCoord)difference startingAt:(int)col
// A private method used by the methods which cause sizing stuff to change
{
	int i;

	if ((col < 0) || (col >= numCols))  {
		return nil;
	}

	for (i=col; i<numCols; i++)  {
		ColumnSize *cSize;
		cSize = (ColumnSize *)[columnSizes elementAt:i];
		if (cSize)  cSize->x += difference;
	}
	return self;
}

- MO_moveRowsDownBy:(NXCoord)difference startingAt:(int)row
// A private method used by the methods which cause sizing stuff to change
{
	int i;
	
	if ((row < 0) || (row >= numRows))  {
		return nil;
	}

	for (i=row; i<numRows; i++)  {
		RowSize *rSize;
		rSize = (RowSize *)[rowSizes elementAt:i];
		if (rSize)  rSize->y += difference;
	}
	return self;
}

- setWidth:(NXCoord)newWidth ofCol:(int)col
// This method allows the setting of column widths
{
	NXCoord diff;
	ColumnSize *cSize;
	
	if ((col < 0) || (col >= numCols))  {
		return nil;
	}

	cSize = (ColumnSize *)[columnSizes elementAt:col];
	diff = newWidth - cSize->width;
	cSize->width = newWidth;
	[self MO_moveColumnsRightBy:diff startingAt:col+1];
	
	return self;
}

- setHeight:(NXCoord)newHeight ofRow:(int)row
// This method allows the setting of row heights
{
	NXCoord diff;
	RowSize *rSize;
	
	if ((row < 0) || (row >= numRows))  {
		return nil;
	}

	rSize = (RowSize *)[rowSizes elementAt:row];
	diff = newHeight - rSize->height;
	rSize->height = newHeight;
	[self MO_moveRowsDownBy:diff startingAt:row+1];
		
	return self;
}

- sizeToCells
// Resize the matrix to the proper size to fit all our cells.
{
	NXRect rect;
	ColumnSize *cSize;
	RowSize *rSize;
	
	[self getFrame:&rect];
	
	if (numCols == 0)  {
		rect.size.width = 0.0;
	}  else  {
		cSize = (ColumnSize *)[columnSizes MO_lastElement];
		rect.size.width = cSize->x + cSize->width - bounds.origin.x;
	}

	if (numRows == 0)  {
		rect.size.height = 0.0;
	}  else  {
		rSize = (RowSize *)[rowSizes MO_lastElement];
		rect.size.height = rSize->y + rSize->height - bounds.origin.y;
	}
	
	[self setFrame:&rect];

	return self;
}

- renewRows:(int)newRows cols:(int)newCols
// Makes sure to keep our storage objects in synch with everything else.
{
	ColumnSize newCSize, *cSize;
	RowSize newRSize, *rSize;
	int i;
	
	// Remove any storage elements past the new number of cols
	for (i=numCols-1; i>=newCols; i--)  {
		[columnSizes removeLastElement];
	}
	// Add any needed new storage elements to get up to the new number of cols
	for (i=numCols; i<newCols; i++)  {
		if (i==0)  {
			newCSize.x = bounds.origin.x;
		}  else  {
			cSize = (ColumnSize *)[columnSizes MO_lastElement];
			newCSize.x = cSize->x + cSize->width + intercell.width;
		}
		newCSize.width = cellSize.width;
		[columnSizes addElement:&newCSize];
	}

	// Remove any storage elements past the new number of rows
	for (i=numRows-1; i>=newRows; i++)  {
		[rowSizes removeLastElement];
	}
	// Add any needed new storage elements to get up to the new number of rows
	for (i=numRows; i<newRows; i++)  {
		if (i==0)  {
			newRSize.y = bounds.origin.y;
		}  else  {
			rSize = (RowSize *)[rowSizes MO_lastElement];
			newRSize.y = rSize->y + rSize->height + intercell.height;
		}
		newRSize.height = cellSize.height;
		[rowSizes addElement:&newRSize];
	}
	
	[super renewRows:newRows cols:newCols];
	
	return self;
}

- addCol
// Keep the storage in synch
{
	ColumnSize newCSize, *cSize;
	
	[super addCol];

	if (numCols > 0)  {
		cSize = (ColumnSize *)[columnSizes MO_lastElement];
		newCSize.x = cSize->x + cSize->width + intercell.width;
	}  else  {
		newCSize.x = bounds.origin.x;
	}
	newCSize.width = cellSize.width;
	[columnSizes addElement:&newCSize];
	
	return self;
}

- addRow
// Keep the storage in synch
{
	RowSize newRSize, *rSize;
	
	[super addRow];

	if (numRows > 0)  {
		rSize = (RowSize *)[rowSizes MO_lastElement];
		newRSize.y = rSize->y + rSize->height + intercell.height;
	}  else  {
		newRSize.y = bounds.origin.y;
	}
	newRSize.height = cellSize.height;
	[rowSizes addElement:&newRSize];
	
	return self;
}

- insertColAt:(int)col
// Keep the storage in synch
{
	ColumnSize newCSize, *cSize;
	
	if (col < 0)  {
		return nil;
	}
	
	if (col <= numCols)  {
		[super insertColAt:col];

		if (col > 0)  {
			cSize = (ColumnSize *)[columnSizes elementAt:col];
			newCSize.x = cSize->x;
		}  else  {
			newCSize.x = bounds.origin.x;
		}
		newCSize.width = cellSize.width;
		[columnSizes insertElement:&newCSize at:col];
		
		[self MO_moveColumnsRightBy:newCSize.width + intercell.width 
					startingAt:col+1];
	}  else  {
		return nil;
	}
	return self;
}

- insertRowAt:(int)row
// Keep the storage in synch
{
	RowSize newRSize, *rSize;
	
	if (row < 0)  {
		return nil;
	}
	
	if (row <= numRows)  {
		[super insertRowAt:row];

		if (row > 0)  {
			rSize = (RowSize *)[rowSizes elementAt:row];
			newRSize.y = rSize->y;
		}  else  {
			newRSize.y = bounds.origin.y;
		}
		newRSize.height = cellSize.height;
		[rowSizes insertElement:&newRSize at:row+1];
		
		[self MO_moveRowsDownBy:newRSize.height + intercell.height 
					startingAt:row+1];
	}  else  {
		return nil;
	}
	return self;
}

- removeColAt:(int)col andFree:(BOOL)flag
// Keep the storage in synch
{
	ColumnSize *cSize;
	NXCoord diff;
	
	if ((col >= numCols) || (col < 0))  {
		return nil;
	}
	
	[super removeColAt:col andFree:flag];

	cSize = (ColumnSize *)[columnSizes elementAt:col];
	diff = cSize->width;
	[columnSizes removeElementAt:col];
	[self MO_moveColumnsRightBy:0.0 - diff - intercell.width startingAt:col];
	
	return self;
}

- removeRowAt:(int)row andFree:(BOOL)flag
// Keep the storage in synch
{
	RowSize *rSize;
	NXCoord diff;
	
	if ((row >= numRows) || (row < 0))  {
		return nil;
	}

	[super removeRowAt:row andFree:flag];

	rSize = (RowSize *)[rowSizes elementAt:row];
	diff = rSize->height;
	[rowSizes removeElementAt:row];
	[self MO_moveRowsDownBy:0.0 - diff - intercell.height startingAt:row];
	
	return self;
}

- drawSelf:(const NXRect *)rects:(int)rectCount
// We do our own drawing because we need to draw our cells in diverse 
// rectangles
{
	int i, j;
	NXRect cFrm;
	
	[window disableFlushWindow];
	
	// the background (if any)
	if (backgroundGray != -1.0)  {
		PSsetgray(backgroundGray);
		if (rectCount==1)  {
			NXRectFill(&(rects[0]));
		}  else  {
			NXRectFill(&(rects[1]));
			NXRectFill(&(rects[2]));
		}
	}
	
	// The cells
	for (i=0; i<numRows; i++)  {
		for (j=0; j<numCols; j++)  {
			[self getCellFrame:&cFrm at:i:j];
			if (rectCount == 1)  {
				if (NXIntersectsRect(&(rects[0]), &cFrm))  {
					[self drawCellAt:i:j];
				}
			}  else  {
				if ((NXIntersectsRect(&(rects[1]), &cFrm)) || 
							(NXIntersectsRect(&(rects[2]), &cFrm)))  {
					[self drawCellAt:i:j];
				}
			}
		}
	}

	[window reenableFlushWindow];
	[window flushWindow];
	
	return self;
}

- getCellFrame:(NXRect *)theRect at:(int)row:(int)col
// Calculate and return the rect used to display the cell at the given
// row and column
{
	ColumnSize *cSize;
	RowSize *rSize;
	
	if (col < numCols)  {
		cSize = (ColumnSize *)[columnSizes elementAt:col];
		theRect->origin.x = cSize->x;
		theRect->size.width = cSize->width;
	}  else  {
		int num = col - numCols;
		
		cSize = (ColumnSize *)[columnSizes MO_lastElement];
		theRect->origin.x = cSize->x +
					(num * (cellSize.width + intercell.width));
		theRect->size.width = cellSize.width;
	}
	
	if (row < numRows)  {
		rSize = (RowSize *)[rowSizes elementAt:row];
		theRect->origin.y = rSize->y;
		theRect->size.height = rSize->height;
	}  else  {
		int num = row - numRows;
		
		rSize = (RowSize *)[rowSizes MO_lastElement];
		theRect->origin.y = rSize->y +
					(num * (cellSize.height + intercell.height));
		theRect->size.height = cellSize.height;
	}
	
	return self;
}

- getRow:(int *)row andCol:(int *)col forPoint:(const NXPoint *)aPoint
// Calculate the row and column of the cell which contains the given point
{
	ColumnSize *cSize;
	RowSize *rSize;
	int i;
	
	*row = -1;
	*col = -1;
	if ((aPoint->x < bounds.origin.x) || 
				(aPoint->x > bounds.origin.x + bounds.size.width) || 
				(aPoint->y < bounds.origin.y) || 
				(aPoint->y > bounds.origin.y + bounds.size.height))  {
		return self;
	}
	for (i=0; i<numCols; i++)  {
		cSize = (ColumnSize *)[columnSizes elementAt:i];
		if ((aPoint->x >= cSize->x) && 
					(aPoint->x <= cSize->x + cSize->width))  {
			*col = i;
			break;
		}
	}
	for (i=0; i<numRows; i++)  {
		rSize = (RowSize *)[rowSizes elementAt:i];
		if ((aPoint->y >= rSize->y) && 
					(aPoint->y <= rSize->y + rSize->height))  {
			*row = i;
			break;
		}
	}
	return self;
}

- setIntercell:(const NXSize *)aSize
// Keep the storage in synch
{
	NXCoord xDiff = aSize->width - intercell.width;
	NXCoord yDiff = aSize->height - intercell.height;
	RowSize *rSize;
	ColumnSize *cSize;
	int i;
	
	for (i=1; i<numRows; i++)  {
		rSize = (RowSize *)[rowSizes elementAt:i];
		rSize->y += (yDiff * i);
	}
	for (i=1; i<numCols; i++)  {
		cSize = (ColumnSize *)[columnSizes elementAt:i];
		cSize->x += (xDiff * i);
	}
	
	return [super setIntercell:aSize];
}


- write:(NXTypedStream *)typedStream
// Write our ivars
{
	[super write:typedStream];
	NXWriteObject(typedStream, columnSizes);
	NXWriteObject(typedStream, rowSizes);
	return self;
}

- read:(NXTypedStream *)typedStream
// Read our ivars
{
	int classVersion;

	[super read:typedStream];
	
	classVersion = NXTypedStreamClassVersion(typedStream, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			columnSizes = NXReadObject(typedStream);
			rowSizes = NXReadObject(typedStream);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			[self setupStorage:numRows :numCols];
			break;
	}
	return self;
}


// ********************Overridden private methods***********************
// *****************that I'm going to hell for using********************

// These methods are used by Matrix's mouseDown:.  Doing the whole 
// mouseDown: method over would have been a royal pain in the butt, 
// so I cheated.


- (BOOL)_mouseHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}

- (BOOL)_loopHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}

- (BOOL)_radioHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}


@end

@implementation Storage(MOLastElementCategory)
	
- (void *)MO_lastElement
// A little shortcut
{
	return [self elementAt:numElements-1];
}
	
@end

