#import "ServerText.h"
#import "MiscObjectRecycler.h"
#import "../NetTalkThinker.h"
#import "ClientText.h"
#import "TextDiff.h"
#import <appkit/appkit.h>
#import <remote/NXProxy.h>
#import <remote/NXConnection.h>


@implementation ServerText

- initFrame:(const NXRect *)frameRect
		text:(const char *)theText alignment:(int)mode
{
	char   path[MAXPATHLEN];
	[[NXBundle bundleForClass:[ServerText class]]
		getPath:path forResource:"Empty.rtfd" ofType:NULL];
	
	[super initFrame:frameRect text:theText alignment:mode];
	[self openRTFDFrom:path];
	textList = [[List alloc] init];
	stayFirstResponder = NO;
	return self;
}


- awakeFromNib
{
//	char    path[MAXPATHLEN];
	NXAtom	supportedTypes[11] = {NXFilenamePboardType,
								NXFileContentsPboardType, 
								NXSoundPboardType,
								NXPostScriptPboardType, 
								NXTIFFPboardType,
								NXColorPboardType,
								NXFontPboardType,
								NXRulerPboardType,
								NXRTFPboardType,
								NXAsciiPboardType,
								NULL};
	[self registerForDraggedTypes:supportedTypes count:10];
/*
*****  IF THIS WERE AN XTEXT ***
	[[NXBundle mainBundle] getPath:path forResource:"emacs" ofType:"keys"];
	demoAction = [[XTDispatchAction alloc] initBase:path estream:nil];
	[self setInitialAction:demoAction];
	fprintf(stderr, "RTFDText: Loaded %s\n", path);
*/
	return self;
}

- setStayFirstResponder:(BOOL)aFlag
{
	stayFirstResponder = aFlag;
	return self;
}

- resignFirstResponder
{
	if (stayFirstResponder)
		return nil;
	return [super resignFirstResponder];
}

- free
{
	while ([textList count])
	{
		id   theObj, connection;
		theObj = [textList removeObjectAt:0];
		connection = [theObj connectionForProxy];
		[theObj serverIsDead];
		[connection unregisterForInvalidationNotification:self];
	}
	[NXConnection removeObject:self];
	[super free];
	return self;
}

- ring:sender
{	
	if (strcmp(NXGetDefaultValue(APPNAME, NTD_RINGINGALLOWED), NTD_YES) == 0)
		[[[[Sound alloc]
			initFromSoundfile:NXGetDefaultValue(APPNAME, NTD_RINGSOUNDNAME)]
			play: nil] free];
	return self;
}

- addClientText:(ClientText *)aText
{
	TextDiff   *diff;
	
	if ([textList indexOf:aText] == NX_NOT_IN_LIST)
	{
		[textList addObject:aText];
		[[(id)aText connectionForProxy] registerForInvalidationNotification:self];
		[(id)aText setProtocolForProxy:@protocol(NetTalkClientText)];
		[aText setServerText:self];
		diff = [TextDiff newFromRecycler];
		[self writeRTFDTo:[diff stream]];
		[diff setSelection:0 :0 :1];
		[aText applyDifference:diff];
		[diff recycle];
	}
	return self;
}

- removeClientText:(ClientText *)aText;
{
	id theObj, connection;
	
	theObj = [textList removeObject:aText];
	connection = [theObj connectionForProxy];
	[connection unregisterForInvalidationNotification:self];
	
	return self;
}

- cleanupClientList
{
	int         i = 0;
	
	while (i < [textList count])
	{
		if ([[(id)[textList objectAt:i] connectionForProxy] isValid])
			i++;
		else
			[textList removeObjectAt:i];
	}
	return self;
}

- makeClientsApply:(TextDiff*)diff
{
	id    aText;
	int   count;
	
	for (count = 0; count < [textList count]; count++)
	{
		aText = [textList objectAt:count];
NX_DURING
		[aText applyDifference:diff];
NX_HANDLER
		fprintf(stderr, "Error occured in applying difference.\n");
NX_ENDHANDLER
	}
	return self;
}

- gotoLineStart
{
	int    pos = sp0.cp;
	NXStream   *text;
	char   c = 'a';
	BOOL   finished = NO;
	
	if (pos == 0)
		return [self setSel:pos :pos];
	text = [self stream];
	while (!finished)
	{
		if (pos > 0)
			pos--;
		else
			finished = YES;
		NXSeek(text, pos, NX_FROMSTART);
		c = NXGetc(text);			
		if (c == '\n')
		{
			finished = YES;
			pos++;
		}
	}
	
	[self setSel:pos :pos];
	return [self scrollSelToVisible];
}

- gotoLineEnd
{
	int    pos = spN.cp;
	NXStream   *text;
	char   c = 'a';
	BOOL   finished = NO;
	
	if (pos == [self textLength])
		return [self setSel:pos :pos];
	text = [self stream];
	NXSeek(text, pos, NX_FROMSTART);
	while (!finished)
	{
		if (NXAtEOS(text))
			finished = YES;
		else
		{
			c = NXGetc(text);
			pos++;
		}
		if (c == '\n')
		{
			finished = YES;
			pos--;
		}
	}
	[self setSel:pos :pos];
	return [self scrollSelToVisible];
}

