#import  "TypistView.h"
#import  <libc.h>
#import  <sys/file.h>
#import  <Foundation/NSBundle.h>
#import  <AppKit/NSGraphics.h>	/* PS_funcs */
#import  <AppKit/NSImage.h>
#import  <AppKit/NSBezierPath.h>
#import  <AppKit/NSStringDrawing.h>
#import  <AppKit/NSEvent.h>
#import  "NSColorAdditional.h"
#import  "CQueue.h"
#import  "PrefControl.h"

#define  InitialImage	@"cover"
#define  ImageExt	@"gif"
#define  CoverImage	@"CoverImage"

#define  SMALL_FONT	15	/* was 14 */
#define  NOWHERE	(D_WIDTH+1)

#define  VMARGIN	10
#define  HMARGIN	10
#define	 NX_LLGRAY	(5.0/6.0)
#define  ChW_MARGIN	0
#define  ChH_MARGIN	1
#define  ChH_BIAS	0	/* ChH_MARGIN > ChH_BIAS */

#define  C_NONE		0
#define  C_ASCII	1
#define  C_KANJI1	2
#define  C_KANJI2	3
#define  C_MASK		0x0F
#define  ADDR2PNT_X(x)	((x) * charstep + HMARGIN)
#define  ADDR2PNT_Y(y)	((lines - 1 - (y)) * linestep + VMARGIN)

static NSColor	*bgColor, *erColor, *BlackColor, *WhiteColor;

@implementation TypistView

+ (void)initialize
{
	static unsigned char defs[] = { 168, 168, 208 };

	BlackColor = [NSColor blackColor];
	WhiteColor = [NSColor whiteColor];
	[self setBackgroundColor:[NSColor colorWithRGBElements: defs]];
	erColor = [[NSColor colorWithCalibratedWhite:NX_LLGRAY alpha:1.0]
		retain];
}

+ (NSColor *)backgroundColor {
	return bgColor;
}

+ (void)setBackgroundColor:(NSColor *)color
{
	[bgColor release];
	bgColor = [color retain];
}

/* Overload */
- (id)initWithFrame:(NSRect)frameRect
{
	NSCachedImageRep *rep;
	NSRect rct;

	[super initWithFrame:frameRect];
	lines = D_HEIGHT;
	[self setFontSize: SMALL_FONT];
	rct = [self frame];
	linestep = (int)((rct.size.height - VMARGIN) / lines);
	charstep = (int)((rct.size.width - HMARGIN) / D_WIDTH);
	cache = [[NSImage alloc] initWithSize: frameRect.size];
	rep = [[NSCachedImageRep alloc]
		initWithSize:[cache size]
		depth:0 separate:[cache isCachedSeparately] alpha:YES];
	[cache addRepresentation: rep];
	[rep release];
	[self allocateGState];

	return self;
}

- (void)resetResponder
{
	[[self window] makeFirstResponder:self];
}

- (void)setStreamQueue:queue
{
	streamQueue = queue; 
}

- (void)setFontSize:(int)size
{
	NSString *fontname = NSLocalizedString(@"FixFont", FIX FONT);
	if (fontname == nil)
		fontname = @"Courier";
	fontHeight = size;
	[fixfont release];
	fixfont = [NSDictionary dictionaryWithObjectsAndKeys:
		[NSFont fontWithName:fontname size:fontHeight],
		NSFontAttributeName,
		BlackColor,
		NSForegroundColorAttributeName,
		nil];
	[fixfont retain];
}

