/* MiscTreeBrowser
 *
 */

// IMPORTS

#import <ansi/assert.h>

#import <appkit/Text.h>
#import <appkit/publicWraps.h>	// NXBeep()

#import <misckit/MiscTreeBrowser.h>
#import <misckit/MiscTreeBrowserCell.h>
#import <misckit/MiscTreeBrowserMatrix.h>
#import <misckit/MiscTreeBrowserProtocols.h>


// DEFINES

#define DEFAULT_BG_RED	0.80079
#define DEFAULT_BG_GRN	0.73438
#define DEFAULT_BG_BLU	0.66797

#define DEFAULT_BG_COLOR	\
	NXConvertRGBToColor(DEFAULT_BG_RED, DEFAULT_BG_GRN, DEFAULT_BG_BLU)


// CLASS VARIABLES

static NXColor defaultBackgroundColor;


// IMPLEMENTATION

@implementation MiscTreeBrowser

/*"
To Be Described

"*/
// PRIVATE CLASS METHODS

+ initialize
/*"
Initialises the class; sets the default background color for instances.
FUTURE: perhaps should set default cell icons as well.
"*/
{
	if ([MiscTreeBrowser class] == self)
	{
		defaultBackgroundColor = DEFAULT_BG_COLOR;
	}

	return self;
}

// PRIVATE METHODS

- (BOOL)_node:(id<MiscTreeBrowserNodes>)aNode
  isDescendantOf:(id<MiscTreeBrowserNodes>)parentNode
/*"
Returns YES if parentNode == [...[aNode parent]... parent] or parentNode is aNode.
"*/
{
	id node = aNode;

	while (node)
	{
		if (node == parentNode)
		{
			return YES;
		}
		node = [node parent];
	}

	return NO;
}

- (int)_indexOfNode:aNode
/*
Returns index of cell containing aNode, or -1 if none does.
*/
{
	unsigned int row, cellCount = [cellMatrix cellCount];

	for (row = 0; row < cellCount; row++)
	{
		id cell = [cellMatrix cellAt:row :0];

		if ([cell node] == aNode)
		{
			return row;
		}
	}

	return -1;					// no cells contain aNode
}

- _cellForNode:aNode
/*"
Returns the cell containing aNode (if any), otherwise returns nil.
"*/
{
	int idx = [self _indexOfNode:aNode];

	return (idx >= 0) ? [cellMatrix cellAt:idx :0] : nil;
}

- _insertCellAt:(unsigned int)index withNode:aNode
/*"
Inserts a cell into the matrix at index, containing node aNode.  Does not call -sizeToCells or -display to redisplay the matrix, allowing rapid insertion of more than one cell e.g. opening a node with children.  The cell is closed and unselected.  Returns the new cell.
"*/
{
	unsigned int cellCount = [cellMatrix cellCount];
	id cell;

	if (index > cellCount)
	{							// if cell beyond end of matrix, change
		index = cellCount;		// to just at end of the matrix
	}
	[cellMatrix endEditing];
								// add cell to matrix
	[cellMatrix insertRowAt:index];
	cell = [cellMatrix cellAt:index :0];
								// initialise cell
	[cell setState:0];
	[cell setOpen:NO];
	[cell setNode:aNode];

	return self;
}

- _removeCellAt:(unsigned int)index
/*"
Removes the cell at index.  Does nothing to the node it contains.  Does not call -sizeToCells or -display to redisplay the matrix, allowing rapid deletion of more than one cell e.g. closing a node with children
"*/
{
	[[cellMatrix cellAt:index :0] setNode:nil];
	[cellMatrix removeRowAt:index andFree:NO];

	return self;
}