- deleteForward
{
	if (sp0.cp == spN.cp)
		spN.cp++;
	return [self delete:nil];
}

- gotoWordStart
{
	int    pos = sp0.cp;
	NXStream   *text;
	char   c = 'a';
	BOOL   onWord = NO;
	BOOL   finished = NO;
	
	if (pos == 0)
		return [self setSel:pos :pos];
	text = [self stream];
	while (!finished)
	{
		if (pos > 0)
			pos--;
		else
			finished = YES;
		NXSeek(text, pos, NX_FROMSTART);
		c = NXGetc(text);			
		if (strchr([self preSelSmartTable], c))
		{
			if (onWord)
			{
				finished = YES;
				pos++;
			}
		}
		else
			onWord = YES;
	}
	
	[self setSel:pos :pos];
	return [self scrollSelToVisible];
}

- gotoWordEnd
{
	int    pos = spN.cp;
	NXStream   *text;
	char   c = 'a';
	BOOL   onWord = NO;
	BOOL   finished = NO;
	
	if (pos == [self textLength])
		return [self setSel:pos :pos];
	text = [self stream];
	NXSeek(text, pos, NX_FROMSTART);
	while (!finished)
	{
		if (NXAtEOS(text))
			finished = YES;
		else
		{
			c = NXGetc(text);
			pos++;
		}
		if (strchr([self postSelSmartTable], c))
		{
			if (onWord)
			{
				finished = YES;
				pos--;
			}
		}
		else
			onWord = YES;
	}
	[self setSel:pos :pos];
	return [self scrollSelToVisible];
}

/* ------------------------------------- */ 

#define EXTEND_NONE  0
#define EXTEND_LEFT  1
#define EXTEND_RIGHT 2

#define	CTRL(c)	((c)&037)
- keyDown:(NXEvent *)theEvent
{
	TextDiff           *diff = [TextDiff newFromRecycler];
	NXSelPt             start, end;
	int                 click;
	static int          extendMode = EXTEND_NONE;
	static NXSelPt      ext0, extN;
	BOOL                didExtend = NO;

	[self getSel:&start:&end];
	if (extendMode != EXTEND_NONE)
	{
		if ((ext0.cp != start.cp) || (extN.cp != end.cp))
			extendMode = EXTEND_NONE;
	}
	click = clickCount;
	switch(theEvent->data.key.charCode)
	{
/* these are emacs bindings that just move the cursor */
/* we don't have to do any diffs then. */
	case CTRL('a'): // goto begin of line
	case CTRL('b'): // move one char back
	case CTRL('e'): // goto end of line
	case CTRL('n'): // move directly down one line
	case CTRL('p'): // move directly up one line
	case CTRL('v'): // scroll down one screenfull
	case CTRL(' '): // mark position
	case 0xa3: // scroll one screenfull up
	case 0xb3: // scroll one screenfull down
		[super keyDown:theEvent];
		break;
	case CTRL('d'):
		[self deleteForward];
		break;
/* the arrows */
	case 172:
	case 173:
	case 174:
	case 175:
		if (theEvent->flags & NX_ALTERNATEMASK)
		{
			// extending the selection  -- up/down and shift mode not done yet
			if (theEvent->data.key.charCode == 172)
			{
				if (extendMode == EXTEND_NONE) extendMode = EXTEND_LEFT;
				switch (extendMode)
				{
				case EXTEND_RIGHT:
					if (spN.cp > sp0.cp)
						[self setSel:sp0.cp :spN.cp-1];
					else
						NXBeep();
					break;
				default:
					if (sp0.cp > 0)
						[self setSel:sp0.cp-1 :spN.cp];
				}
			}
			if (theEvent->data.key.charCode == 174)
			{
				if (extendMode == EXTEND_NONE) extendMode = EXTEND_RIGHT;
				switch (extendMode)
				{
				case EXTEND_LEFT:
					if (sp0.cp < spN.cp)
						[self setSel:sp0.cp+1 :spN.cp];
					else
						NXBeep();
					break;
				default:
					[self setSel:sp0.cp :spN.cp+1];
				}
			}
			[self getSel:&ext0 :&extN];
			didExtend = YES;
			break;
		}
		if (theEvent->flags & NX_CONTROLMASK)
		{
			// moving to end of screen  --  up/down and shift mode not done yet
			if (theEvent->data.key.charCode == 174)
				[self gotoLineEnd];
			if (theEvent->data.key.charCode == 172)
				[self gotoLineStart];
			break;
		}
		if (theEvent->flags & NX_SHIFTMASK)
		{
			// moving word bounds with shift
			if (theEvent->data.key.charCode == 174)
				[self gotoWordEnd];
			if (theEvent->data.key.charCode == 172)
				[self gotoWordStart];
			break;
		}
		// normal cursor movement
		[super keyDown:theEvent];
		break;
	default:
		[super keyDown:theEvent];
		[diff setSelection:start.cp :end.cp :click];
		if (sp0.cp < start.cp)
			[diff setSelection:sp0.cp :end.cp :click];
		else
		{
			NXSelPt s = sp0;
			sp0 = start;
			[self writeRTFDSelectionTo:[diff stream]];
			sp0 = s;
		}
NX_DURING
		[self makeClientsApply:diff];
NX_HANDLER
		fprintf(stderr, "Error transmitting difference package\n");
NX_ENDHANDLER
	}
	[diff recycle];
	if (!didExtend)
		extendMode = EXTEND_NONE;
	return self;
}