/* Local Method */
- (void)drawImage:(NSImage *)image withAlpha:(BOOL)alpha onColor:(NSColor *)color
{
	NSSize	isize;
	NSPoint	point;
	NSRect	rct, frame;
	float	xm, ym;

	frame = [self frame];
	[image setScalesWhenResized:YES];
	isize = [image size];
	xm = frame.size.width / isize.width;
	ym = frame.size.height / isize.height;
	if (xm > ym) xm = ym;
	if (xm >= 1.0)
		xm = (int)(xm + 0.5);
	else {
		int t = (int)(1.0 / xm + 0.5);
		xm = 1.0 / t;
	}
	rct.origin = NSZeroPoint;
	rct.size.width = (int)(isize.width * xm);
	rct.size.height = (int)(isize.height * xm);
	[image setSize:rct.size];
	point.x = (int)((frame.size.width - rct.size.width) / 2);
	point.y = (int)((frame.size.height - rct.size.height) / 2);
	frame.origin = NSZeroPoint;
	if (alpha) {
		[cache lockFocus];
		[color set];
		NSRectFill(frame);
		[image compositeToPoint:point
			fromRect:rct operation:NSCompositeSourceOver];
		[cache unlockFocus];
	}else {
		[cache lockFocus];
		[image compositeToPoint:point
			fromRect:rct operation:NSCompositeCopy];
		[cache unlockFocus];
	}
}

- (void)drawInitialImage
{
	NSImage	*image = nil;
	NSString *p;
	NSRect	vrect;
	NSDictionary	*dic;
	NSColor *bgc = nil;

	p = [[NSUserDefaults standardUserDefaults] objectForKey:CoverImage];
	if (p)
		image = [[NSImage alloc] initWithContentsOfFile: p];
	if (image == nil) {
		static unsigned char defs[] = { 173, 140, 123 };
		NSString	*path;
		id bundle = [NSBundle mainBundle];
		path = [bundle pathForResource:InitialImage ofType:ImageExt];
		if (path)
			image = [[NSImage alloc] initWithContentsOfFile:path];
		if (image)
			bgc = [NSColor colorWithRGBElements: defs];
	}

	vrect.origin = NSZeroPoint;
	vrect.size = [self frame].size;
	if (image != nil) {
		[self drawImage:image withAlpha:(bgc != nil) onColor:bgc];
	}else {
		[cache lockFocus];
		[[NSColor lightGrayColor] set];
		NSRectFill(vrect);
		[cache unlockFocus];
	}
	[cache lockFocus];
	[[NSColor blackColor] set];
	dic = [NSDictionary dictionaryWithObject:
		//[NSFont fontWithName:@"Times-BoldItalic" size:36.0]
		[NSFont fontWithName:@"FreeSerifBoldItalic" size:36.0]
		forKey:NSFontAttributeName];
	[@"Typist" drawAtPoint:NSMakePoint(20,30) withAttributes:dic];
	p = NSLocalizedString(@"Typing Tutor", Typing Tutor);
	[p drawAtPoint:NSMakePoint(20,5) withAttributes:fixfont];
	/* This is important to draw Japanese characters rightly.
	   May be a bug ? */
	[cache unlockFocus];
//	[self lockFocus];
//	[cache compositeToPoint:vrect.origin fromRect:vrect operation:NSCompositeCopy];
//	[self unlockFocus];
	[self display];
	[image release]; 
}


/* Override ... Method of View Class */
- (void)drawRect:(NSRect)r
{
	[cache compositeToPoint:(r.origin) fromRect:r
		operation:NSCompositeCopy];
}


/* Override ... Method of Responder Class */
- (BOOL)acceptsFirstResponder {
	return YES;
}

- (BOOL)becomeFirstResponder {
	return YES;
}

/* Override ... Method of Responder Class */
- (void)keyDown:(NSEvent *)theEvent 
{
	int	cc;
	BOOL	sft;
	const char	*s;

	s = [[theEvent characters] cString];
	sft = (([theEvent modifierFlags] & NSShiftKeyMask) != 0);
	while (*s) {
		cc = *s++ & 0xff;
		if (cc == ASCII_RUBOUT) cc = ASCII_BS;
		if (sft) cc |= Q_SHIFT;
		[streamQueue put:cc];
	}
}

