/*

File Crossword.m

A crossword is a matrix of squares.  The user can adjust the squares that are empty and filled using the mouse.  When resized, a crossword automatically adjusts the number of squares in the frame to match.  It is possible to extract the words from a crossword with the -getWords method.

*/

#import <appkit/appkit.h>

#import "Crossword.h"
#import "CrosswordSquare.h"


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


#define VERSION			1
#define OLDHILIGHTS		4

#define spaceWidth		(squareWidth + INTERCELLWIDTH)
#define cells(n)		(((n) + 1) / spaceWidth)

static BOOL		doSquare	(int inWhite, id square, id words);


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


@implementation Crossword

- (float) getSquareWidth	{	return squareWidth;		}
- (BOOL) getFill			{	return fill;			}
- (BOOL) getNumbered		{	return numbered;		}
- getInspector				{	return inspector;		}

- setInspector: (id) theInspector	{	inspector = theInspector;	return self;  }


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

There are currently two different versions of the crossword object that need to be unarchived.

*/

+ initialize
{
    [Crossword setVersion: VERSION];
    return self;
}


/*

The frame will have no line along its left and top edges.  A crossword matrix should be shown against an appropriate background that clarifies these edges.

*/

- initFrame: (NXRect *) frameRect
{
	[self  initFrame: frameRect  squareWidth: DEFAULTSQUAREWIDTH];
	return self;
}


- initFrame: (NXRect *) frameRect  squareWidth: (float) squareSize
{
	NXSize	size, between;
	
	squareWidth = size.width = size.height = squareSize;
	between.width = between.height = INTERCELLWIDTH;
	numbered = NO;
	
	[super  initFrame:	frameRect
			mode:		NX_TRACKMODE
			cellClass:	[CrosswordSquare  class]
			numRows:	cells(frameRect->size.height)
			numCols:	cells(frameRect->size.width) ];
	
	[self  setCellSize: &size];
	[self  setIntercell: &between];
	[self  setBackgroundGray: 0.0];
	[self  setCellBackgroundGray: -1.0];
	[self  changeFont: [FontManager  new]];
	
	return self;
}


- write: (NXTypedStream *) stream
{
	[super  write: stream];
	NXWriteTypes(stream, "fc", &squareWidth, &numbered);
	return self;
}


- read: (NXTypedStream *) stream
{
	[super  read: stream];
    switch (NXTypedStreamClassVersion(stream, "Crossword"))
	{
		case 1:
			NXReadTypes(stream, "fc", &squareWidth, &numbered);
			break;
			
		default:
			break;
	}
	
	return self;
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

Here are the standard view methods that take care of user interactions.  Clicking within the frame allows squares to be filled and erased.

*/

- superviewSizeChanged: (NXSize *) old
{
	[super  superviewSizeChanged: old];
	[self  renewRows: cells(frame.size.height)  cols: cells(frame.size.width)];
	[inspector  update: self];
	
	return self;
}


- changeFont: sender
{
	[self  setFont: [sender  convertFont: font]];
	[inspector  update: self];
	
	return self;
}


- print: sender
{
	[superview printPSCode: self];
	return self;
}


- mouseDown: (NXEvent *) event
{
	NXPoint		location;
	
	location = event->location;
	[self  convertPoint: &location  fromView: nil];
	fill = ![[self  cellAt: location.y / spaceWidth
						  : location.x / spaceWidth] isOpaque];
	
	[super  mouseDown: event];
	
	if (numbered)
			[self  unnumber];			else
			[inspector  update: self];
	
	return self;
}


- (BOOL) acceptsFirstMouse
{
	return NO;
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


- clear: (squareColor) color;
{
	CrosswordSquare		* square;
	int					i;
	
	i = [cellList  count];
	while (i--)
	{
		square = [cellList  objectAt: i];
		[[square  setColor: color]  setLetter: EMPTY];
	}
	
	[self  display];
	
	return self;
}


- putLetter: (char) letter  inSquare: (id) square
{
	[square  setLetter: letter];
	[self  drawCellInside: square];
	
	return self;
}


- number
{
	int		i, j, n;
	id		square;
	
	for (n = 1, i = 0; i < numRows; i++)
	for (j = 0; j < numCols; j++)
	if (
			[square = [self  cellAt: i: j]  isOpaque] &&
				(	![[self  cellAt: (i - 1): j]  isOpaque] ||
					![[self  cellAt: i: (j - 1)]  isOpaque] )
			
							)	[square  setNumber: n++];
	
	numbered = YES;
	[self  display];
	[inspector  update: self];
	
	return self;
}


- unnumber
{
	int		i, j;
	
	for (i = 0; i < numRows; i++)
	for (j = 0; j < numCols; j++)
			[[self  cellAt: i: j]  setNumber: EMPTY];
	
	numbered = NO;
	[self  display];
	[inspector  update: self];
	
	return self;
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


- getWords
{
	int		i, j;
	BOOL	inWhite;
	id		words;
	
	words = [[List  alloc]  init];
	
	for (i = 0; i < numRows; i++)
	for (inWhite = NO, j = 0; j < numCols; j++)
				inWhite = doSquare(inWhite, [self  cellAt: i: j], words);
				
	for (j = 0; j < numCols; j++)
	for (inWhite = NO, i = 0; i < numRows; i++)
				inWhite = doSquare(inWhite, [self  cellAt: i: j], words);
	
	return words;
}


@end


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

The routine below processes an individual square when extracting words.  It exists as a separate function to avoid duplication in the across and down directions.

*/

static BOOL	doSquare (inWhite, square, words)

BOOL	inWhite;
id		square;
id		words;

{
	BOOL	opaque = [square  isOpaque];
	
	if (!inWhite && opaque) [words  addObject: [[List  alloc]  init]];
	if (opaque) [[words  lastObject]  addObject: square];
	
	return opaque;
}