- delete:sender
{
	TextDiff  *diff = [TextDiff newFromRecycler];

	if (spN.cp != sp0.cp)
	{
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
	}
	[diff recycle];
	
	return [super delete:sender];
}

static id           pasteboard = nil;
static BOOL         pasting = NO;
- paste:sender
{
	id                  retval;
	int                 start, end, click;
	
	if (pasteboard == nil)
	{
		pasteboard = [Pasteboard new];
	}

	start = sp0.cp;
	end = spN.cp;
	click = clickCount;
	pasting = YES;
	retval = [super paste:sender];
	pasting = NO;
	if (retval)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[diff setSelection:start :end :click];
		[self setSel:start :sp0.cp];
		[self writeRTFDSelectionTo:[diff stream]];
		[self makeClientsApply:diff];
		[diff recycle];
		[self setSel:spN.cp :spN.cp];
	}
	
	return retval;
}

- setSelGray:(float)value
{
	id        retval = [super setSelGray:value];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- setSelColor:(NXColor) color
{
	id          retval = [super setSelColor:color];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- setSelFont:fontId
{
	id          retval = [super setSelFont:fontId];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- setSelFontSize:(float)size
{
	id          retval = [super setSelFontSize:size];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- setSelFontFamily:(const char *)fontName
{
	id        retval = [super setSelFontFamily:fontName];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- subscript:sender
{
	id          retval = [super subscript:sender];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- superscript:sender
{
	id          retval = [super superscript:sender];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- underline:sender
{
	id           retval = [super underline:sender];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- unscript:sender
{
	id          retval = [super unscript:sender];
	if (sp0.cp != spN.cp)
	{
		TextDiff   *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- changeFont:sender
{
	id     retval;
	BOOL   doApply = (sp0.cp != spN.cp);
	
	retval = [super changeFont:sender];
	if (doApply)
	{
		TextDiff *diff = [TextDiff newFromRecycler];
		[self writeRTFDSelectionTo:[diff stream]];
		[diff setSelection:sp0.cp :spN.cp :clickCount];
		[self makeClientsApply:diff];
		[diff recycle];
	}
	return retval;
}

- replaceSel:(const char *)aString
{
	id        retval;
	TextDiff *diff  = [TextDiff newFromRecycler];
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSel:aString];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	return retval;
}

- replaceSel:(const char *)aString
		length:(int)length
{
	id        retval;
	TextDiff *diff  = [TextDiff newFromRecycler];
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSel:aString length:length];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	return retval;
}

- replaceSel:(const char *)aString
		length:(int)length
		runs:(NXRunArray *)insertRuns
{
	id        retval;
	TextDiff *diff  = [TextDiff newFromRecycler];
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSel:aString length:length runs:insertRuns];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	return retval;
}

- replaceSelWithCell:cell
{
	id        retval;
	int       start;
	TextDiff *diff;
	
	if (pasting)
		return [super replaceSelWithCell:cell];
	diff = [TextDiff newFromRecycler];
	start = sp0.cp;
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSelWithCell:cell];
	[self setSel:start :start+1];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	[self setSel:start+1 :start+1];
	return retval;
}

- replaceSelWithRichText:(NXStream *)stream
{
	id        retval;
	int       start, end;
	TextDiff *diff;
	
	if (pasting)
		return [super replaceSelWithRichText:stream];
	diff  = [TextDiff newFromRecycler];
	start = sp0.cp;
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSelWithRichText:stream];
	end = spN.cp;
	[self setSel:start :end];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	[self setSel:end :end];
	return retval;
}

- replaceSelWithRTFD:(NXStream *)stream
{
	id        retval;
	TextDiff *diff  = [TextDiff newFromRecycler];
	[diff setSelection:sp0.cp :spN.cp :clickCount];
	retval = [super replaceSelWithRTFD:stream];
	[self writeRTFDSelectionTo:[diff stream]];
	[self makeClientsApply:diff];
	[diff recycle];
	return retval;
}


/********************************************/
/* NXSenderIsInvalid protocol               */
/********************************************/

- senderIsInvalid:sender
{
	return [self cleanupClientList];
}

/********************************************/
/* behavior on mouse-down                   */
/********************************************/

- (BOOL)acceptsFirstMouse
{
	return YES;
}

- mouseDown:(NXEvent *)e
{
	if (![window isKeyWindow])
		return self;
	return [super mouseDown:e];
}

@end