- (int)_nextNonDescendantOf:cell after:(int)index
/*"
Returns the row index of the next Cell after index which is %{not} a descendant of cell.  If all Cells following cell are descendants, it returns a row index one beyond the end of the matrix.
"*/
{
	int cellCount = [cellMatrix cellCount];
	id parentNode = [cell node];

	while (++index < cellCount)
	{
		id node = [[cellMatrix cellAt:index :0] node];

		if (![self _node:node isDescendantOf:parentNode])
		{
			return index;
		}
	}

	return index;
}

// Opening and closing cells

- _openCell:aCell
/*"
Opens the node in cell aCell.  If aCell is nil or not one of our cells, do nothing.
"*/
{
	int row, col;

	if (!aCell || [aCell isOpen])
	{							// do nothing if already open
		return self;
	}
	[cellMatrix getRow:&row andCol:&col ofCell:aCell];

	if (row >= 0)
	{
		id node = [aCell node];
		int numChildren = [node childCount];

		[aCell setOpen:YES];
		if (numChildren > 0)
		{
			int i;

			for (i = 0; i < numChildren; i++)
			{
				row++;
				[self _insertCellAt:row withNode:[node childAt:i]];
			}
			[cellMatrix sizeToCells];
		}
		[cellMatrix display];
	}

	return self;
}


- _closeCell:aCell
/*"
Closes the node in cell aCell.  If aCell is nil or not one of our cells, do nothing.
"*/{
	int row, col;

	if (!aCell || ![aCell isOpen])
	{							// do nothing if already closed
		return self;
	}
	[cellMatrix getRow:&row andCol:&col ofCell:aCell];
	if (row >= 0)
	{
		int cellCount = [cellMatrix cellCount];
		id thisNode = [aCell node];

		row++;
		while ((row < cellCount) &&
			   ([self _node:[[cellMatrix cellAt:row :0] node]
					  isDescendantOf:thisNode]))
		{
			[self _removeCellAt:row];
			cellCount--;
		}
		[aCell setOpen:NO];
		[cellMatrix sizeToCells];
		[cellMatrix display];
	}

	return self;
}

// PUBLIC METHODS

#if 0
- mouseDragged:(NXEvent *)theEvent
/*"
Should not be called; left in to catch clicks if something breaks whilst I'm
hacking.
"*/
{
	printf("MTB:dr\n");

	return [super mouseDragged:theEvent];
}
#endif

- mouseUp:(NXEvent *)theEvent
/*"
Should not be called; left in to catch clicks if something breaks whilst I'm
hacking.
"*/
{
	printf("MTB:up\n");

	return [super mouseUp:theEvent];
}

- doubleClick:sender
/*"
Should not be called; left in to catch double-clicks if something breaks whilst I'm hacking.  IMPLEMENTATION NOTE. %will be called if the user clicks on a part of the cell not covered by icons or text...
"*/
{
	printf("MTB:edit!\n");

	return self;
}

- init
/*"
Should not be called; Initialises self with a NULL frame.
"*/
{
  return [self initFrame:NULL];
}