/* Local Method */
- (void)printchar:(unichar *)chp
	count:(int)count at:(int)ax :(int)ay color:(NSColor *)color
{
	float	x, y, wid;
	NSString *str;
	NSRect rect;

	x = ADDR2PNT_X(ax);
	y = ADDR2PNT_Y(ay);
	wid = charstep;
	if (count == 2)
		wid *= 2;
	rect = NSMakeRect(x, y-ChH_BIAS, wid, fontHeight+ChH_MARGIN);
	[cache lockFocus];
	[color set];
	NSRectFill(rect);
	if (count > 0) {
		str = [NSString stringWithCharacters:chp length:1];
		[str drawAtPoint: NSMakePoint(x+ChW_MARGIN, y+ChH_MARGIN)
			withAttributes:fixfont];
	}
	[cache unlockFocus];

	[self lockFocus];
	[self drawRect:rect];
	[self unlockFocus];
}

/* Local Method */
- (void)drawCursor:(BOOL)draw at:(int)x :(int)y
{
	int	attr, count;
	NSColor	*color;
	
	//WhiteColor = [NSColor whiteColor];

	if (x >= D_WIDTH || y >= lines)
		return;
	attr = dispAttrs[y][x];
	switch (attr & C_MASK) {
	case C_NONE:	count = 0;  break;
	case C_KANJI1:	count = 2;  break;
	case C_ASCII:
	default:	count = 1;  break;
	}
	 color = draw ? WhiteColor :
		( ((attr & A_MASK) == A_ERROR) ? erColor: bgColor );

	[self printchar: &dispChars[y][x] count:count at:x:y color:color];
}

- (oneway void)move:(int)y : (int)x
{
	[self drawCursor:NO at: pointx : pointy];
	if ((dispAttrs[y][x] & C_MASK) == C_KANJI2)
		if (pointx > x) x--; else x++;
	pointx = (x >= D_WIDTH) ? D_WIDTH-1 : x;
	pointy = (y >= lines) ? lines-1 : y;
	[self drawCursor:YES at: pointx : pointy];
	[self display];
}

- (oneway void)clear
{
	int	i, j;
	NSRect	rect;

	rect.origin = NSZeroPoint;
	rect.size = [self frame].size;
	for (i = 0; i < lines; i++)
		for (j = 0; j < D_WIDTH; j++)
			dispAttrs[i][j] = C_NONE;
	[cache lockFocus];
	[bgColor set];
	NSRectFill(rect);
	[cache unlockFocus];

	[self lockFocus];
	[bgColor set];
	NSRectFill(rect);
	[self unlockFocus];

	pointx = NOWHERE;
	pointy = 0;
	[self display];
}

- (oneway void)setLineNumber:(int)newlines and:(int)maxlength
{
#if (SMALL_FONT == 15)
	static const int fontsize[] = { 15, 16, 18 };
	static const struct {
		int wid, hgt;
	} limits[] = {
		{D_WIDTH, D_HEIGHT},
		{((D_WIDTH * 15) / 16) - 1, (D_HEIGHT * 18) / 19},
		{((D_WIDTH * 15) / 18) - 1, (D_HEIGHT * 18) / 21},
		{ 0, 0 }
	};
#else /* 14 */
	static const int fontsize[] = { 14, 16, 18 };
	static const struct {
		int wid, hgt;
	} limits[] = {
		{D_WIDTH, D_HEIGHT},
		{((D_WIDTH * 14) / 16) - 1, (D_HEIGHT * 17) / 19},
		{((D_WIDTH * 14) / 18) - 1, (D_HEIGHT * 17) / 21},
		{ 0, 0 }
	};
#endif
	int	i, csize;
        NSSize	sz;

	if (newlines < 5) lines = 5;
	else
		lines = (D_HEIGHT < newlines) ? D_HEIGHT : newlines;
        csize = [PrefControl lessonCharSize];
	if (csize == LCS_SMALL)
		i = 0;
	else {
		for (i = 1; limits[i].wid > maxlength && limits[i].hgt > lines; i++)
			;
		--i;
		if (csize == LCS_MID && i > 1)
			i = 1;
	}
        sz = [self frame].size;
	charstep = (int)((sz.width - HMARGIN) / limits[i].wid);
	[self setFontSize: fontsize[i]];
	linestep = (int)((sz.height - VMARGIN) / lines);
}

