
/* Generated by Interface Builder */

#import "NekoView.h"

// based on xneko by Masayuki Koba
// This module is really, truly hideous because I chose to preserve the
// original code to a large extent rather than reimplementing the state
// machine in a sane fashion.

#import <libc.h>
#import <math.h>
#import <appkit/defaults.h>
#import <appkit/graphics.h>
#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <dpsclient/wraps.h>

const NXCoord Even[]={
    Mati2,	// STATE_STOP
    Jare2,	// STATE_JARE
    Kaki1,	// STATE_KAKI
    Mati3,	// STATE_AKUBI
    Sleep1,	// STATE_SLEEP
    Awake,	// STATE_AWAKE
    Up1,	// STATE_U_MOVE
    Down1,	// STATE_D_MOVE
    Left1,	// STATE_L_MOVE
    Right1,	// STATE_R_MOVE
    UpLeft1,	// STATE_UL_MOVE
    UpRight1,	// STATE_UR_MOVE
    DownLeft1,	// STATE_DL_MOVE
    DownRight1,	// STATE_DR_MOVE
    UpTogi1,	// STATE_U_TOGI
    DownTogi1,	// STATE_D_TOGI
    LeftTogi1,	// STATE_L_TOGI
    RightTogi1	// STATE_R_TOGI
};

const NXCoord Odd[]={
    Mati2,	// STATE_STOP
    Mati2,	// STATE_JARE
    Kaki2,	// STATE_KAKI
    Mati3,	// STATE_AKUBI
    Sleep2,	// STATE_SLEEP
    Awake,	// STATE_AWAKE
    Up2,	// STATE_U_MOVE
    Down2,	// STATE_D_MOVE
    Left2,	// STATE_L_MOVE
    Right2,	// STATE_R_MOVE
    UpLeft2,	// STATE_UL_MOVE
    UpRight2,	// STATE_UR_MOVE
    DownLeft2,	// STATE_DL_MOVE
    DownRight2,	// STATE_DR_MOVE
    UpTogi2,	// STATE_U_TOGI
    DownTogi2,	// STATE_D_TOGI
    LeftTogi2,	// STATE_L_TOGI
    RightTogi2	// STATE_R_TOGI
};

#define	PI_PER8			((double)M_PI/(double)8)
static double	SinPiPer8Times3;
static double	SinPiPer8;

@implementation NekoView

void inline TickCount(NekoView *self)
{
    if ( ++(self->NekoTickCount) >= MAX_TICK ) {
	self->NekoTickCount = 0;
    }

    if ( self->NekoTickCount % 2 == 0 ) {
	if ( self->NekoStateCount < MAX_TICK ) {
	    self->NekoStateCount++;
	}
    }
}

void inline SetNekoState(NekoView *self, int SetValue)
{
    self->NekoTickCount=0;
    self->NekoStateCount=0;

    self->NekoState=SetValue;
}