- initFrame:(const NXRect *)frameRect
/*"
Designated initialiser; creates the MiscTreeBrowser and initialises other instance variables.  Returns self.
"*/
{
	NXSize interCellSpacing = {0.0, 0.0}, docSize;

	[super initFrame:frameRect];

	cellMatrix = [[MiscTreeBrowserMatrix alloc] initFrame:frameRect
											mode:NX_RADIOMODE
											cellClass:[MiscTreeBrowserCell class]
											numRows:0
											numCols:1];

	[self setBackgroundColor:defaultBackgroundColor];
	/* we don't want any space between the matrix's cells  */
	[cellMatrix setIntercell:&interCellSpacing];
	/* resize the matrix to contain the cells */
	[cellMatrix sizeToCells];
	[cellMatrix setAutosizeCells:YES];

	/* let the matrix stretch horizontally */
	[cellMatrix setAutosizing:NX_WIDTHSIZABLE];  
	/* Install the matrix as the docview of the scrollview */
	[[self setDocView:cellMatrix] free];
	/* set up the visible attributes of the scrollview */
	[self setVertScrollerRequired:YES];
	[self setBorderType:NX_BEZEL];
	/* tell the subviews to resize along with the scrollview */
	[self setAutoresizeSubviews:YES];
	/* This is the only way to get the clipview to resize too */
	[[cellMatrix superview] setAutoresizeSubviews:YES];
	/* Allow the scrollview to stretch both horizontally and vertically */
	[self setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
	/* Resize the matrix to fill the inside of the scrollview */
	[self getContentSize:&docSize];
	[cellMatrix sizeTo:docSize.width :docSize.height - 10.0];

	rootNodes = [[List alloc] init];

	return self;
}

- free
/*"
Frees the receiver - any nodes in the cell matrix are %not freed.  Returns nil.
"*/
{
	[cellMatrix free];
	[rootNodes free];

	return [super free];
}

- cellMatrix
/*"
Returns the receiver MiscTreeBrowserMatrix instance.
"*/
{
	return cellMatrix;
}

- setFont:aFont
/*"
Sets the receiver's font to aFont.
"*/
{
	[cellMatrix setFont:aFont];

	return self;
}

- font
/*"
Returns the receiver's font.
"*/
{
	return [cellMatrix font];
}

// accessing nodes

- nodeAt:(int)index
/*"
if a cell exists at row index, return its node otherwise return nil;
"*/
{
	id cell = [cellMatrix cellAt:index :0];

	return cell ? [cell node] : nil;
}

- selectedNode
/*"
If a cell is selected, return its node otherwise return nil;
"*/
{
	id cell = [cellMatrix selectedCell];

	return cell ? [cell node] : nil;
}

// accessing node information

- (BOOL)nodeIsOpen:aNode
/*"
Returns YES if aNode is "open" (displaying its children, if any).
"*/
{
	id cell = [self _cellForNode:aNode];

	return cell ? [cell isOpen] : NO;
}

// creating and deleting nodes

- removeNodeAt:(int)index
/*"
Removes the node at cell index from the node tree and the MiscTreeBrowserMatrix, but does not free it.
"*/
{
	id cell = [cellMatrix cellAt:index :0];

	if (cell)
	{
		id<MiscTreeBrowserNodes> node = [cell node];
		id<MiscTreeBrowserNodes> parent = [node parent];

		if (!parent || [parent canRemoveChild:node])
		{
			if ([cell isOpen] && ![cell isLeaf])
			{
				[self _closeCell:cell];
			}
			if (parent)
			{
				[parent removeChild:node];
			}
			else
			{
				[rootNodes removeObject:node];
			}
			[cell setNode:nil];		
			[cellMatrix  removeRowAt:index andFree:NO];
			[cellMatrix sizeToCells];
			[cellMatrix display];
		}
		else
		{
			NXBeep();
		}
	}

	return self;
}

- removeNode:aNode
/*"
Removes (if possible) aNode from the node tree.  Returns aNode if successfully removed, otherwise nil.
"*/
{
	if (aNode)
	{
		id next, root = aNode;

		while ((next = [root parent]))
		{
			root = next;
		}
		if (!root || ([rootNodes indexOf:root] != NX_NOT_IN_LIST))
		{
			int row = [self _indexOfNode:aNode];
			id parent;

			if (row >= 0)
			{
				return [self removeNodeAt:row];
			}
			// else
			parent = [aNode parent];
			if (parent && [parent canRemoveChild:aNode])
			{
				return [parent removeChild:aNode];
			}
			else if (!parent)
			{
				return [rootNodes removeObject:aNode];
			}
		}
	}

	return nil;
}

- addNode:aNode toParent:newParent after:sibling
/*"
Adds aNode to (or moves it within) the node tree, to parent newParent
(nil if aNode is to be a new root node) and after sibling (nil if
aNode is to be the first child).  Returns self for success, nil if
failed to add/move aNode.
"*/
{
	id siblingCell;
	int nodeRow;
	BOOL nodeWasOpen = NO;

	assert(aNode);
	assert((sibling == nil) ||
		   (([sibling parent] == newParent) || ([sibling parent] == nil)));

	/* cannot make a node into a child of one of its descendants! */
	if (newParent && [self _node:newParent isDescendantOf:aNode])
	{
		NXBeep();
		return nil;
	}

	if (newParent && ![newParent canAddChild:aNode after:sibling])
	{
		NXBeep();
		return nil;
	}

//	printf("Node: %s parent:%s after:%s\n", aNode ? [aNode stringValue] : "<>", newParent ? [newParent stringValue] : "<>", sibling ? [sibling stringValue] : "<>");

    [window disableFlushWindow];

	nodeRow = [self _indexOfNode:aNode];

	if (nodeRow >= 0)
	{					// node already in browser; note state and remove it
		id cell = [self _cellForNode:aNode];
		nodeWasOpen = [cell isOpen];
		[self removeNode:aNode];
	}

	siblingCell = [self _cellForNode:sibling];

	if (newParent)
	{
		id parentCell = [self _cellForNode:newParent];
		int row;

		[newParent addChild:aNode after:sibling];

		// choose where to display new node; after sib, or parent if sib == nil
		//
		if (sibling && siblingCell)
		{
			row = [self _nextNonDescendantOf:siblingCell
						after:[self _indexOfNode:sibling]];
			[self _insertCellAt:row withNode:aNode];
		}
		else if ([parentCell isOpen])
		{
			row = [self _indexOfNode:newParent] + 1;
			[self _insertCellAt:row withNode:aNode];
		}
	}
	else						// another "root" object
	{
		if (sibling)
		{						// display after sibling
			int siblingRow = [self _indexOfNode:sibling];

			if (siblingRow < 0)
			{					// invalid sibling, put on end
				[self _insertCellAt:[cellMatrix cellCount] + 1 withNode:aNode];
				[rootNodes addObject:aNode];
			}
			else
			{
				int nextRow = [self _nextNonDescendantOf:siblingCell
									after:siblingRow];
				[self _insertCellAt:nextRow withNode:aNode];
				[rootNodes insertObject:aNode
						   at:([rootNodes indexOf:sibling] + 1)];
			}
		}
		else					// or at top if sibling == nil
		{
			[self _insertCellAt:0 withNode:aNode];
			[rootNodes insertObject:aNode at:0];
		}
	}
	if (nodeWasOpen)
	{
		[self openNode:aNode];
	}
	[cellMatrix sizeToCells];
	[cellMatrix scrollNodeToVisible:aNode];
	[cellMatrix selectNode:aNode];
	[cellMatrix display];
	
    [[window reenableFlushWindow] flushWindow];

	return self;
}

- addChild:aNode
/*"
Add aNode as a child of the node in the selected cell.  Returns self for
success, nil for failure.
"*/
{
	id scell = [cellMatrix selectedCell];

	if (scell)
	{
		id par = [scell node];

		return [self addNode:aNode toParent:par after:MTB_LASTCHILD(par)];
	}

	return nil;
}

- addSibling:aNode
/*"
Add aNode as a sibling of the node in the selected cell.  Returns self for
success, nil for failure.
"*/
{
	id scell = [cellMatrix selectedCell];

	if (scell)
	{
		id sibling = [scell node];

		[self addNode:aNode toParent:[sibling parent] after:sibling];
		return self;
	}

	return nil;
}

// opening and closing nodes

- openNode:aNode
/*"
If aNode is in a cell and is closed, "open" the node (display all of aNode's
children in cells below it).
"*/
{
	return [self _openCell:[self _cellForNode:aNode]];
}

- closeNode:aNode
/*"
If aNode is in a cell and is open, "close" the node (remove all the cells below aNode's containing its children).
"*/
{
	return [self _closeCell:[self _cellForNode:aNode]];
}

@end