- (oneway void)addch:(int)ch attr:(int)attr
{
	NSColor	*color;

	if (pointx >= D_WIDTH)
		return;
	dispChars[pointy][pointx] = ch & 0xff;
	dispAttrs[pointy][pointx] = attr | C_ASCII;
	color = (attr == A_NORMAL) ? bgColor: erColor;

	[self printchar: &dispChars[pointy][pointx]
			count:1 at:pointx:pointy color:color];
	[self drawCursor:YES at: ++pointx : pointy];
	[self display];
}

- (oneway void)addkanji:(unichar)ch attr:(int)attr
{
	NSColor	*color;

	if (pointx >= D_WIDTH)
		return;
	dispChars[pointy][pointx] = ch;
	dispChars[pointy][pointx+1] = 0;
	dispAttrs[pointy][pointx] = attr | C_KANJI1;
	dispAttrs[pointy][pointx+1] = attr | C_KANJI2;
	color = (attr == A_NORMAL) ? bgColor: erColor;

	[self printchar: &dispChars[pointy][pointx]
			count:2 at:pointx:pointy color:color];
	[self drawCursor:YES at: (pointx += 2) : pointy];
	[self display];
}

- (oneway void)putbs:(int)ch
{
	if (pointx <= 0 || pointx >= D_WIDTH)
		return;
	[self printchar:&dispChars[pointy][pointx]
			count:1 at:pointx:pointy color:bgColor];
	pointx--;
	dispChars[pointy][pointx] = ch & 0xff;
	dispAttrs[pointy][pointx] = C_ASCII;
	[self printchar: &dispChars[pointy][pointx]
			count:1 at:pointx:pointy color:WhiteColor];
	[self display];
}

- (oneway void)putbskanji:(unichar)ch
{
	if (pointx < 2 || pointx >= D_WIDTH)
		return;
	[self printchar:&dispChars[pointy][pointx]
			count:2 at:pointx:pointy color:bgColor];
	pointx -= 2;
	dispChars[pointy][pointx]   = ch;
	dispChars[pointy][pointx+1] = 0;
	dispAttrs[pointy][pointx]   = C_KANJI1;
	dispAttrs[pointy][pointx+1] = C_KANJI2;
	[self printchar: &dispChars[pointy][pointx]
			count:2 at:pointx:pointy color:WhiteColor];
	[self display];
}


- (oneway void)addString:(NSString *)str at:(int)ay :(int)ax attr:(int)attr
{
	int	i, length, chs, px;
	unichar	uch;
	float	x, y, wid;
	NSColor *color;
	NSRect	r;

	[self drawCursor:NO at: pointx : pointy];
	if ((dispAttrs[ay][ax] & C_MASK) == C_KANJI2)
		if (pointx > ax) ax--; else ax++;
	pointx = (ax >= D_WIDTH) ? D_WIDTH-1 : ax;
	pointy = (ay >= lines) ? lines-1 : ay;

	if (!str || (length = [str length]) == 0)
		return;
	x = ADDR2PNT_X(pointx);
	y = ADDR2PNT_Y(pointy);
	for (i = 0, chs = 0; i < length; i++) {
		uch = [str characterAtIndex:i];
		chs += (uch & ~0xff) ? 2 : 1;
	}
	wid = charstep * chs;
	color = (attr == A_NORMAL) ? bgColor: erColor;

	[cache lockFocus];
	[color set];
	NSRectFill(NSMakeRect(x, y-ChH_BIAS, wid, fontHeight+ChH_MARGIN));
	x += ChW_MARGIN;
	y += ChH_MARGIN;
	for (i = 0, px = pointx, chs = 0; i < length; i++, px++) {
		NSString *single;
		NSPoint pp = NSMakePoint(x, y);
		uch = [str characterAtIndex:i];
		if (uch & ~0xff) { /* not ASCII */
			dispAttrs[pointy][px] = attr | C_KANJI1;
			dispAttrs[pointy][px+1] = attr | C_KANJI2;
			dispChars[pointy][px] = uch;
			dispChars[pointy][++px] = 0;
			x += charstep * 2;
		}else {
			dispAttrs[pointy][px] = attr | C_ASCII;
			dispChars[pointy][px] = uch;
			x += charstep;
		}
		single = [NSString stringWithCharacters:&uch length:1];
		[single drawAtPoint:pp withAttributes:fixfont];
	}
	[cache unlockFocus];

	r.size.width = wid;
	r.size.height = fontHeight+ChH_MARGIN;
	r.origin.x = ADDR2PNT_X(pointx);
	r.origin.y = ADDR2PNT_Y(pointy) - ChH_BIAS;
	[self lockFocus];
	[self drawRect:r];
	[self unlockFocus];
	pointx = px;
	[self display];
}


