/* Generated by Interface Builder */

#import "AliceDoc.h"
#import "AliceApp.h"
#import "CubeView.h"
#import "CubePlane.h"

#import "Plane.h"

extern "Objective-C" {
#import "BasicApp.h"
#import <objc/List.h>
#import <objc/Storage.h>
#import <appkit/Window.h>
#import <appkit/Matrix.h>
#import <appkit/MenuCell.h>
}

extern "C" {
#import <strings.h>
}

typedef struct _Action {
	CubeCoord plane;
	CubeDir dir;
} Action;

#define ZAlloc(CLASS) [CLASS allocFromZone:[self zone]]

#ifndef DITCHDAY
static id RandGen;
static const int RandTurns = 8;
#else
#define CAPITAL 0x20
static const char Scrambler[] = "BmWjHbWLkkoGcggx";
static const int RandTurns = 16;
#endif

static const CubeInt SidesShown = 3;

@implementation AliceDoc

+ initialize
{
#ifndef DITCHDAY
	RandGen = [ZAlloc(Random) init];
#endif
	[super initialize];
	[self setContentsClass:[CubeView class]];
	return self;
}

- getWindow
{
	myCube = new Cube;

	for (CubeInt d=0; d < SidesShown; d++) {
#ifdef THREED
		Plane *newPlane = myCube->plane(2*d+1);
#else
		Plane *newPlane = myCube->plane(2*d+1, T);
#endif
		[contents addSubview:[ZAlloc(CubePlane) 
				    initPlane:newPlane tag:d]];
	}
	[contents resize:self];

	undoActions = [ZAlloc(Storage) initCount:0 
		     elementSize:sizeof(Action)
		     description:"{ii}"];

	[super getWindow];
	[window addToEventMask:NX_KEYDOWNMASK];
	return self;
}

- free
{
	[undoActions free];
	return [super free];
}

- reset:sender
{
	myCube->reset();
	[undoActions empty];
	scrambled = NO;
	return [self test:self];
}

- scramble:sender
{
	int n;
	[self reset:sender];
	[contents setAutodisplay:NO];
	for (n=0; n < RandTurns; n++) {
#ifndef DITCHDAY
		[self twist:(int)[RandGen randMax:(CubeNSides-1)]
			   :getDir([RandGen rand])];
#else
		char code = Scrambler[n];
		CubeDir dir = (code & CAPITAL) ? 1 : 0;
		[self doTwist:code :dir];
#endif
	[undoActions empty];
	}
	[contents setAutodisplay:YES];
	scrambled = YES;
	return [self test:self];
}

- unscramble:sender
{
	while ([self undo:self]) {
		NXPing();
	}
	return [self test:self];
}

- undo:sender
{
	int count;
	if (count = [undoActions count]) {
		Action *last;
		last = (Action*)[undoActions elementAt:(count-1)];
		[self twist:last->plane :last->dir];
		[undoActions removeLastElement]; /* Don't redo this! */
		[undoActions removeLastElement]; /* Don't undo this! */
		return [self test:self];	
	}
	return nil;
}

- test:sender
{
	if (scrambled && [undoActions count] && !myCube->test()) {
		[NXApp successPanel:self];
		scrambled = NO;
	}
	return [contents update];
}

/* Modify State */

- twist:(CubeCoord) plane :(CubeDir) rot
{
	Action next;
#ifdef THREED
	CubeAxis axis2 = XYZTMAX;	/* Use default */
	Plane *twister = myCube->plane(plane);
#else
	CubeAxis axis2 = getAxis(rot);
	Plane *twister = myCube->plane(plane,axis2);
#endif

#ifdef DEBUG1
		printf("Twist:%d :%d\n",plane,rot);
#endif
	twister[LO].rotate(getDir(rot),axis2);
#ifndef THREED
	twister[HI].rotate(getDir(rot),axis2);
#endif

	delete twister;

	next.plane = plane;	/* Undo List */
	next.dir = revDir(rot);
	[undoActions addElement:&next];
	return self;
}

- rotate:(CubeCoord) plane :(CubeDir) dir
{
	[self twist:plane :dir];
	[self twist:otherSide(plane) :dir];
	return self;
}

/* Rotate */

- rotateX:sender
{
	CubeDir tag = [sender selectedTag];
	[self rotate:X0 :tag];
 	return [self test:self];
}

- rotateY:sender
{
	CubeDir tag = [sender selectedTag];
	[self rotate:Y0 :tag];
	return [self test:self];
}

- rotateZ:sender
{
	CubeDir tag = [sender selectedTag];
	[self rotate:Z0 :tag];
	return [self test:self];
}

- rotateT:sender
{
	CubeDir tag = [sender selectedTag];
	[self rotate:T0 :tag];
	return [self test:self];
}

#define POS_MASK ('\010')
inline CubePos parsePos(CubeInt tag) {return (tag & POS_MASK) ? 1 : 0;}
inline CubeDir parseDir(CubeInt tag) {return tag & (~POS_MASK);}

/* Twist */

- twistX:sender
{
	CubeInt tag = [sender selectedTag];
	[self twist:X0+parsePos(tag) :parseDir(tag)];
	return [self test:self];
}

- twistY:sender
{
	CubeInt tag = [sender selectedTag];
	[self twist:Y0+parsePos(tag) :parseDir(tag)];
	return [self test:self];
}

- twistZ:sender
{
	CubeInt tag = [sender selectedTag];
	[self twist:Z0+parsePos(tag) :parseDir(tag)];
	return [self test:self];
}

- twistT:sender
{
	CubeInt tag = [sender selectedTag];
	[self twist:T0+parsePos(tag) :parseDir(tag)];
	return [self test:self];
}

#define COORD_MASK 0x01F		/* Just use the bottom 31 */
#define WRAP(C) ((C & (COORD_MASK))-1) /* a =0, b = 1, ... x=31 */
/* 6 per plane, 3 per position */
#define NROTS (3)
/* key Equivalents */
- doTwist:(unsigned short) charCode :(short)dir
{
	unsigned char code = WRAP(charCode);
	CubeCoord coord = (code / NROTS) % CubeNCoords;
	CubeAxis axis = code % NROTS;
	if (axis == getAxis(coord)) axis = T;
	[self twist:coord :setDir(axis,dir)];
	return [self test:self];
}

- doRotate:(unsigned short) charCode :(short)dir
{
	unsigned char code = WRAP(charCode);
	CubeCoord coord = (code / NROTS) % CubeNCoords;
	CubeAxis axis = code % NROTS;
	if (axis == getAxis(coord)) axis = T;
	[self rotate:coord :setDir(axis,dir)];
	return [self test:self];
}


/* Delegate Methods */

- (BOOL)validateCommand:menuCell
/*
 * Validates whether a menu command that Document responds to
 * is valid at the current time.
 */
{

    SEL action = [menuCell action];

    if  (action == @selector(reset:) || action == @selector(scramble:) ) {
	    return YES;
    } else if  (action == @selector(undo:) || action==@selector(unscramble:)) {
	    return (BOOL) [undoActions count];
    }

    return YES;
}
@end