void DrawNeko(NekoView *self, NXCoord DrawIcon)
{ 
    self->NekoLastIcon.origin.x=DrawIcon;
    if ([self isAutodisplay]) {
	[self lockFocus];
	if (NX_X(&self->NekoPos)!=self->NekoLastXY.x||
	    NX_Y(&self->NekoPos)!=self->NekoLastXY.y) {
	    const NXRect zero={ { Space, 0.0 },
		{ (NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT } };
	    self->NekoLastXY.y+=(NXCoord)BITMAP_HEIGHT;	// X lossage
	    [self->bitmaps composite:NX_SOVER fromRect:&zero
		toPoint:&self->NekoLastXY];	    
	}
        self->NekoPos.origin.y+=(NXCoord)BITMAP_HEIGHT;	// X lossage
	[self->bitmaps composite:NX_SOVER fromRect:&self->NekoLastIcon
	    toPoint:&self->NekoPos.origin];
	self->NekoPos.origin.y-=(NXCoord)BITMAP_HEIGHT;
	PSflushgraphics();
	[self unlockFocus];
    }
    else [self setNeedsDisplay:YES];
    self->NekoLastXY=self->NekoPos.origin;
}

void NekoDirection(NekoView *self)
{
    int			NewState;
    double		LargeX, LargeY;
    double		Length;
    double		SinTheta;

    if ( self->NekoMoveDx == 0 && self->NekoMoveDy == 0 ) {
	NewState = STATE_STOP;
    } else {
	LargeX = (double)self->NekoMoveDx;
	LargeY = (double)(-self->NekoMoveDy);
	Length = sqrt( LargeX * LargeX + LargeY * LargeY );
	SinTheta = LargeY / Length;

	if ( self->NekoMoveDx > 0 ) {
	    if ( SinTheta > SinPiPer8Times3 ) {
		NewState = STATE_U_MOVE;
	    } else if ( ( SinTheta <= SinPiPer8Times3 )
			&& ( SinTheta > SinPiPer8 ) ) {
		NewState = STATE_UR_MOVE;
	    } else if ( ( SinTheta <= SinPiPer8 )
			&& ( SinTheta > -( SinPiPer8 ) ) ) {
		NewState = STATE_R_MOVE;
	    } else if ( ( SinTheta <= -( SinPiPer8 ) )
			&& ( SinTheta > -( SinPiPer8Times3 ) ) ) {
		NewState = STATE_DR_MOVE;
	    } else {
		NewState = STATE_D_MOVE;
	    }
	} else {
	    if ( SinTheta > SinPiPer8Times3 ) {
		NewState = STATE_U_MOVE;
	    } else if ( ( SinTheta <= SinPiPer8Times3 )
			&& ( SinTheta > SinPiPer8 ) ) {
		NewState = STATE_UL_MOVE;
	    } else if ( ( SinTheta <= SinPiPer8 )
			&& ( SinTheta > -( SinPiPer8 ) ) ) {
		NewState = STATE_L_MOVE;
	    } else if ( ( SinTheta <= -( SinPiPer8 ) )
			&& ( SinTheta > -( SinPiPer8Times3 ) ) ) {
		NewState = STATE_DL_MOVE;
	    } else {
		NewState = STATE_D_MOVE;
	    }
	}
    }

    if ( self->NekoState != NewState ) {
	SetNekoState(self, NewState);
    }
}

BOOL IsWindowOver(NekoView *self)
{
    BOOL	ReturnValue = NO;

    if ( NX_Y(&self->NekoPos) <= 0.0 ) {
	self->NekoPos.origin.y = 0.0;
	ReturnValue = YES;
    } else if ( NX_Y(&self->NekoPos) >=
	NX_HEIGHT(&self->bounds) - BITMAP_HEIGHT ) {
	self->NekoPos.origin.y = NX_HEIGHT(&self->bounds) - BITMAP_HEIGHT;
	ReturnValue = YES;
    }
    if ( NX_X(&self->NekoPos) <= 0.0 ) {
	self->NekoPos.origin.x = 0;
	ReturnValue = YES;
    } else if ( NX_X(&self->NekoPos) >=
	NX_WIDTH(&self->bounds) - BITMAP_WIDTH ) {
	self->NekoPos.origin.x = NX_WIDTH(&self->bounds) - BITMAP_WIDTH;
	ReturnValue = YES;
    }

    return( YES );
}

BOOL IsNekoDontMove(NekoView *self)
{
    if ( NX_X(&self->NekoPos) == self->NekoLastXY.x &&
	NX_Y(&self->NekoPos) == self->NekoLastXY.y ) {
	return( YES );
    } else {
	return( NO );
    }
}

BOOL IsNekoMoveStart(NekoView *self)
{
    if ( ( self->PrevMouseX >= self->MouseX - IDLE_SPACE
	 && self->PrevMouseX <= self->MouseX + IDLE_SPACE ) &&
	 ( self->PrevMouseY >= self->MouseY - IDLE_SPACE 
	 && self->PrevMouseY <= self->MouseY + IDLE_SPACE ) ) {
	return( NO );
    } else {
	return( YES );
    }
}

void CalcDxDy(NekoView *self)
{
    NXPoint			RelativeXY;
    double		LargeX, LargeY;
    double		DoubleLength, Length;

    [self->window getMouseLocation:&RelativeXY];
    [self convertPoint:&RelativeXY fromView:nil];

    self->PrevMouseX = self->MouseX;
    self->PrevMouseY = self->MouseY;

    self->MouseX = (int)RelativeXY.x;
    self->MouseY = (int)RelativeXY.y;

    LargeX = (double)( self->MouseX - NX_X(&self->NekoPos) - BITMAP_WIDTH/2 );
    LargeY = (double)( self->MouseY - NX_Y(&self->NekoPos) - BITMAP_HEIGHT );

    DoubleLength = LargeX * LargeX + LargeY * LargeY;

    if ( DoubleLength != (double)0 ) {
	Length = sqrt( DoubleLength );
	if ( Length <= self->NekoSpeed ) {
	    self->NekoMoveDx = (int)LargeX;
	    self->NekoMoveDy = (int)LargeY;
	} else {
	    self->NekoMoveDx = (int)( ( self->NekoSpeed * LargeX ) / Length );
	    self->NekoMoveDy = (int)( ( self->NekoSpeed * LargeY ) / Length );
	}
    } else {
	self->NekoMoveDx = self->NekoMoveDy = 0;
    }
}


void NekoThinkDraw(DPSTimedEntry te, double now, void *userData) {
    NekoView *self=(NekoView *)userData;

    CalcDxDy(self);

    if ( self->NekoState != STATE_SLEEP )
	DrawNeko(self, self->NekoTickCount % 2 == 0 ?
	Even[self->NekoState] : Odd[self->NekoState]);
    else  DrawNeko(self, self->NekoTickCount % 8 <= 3 ?
	Even[self->NekoState] :Odd[self->NekoState]);

    TickCount(self);

    switch ( self->NekoState ) {
    case STATE_STOP:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	if ( self->NekoStateCount < TIME_STOP ) {
	    break;
	}
	if ( self->NekoMoveDx < 0 && NX_X(&self->NekoPos) <= 0 ) {
	    SetNekoState(self, STATE_L_TOGI );
	} else if ( self->NekoMoveDx > 0 &&
	    NX_X(&self->NekoPos) >= NX_WIDTH(&self->bounds) - BITMAP_WIDTH ) {
	    SetNekoState(self, STATE_R_TOGI );
	} else if ( self->NekoMoveDy < 0 && NX_Y(&self->NekoPos) <= 0 ) {
	    SetNekoState(self, STATE_U_TOGI );
	} else if ( self->NekoMoveDy > 0 &&
	    NX_Y(&self->NekoPos) >=
	    NX_HEIGHT(&self->bounds) - BITMAP_HEIGHT ) {
	    SetNekoState(self, STATE_D_TOGI );
	} else {
	    SetNekoState(self, STATE_JARE );
	}
	break;
    case STATE_JARE:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	if ( self->NekoStateCount < TIME_JARE ) {
	    break;
	}
	SetNekoState(self, STATE_KAKI );
	break;
    case STATE_KAKI:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	if ( self->NekoStateCount < TIME_KAKI ) {
	    break;
	}
	SetNekoState(self, STATE_AKUBI );
	break;
    case STATE_AKUBI:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	if ( self->NekoStateCount < TIME_AKUBI ) {
	    break;
	}
	SetNekoState(self, STATE_SLEEP );
	break;
    case STATE_SLEEP:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	break;
    case STATE_AWAKE:
	if ( self->NekoStateCount < TIME_AWAKE ) {
	    break;
	}
	NekoDirection(self);
	break;
    case STATE_U_MOVE:
    case STATE_D_MOVE:
    case STATE_L_MOVE:
    case STATE_R_MOVE:
    case STATE_UL_MOVE:
    case STATE_UR_MOVE:
    case STATE_DL_MOVE:
    case STATE_DR_MOVE:
	self->NekoPos.origin.x += self->NekoMoveDx;
	self->NekoPos.origin.y += self->NekoMoveDy;
	NekoDirection(self);
	if ( IsWindowOver(self) ) {
	    if ( IsNekoDontMove(self) ) {
		SetNekoState(self, STATE_STOP );
	    }
	}
	break;
    case STATE_U_TOGI:
    case STATE_D_TOGI:
    case STATE_L_TOGI:
    case STATE_R_TOGI:
	if ( IsNekoMoveStart(self) ) {
	    SetNekoState(self, STATE_AWAKE );
	    break;
	}
	if ( self->NekoStateCount < TIME_TOGI ) {
	    break;
	}
	SetNekoState(self, STATE_KAKI );
	break;
    default:
	/* Internal Error */
	SetNekoState(self, STATE_STOP );
	break;
    }
}

void NekoAdjust(NekoView *self)
{
    if ( NX_X(&self->NekoPos) < 0 ) {
	self->NekoPos.origin.x = 0;
    } else if ( NX_X(&self->NekoPos) >
	NX_WIDTH(&self->bounds) - BITMAP_WIDTH ) {
	self->NekoPos.origin.x = NX_WIDTH(&self->bounds) - BITMAP_WIDTH;
    }

    if ( NX_Y(&self->NekoPos) < 0 ) {
	self->NekoPos.origin.y = 0;
    } else if ( NX_Y(&self->NekoPos) >
	NX_HEIGHT(&self->bounds) - BITMAP_HEIGHT ) {
	self->NekoPos.origin.y = NX_HEIGHT(&self->bounds) - BITMAP_HEIGHT;
    }
}

static id Nlist;

+ initialize
{
    SinPiPer8Times3=sin(PI_PER8*(double)3);
    SinPiPer8=sin(PI_PER8);

    [self setVersion:1];
    Nlist=[[List alloc] initCount:1];
    return self;
}

+ shouldRun:(BOOL)flag
{
    [Nlist makeObjectsPerform:flag ? @selector(start) : @selector(stop)];
    return self;
}

- setBitmaps:anObject
{
    bitmaps=anObject;
    return self;
}

- bitmaps
{
    return bitmaps;
}

- (int)start
{
    if (!teNum&&[window isVisible]) teNum=DPSAddTimedEntry(
	atof(NXGetDefaultValue([NXApp appName], "Interval"))/1000000.0,
	NekoThinkDraw, (void *)self, NX_BASETHRESHOLD);
    return 0;
}

- (int)stop
{
    if (teNum) {
	DPSRemoveTimedEntry(teNum);
	teNum=0;
    }
    return 0;
}

- initFrame:(const NXRect *)frameRect tora:(BOOL)flag
{
    [super initFrame:frameRect];
    [self setFlipped:YES];	// I *hate* X!!!
    [self setClipping:NO];
    [self setOpaque:YES];
    NXSetRect(&NekoPos,
	(NX_WIDTH(frameRect)-(NXCoord)(BITMAP_WIDTH/2))/2.0,
	(NX_HEIGHT(frameRect)-(NXCoord)(BITMAP_HEIGHT/2))/2.0,
	(NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT);
    NekoLastXY=NekoPos.origin;
    bitmaps=[NXImage findImageNamed:flag ? "tora.tiff" : "bitmaps.tiff"];
    NXSetRect(&NekoLastIcon, Even[STATE_STOP], 0.0,
	(NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT);
    NekoSpeed=atof(NXGetDefaultValue([NXApp appName], "Speed"));
    SetNekoState(self, STATE_STOP);
    [Nlist addObject:self];
    return [self allocateGState];
}

- initFrame:(const NXRect *)frameRect
{
    return [self initFrame:frameRect tora:NO];
}

- free
{
    [self stop];
    [Nlist removeObject:self];
    return [super free];
}

- (BOOL)acceptsFirstMouse;
{
    return YES;
}

- mouseDown:(NXEvent *)theEvent
{
    return [window dragFrom:theEvent->location.x:theEvent->location.y
	eventNum:theEvent->data.mouse.eventNum];
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    if (rectCount==3) {
	NXEraseRect(&rects[1]);
	NXEraseRect(&rects[2]);
	if (!NXIntersectsRect(&rects[1], &NekoPos)&&
	    !NXIntersectsRect(&rects[2], &NekoPos)) return self;
    }
    else NXEraseRect(rects);
    NekoPos.origin.y+=BITMAP_HEIGHT;	// X lossage
    [bitmaps composite:NX_SOVER fromRect:&NekoLastIcon
	toPoint:&NekoPos.origin];
    NekoPos.origin.y-=BITMAP_HEIGHT;

    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
    [super sizeTo:width:height];
    NekoAdjust(self);
    return self;
}

- write:(NXTypedStream *)stream
{
    [super write:stream];
    NXWriteTypes(stream, "!iiiiiii", &teNum, &NekoTickCount, &NekoStateCount, 
	&NekoState, &MouseX, &MouseY, &PrevMouseX, &PrevMouseY);
    NXWriteRect(stream, &NekoPos);
    NXWriteTypes(stream, "ii", &NekoMoveDx, &NekoMoveDy);
    NXWritePoint(stream, &NekoLastXY);
    NXWriteRect(stream, &NekoLastIcon);
    NXWriteType(stream, "d", &NekoSpeed);
    NXWriteObjectReference(stream, bitmaps);
    return self;
}

- read:(NXTypedStream *)stream
{
    [super read:stream];
    NXReadTypes(stream, "!iiiiiii", &teNum, &NekoTickCount, &NekoStateCount, 
	&NekoState, &MouseX, &MouseY, &PrevMouseX, &PrevMouseY);
    teNum=0;
    NXReadRect(stream, &NekoPos);
    NXReadTypes(stream, "ii", &NekoMoveDx, &NekoMoveDy);
    NXReadPoint(stream, &NekoLastXY);
    NXReadRect(stream, &NekoLastIcon);
    NXReadType(stream, "d", &NekoSpeed);
    bitmaps=NXReadObject(stream);
    return self;
}

- awake
{
    [super awake];
    if (!bitmaps)
	bitmaps=[NXImage findImageNamed:"bitmaps.tiff"];	// sigh
    return self;
}


@end