- (oneway void)printLine:(NSString *)str at:(int)ay attr:(int)attr
{
	float	x, y, wid;
	NSColor *bgc;
	NSRect	rect;
	id	font;

	[self drawCursor:NO at: pointx : pointy];
	pointy = (ay >= lines) ? lines-1 : ay;
	pointx = NOWHERE;

	if (str == nil || [str length] == 0)
		return;
	x = ADDR2PNT_X(0);
	y = ADDR2PNT_Y(pointy);
	if (attr == P_REVERSE) {
		font = [[fixfont mutableCopy] autorelease];
		[font setObject:WhiteColor forKey:NSForegroundColorAttributeName];
		bgc = BlackColor;
	}else {
		font = fixfont;
		bgc = (attr == A_NORMAL) ? bgColor: erColor;
	}
	[cache lockFocus];
	[bgc set];
	wid = [str sizeWithAttributes:font].width;
	rect = NSMakeRect(x, y-ChH_BIAS, wid, fontHeight+ChH_MARGIN);
	NSRectFill(rect);
	[str drawAtPoint:NSMakePoint(x+ChW_MARGIN, y+ChH_MARGIN) withAttributes:font];
	[cache unlockFocus];

	[self lockFocus];
	[self drawRect:rect];
	[self unlockFocus];
	[self display];
}

static void PSdecorateLine(float y, float w, float hg)
{
	[WhiteColor set];
	//[[NSColor whiteColor] set];
	[NSBezierPath setDefaultLineWidth:1.0];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(0.0, y-2)
		toPoint:NSMakePoint(w, y-2)];
	[[NSColor darkGrayColor] set];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(0.0, y+hg)
		toPoint:NSMakePoint(w, y+hg)];
}

- (oneway void)decorateLine:(int)line attr:(int)attr
{
	float y, w, hg;

	y = ADDR2PNT_Y(line);
	w = [self frame].size.width;
	hg = (linestep - (fontHeight+1)) / 3 + (fontHeight+1);
	[cache lockFocus];
	PSdecorateLine(y, w, hg);
	[cache unlockFocus];
	[self lockFocus];
	PSdecorateLine(y, w, hg);
	[self unlockFocus];
	[self display];
}

- (oneway void)displayMessage:(NSString *)msg
{
	[msgText setStringValue:msg];
}

- (oneway void)fine:(int)attr
{
	NSString *path;
	NSImage *image = nil;

	path = [[NSBundle mainBundle]
		pathForResource:InitialImage ofType:ImageExt];
	if (path)
		image = [[NSImage alloc] initWithContentsOfFile:path];
	if (image == nil)
		return;
	[self drawImage:image withAlpha:YES onColor:bgColor];
	[self display];
	[image release];
}

- (oneway void)setLessonName:(NSString *)str active:(BOOL)active
{
	NSColor *clr = active ? BlackColor : [NSColor redColor];
	[lessonName setTextColor:clr];
	if (str)
		[lessonName setStringValue: str];
}

@end
