/*
**	LispText.m
**	A Text object to drive the Lisp object.
**	Lee Boynton, NeXT, Inc., 1989
*/


#import <stdlib.h>
#import <string.h>
#import <sys/time.h>
#import <appkit/Window.h>
#import <appkit/publicWraps.h>
#import "Lisp.h"
#import "LispText.h"

@implementation LispText

#define DEFAULT_HISTORY 64

#define CLOSE_PAREN (')')
#define OPEN_PAREN ('(')
#define NEW_LINE ('\n')
#define CTRL_A (1)
#define CTRL_B (2)
#define CTRL_C (3)
#define CTRL_D (4)
#define CTRL_E (5)
#define CTRL_F (6)
#define CTRL_K (11)
#define CTRL_N (14)
#define CTRL_P (16)


unsigned short lispFilter(unsigned short theChar, int flags,
					 unsigned short charSet)
{
    if (flags & NX_COMMANDMASK)
	theChar = 0;
    else if (theChar == NX_DELETE)
	theChar = NX_BACKSPACE;
    else if (theChar == NX_CR || theChar == NEW_LINE)
	theChar = 0;
    else if (theChar == '\t')
	theChar = '\t';
    else if (theChar < ' ')
	theChar = 0;
    return theChar;
}

static void initLispCategoryTable(id self)
{
    static unsigned char lispTable[256];
    unsigned char punctuationCategory, normalCategory;
    unsigned char nullCategory, whiteSpaceCategory;
    int i;

    bcopy([self charCategoryTable],lispTable,sizeof(lispTable));
    nullCategory = lispTable[0];
    punctuationCategory = lispTable[';'];
    normalCategory = lispTable['a'];
    whiteSpaceCategory = lispTable[' '];
    lispTable['!'] = normalCategory;
    lispTable['&'] = normalCategory;
    lispTable['('] = punctuationCategory;
    lispTable[')'] = punctuationCategory;
    lispTable['*'] = normalCategory;
    lispTable['+'] = normalCategory;
    lispTable['-'] = normalCategory;
    lispTable['.'] = normalCategory;
    lispTable['/'] = normalCategory;
    lispTable[':'] = normalCategory;
    lispTable['<'] = normalCategory;
    lispTable['='] = normalCategory;
    lispTable['>'] = normalCategory;
    lispTable['?'] = normalCategory;
    lispTable['@'] = normalCategory;
    lispTable['['] = punctuationCategory;
    lispTable['\\'] = normalCategory;
    lispTable[']'] = punctuationCategory;
    lispTable['{'] = punctuationCategory;
    lispTable['|'] = normalCategory;
    lispTable['}'] = punctuationCategory;
    for(i=128;i<256;i++) lispTable[i] = nullCategory;
    [self setCharCategoryTable:lispTable];
}

+ newFrame:(NXRect *)frameRect text:(char *)theText alignment:(int)align
{
    int i;
    self = [super newFrame:frameRect text:theText alignment:align];
    [self setCharFilter:lispFilter];
    [self setOpaque:YES];
    [self setOverstrikeDiacriticals:NO];
    initLispCategoryTable(self);
    inputPosition = 0;
    maxHistory = DEFAULT_HISTORY;
    history = (char **)malloc(maxHistory*sizeof(char*));
    for (i=0; i<maxHistory; i++) history[i] = NULL;
    historyHead = 0;
    connectedToLisp=NO;
    theLisp = [Lisp new];
    [theLisp setDelegate:self];
    return self;
}

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

- lispOutput:(const char *)theString
{
    int offset;
    NXSelPt selStart, selEnd;
    if (!theString) return self;
    offset = strlen(theString);
    if (!connectedToLisp) {
	int i = [self textLength];
	[self setSel:0 :i];
	[self replaceSel:theString];
	inputPosition = offset;
	[window makeFirstResponder:self];
	connectedToLisp=YES;
    } else {
	[self getSel:&selStart :&selEnd];
	[self setSel:inputPosition :inputPosition];
	[self replaceSel:theString];
	inputPosition += offset;
	[self setSel:selStart.cp+offset :selEnd.cp+offset];
    }
    [self scrollSelToVisible];
    return self;
}

/*
** NXBGetc - a text stream macro to get the previous character.
*/

typedef struct {
    id text;
    NXTextBlock *block;
} textInfo;

static char getPrevious(NXStream *s)
{
    textInfo *info = (textInfo *) s->info;

    NXTextBlock *block = info->block->prior;
    if (!block) {
	return EOF;
    }
    s->buf_base = block->text;
    s->buf_ptr = s->buf_base + block->chars;
    s->offset -= block->chars;
    info->block = block;
    return *(--s->buf_ptr);
}

#define NXBGetc(s) \
    (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
	*(--((s)->buf_ptr)) )


int findMatchingOpenParen(id text, char thisChar, char thatChar, int curPos)
{
    int count = 1;
    char ch;
    NXStream *s = [text stream];
    NXSeek(s, curPos, NX_FROMSTART);
    while ((ch = NXBGetc(s)) != EOF) {
	if (ch == thatChar && !(--count))
	    return NXTell(s);
	else if (ch == thisChar)
	    count++;
    }
    return -1;
}

int findMatchingCloseParen(id text, char thisChar, char thatChar, int curPos)
{
    int count = 1;
    char ch;
    NXStream *s = [text stream];
    NXSeek(s, curPos, NX_FROMSTART);
    while ((ch = NXGetc(s)) != EOF) {
	if (ch == thatChar && !(--count))
	    return NXTell(s);
	else if (ch == thisChar)
	    count++;
    }
    return -1;
}

void highlightChar(id text, int charPosition)
{
    NXSelPt selStart, selEnd;
    [text getSel:&selStart :&selEnd];
    [text setSel:charPosition :charPosition+1];
    [[text window] flushWindow];
    NXPing();
    [[text window] disableFlushWindow];
    [text setSel:selStart.cp :selEnd.cp];
    [[text window] reenableFlushWindow];
}

int searchBackwardsForChar(id text, char theChar, int curPos)
{
    char ch;
    NXStream *s = [text stream];
    NXSeek(s, curPos, NX_FROMSTART);
    
    while ((ch = NXBGetc(s)) != EOF) {
	if (ch == theChar)
	    return NXTell(s);
    }
    return -1;
}

void insertChar(id self, char c, int pos)
{
    char buf[2];
    buf[0] = c;
    buf[1] = 0;
    [self setSel:pos :pos];
    [self replaceSel:buf];
    [self scrollSelToVisible];
}

static void addToHistory(id self, char *buf)
{
    int i = (self->historyHead++) % self->maxHistory;
    if (self->history[i])
	free(self->history[i]);
    self->history[i] = buf;
}

static char *getHistory(id self, int index)
{
    int i = index % self->maxHistory;
    return self->history[i];
}

- keyDown:(NXEvent *)theEvent
{
    static int history_index=0;
    int i, lastPos = [self textLength];
    unsigned short val;
    NXSelPt selStart, selEnd;
    if (!connectedToLisp) {
	NXBeep();
	return self;
    }
    [self getSel:&selStart :&selEnd];
    val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8);
    switch (val) {
	case CLOSE_PAREN:
	    if (selStart.cp < inputPosition)
		[self setSel:lastPos :lastPos];
	    i = findMatchingOpenParen(self,CLOSE_PAREN,OPEN_PAREN,selEnd.cp);
	    [super keyDown:theEvent];
	    if (i >= 0)
		highlightChar(self,i);
	    return self;
	case NX_DELETE:
	case NX_BACKSPACE:
	    if (selEnd.cp == selStart.cp) {
		if (lastPos <= inputPosition || selStart.cp <= inputPosition) {
		    NXBeep();
		    return self;
		}
	    } else {
		if (selEnd.cp <= inputPosition ||
					 selStart.cp <= inputPosition) {
		    NXBeep();
		    return self;
		}
	    }
	    break;
	case NX_CR:
	case NEW_LINE:
	    if (selEnd.cp >= inputPosition) {
		char *buf;
		i = lastPos - inputPosition;
		buf = (char *)malloc(i+2);
		if (i)
		    [self getSubstring:buf start:inputPosition length:i];
		buf[i] = NEW_LINE;
		buf[i+1] = 0;
		inputPosition = lastPos+1;
		[theLisp inputLisp:buf];
		if (i) {
		    buf[i] = 0;
		    addToHistory(self,buf);
		    history_index = historyHead;
		}
		[self setSel:lastPos :lastPos];
		insertChar(self,NEW_LINE,lastPos);
		return self;
	    } else {
		int len = selEnd.cp-selStart.cp;
		if (len) {
		    char *buf = (char *)malloc(len+1);
		    [self getSubstring:buf start:selStart.cp length:len];
		    buf[len] = 0;
		    [self setSel:lastPos :lastPos];
		    [self replaceSel:buf];
		    free(buf);
		    [self scrollSelToVisible];
		} else
		    [self setSel:lastPos :lastPos];
		return self;
	    }
	case CTRL_C:
	    [self setSel:lastPos :lastPos];
	    [theLisp interruptLisp];
	    return self;
	    break;
	case CTRL_A:
	    [self setSel:inputPosition :inputPosition];
	    return self;
	case CTRL_E:
	    [self setSel:lastPos :lastPos];
	    return self;
	case CTRL_B:
	    if (selStart.cp < inputPosition)
		[self setSel:inputPosition :inputPosition];
	    else if (selStart.cp != selEnd.cp)
		[self setSel:selStart.cp :selStart.cp];
	    else if (selStart.cp > inputPosition)
		[self setSel:selStart.cp-1 :selStart.cp-1];
	    else
		NXBeep();
	    return self;
	case CTRL_F:
	    if (selEnd.cp < inputPosition)
		[self setSel:inputPosition :inputPosition];
	    else if (selStart.cp != selEnd.cp)
		[self setSel:selEnd.cp :selEnd.cp];
	    else if (selEnd.cp < lastPos)
		[self setSel:selEnd.cp+1 :selEnd.cp+1];
	    else
		NXBeep();
	    return self;
	case CTRL_D:
	    if (selStart.cp >= inputPosition) {
		if (selStart.cp == selEnd.cp)
		    if (selEnd.cp >= lastPos) {
			NXBeep();
			return;
		    } else
			[self setSel:selEnd.cp :selEnd.cp+1];
		[self replaceSel:""];
	    } else
		NXBeep();
	    return self;
	case CTRL_K:
	    if (selStart.cp >= inputPosition) {
		if (inputPosition != lastPos)
		    [self setSel:selStart.cp :lastPos];
		[self replaceSel:""];
	    } else
		NXBeep();
	    return self;
	case CTRL_P: {
	    char *s;
	    if (!historyHead || 
		    (history_index <= (historyHead-self->maxHistory)))
		return self;
	    s = getHistory(self, history_index-1);
	    if (!s) return self;
	    history_index--;
	    [window disableFlushWindow];
	    [self setSel:inputPosition :lastPos];
	    [self replaceSel:s];
	    [self scrollSelToVisible];
	    [window reenableFlushWindow];
	    [window flushWindow];
	    return self; }
	case CTRL_N:
	    {
	    char *s;
	    if (!historyHead || (history_index >= (historyHead)))
		return self;
	    s = getHistory(self, history_index);
	    if (s)
		s = getHistory(self, ++history_index);
	    [window disableFlushWindow];
	    [self setSel:inputPosition :lastPos];
	    [self replaceSel:s];
	    [self scrollSelToVisible];
	    [window reenableFlushWindow];
	    [window flushWindow];
	    return self; }
	default:
	    if (selStart.cp < inputPosition)
		[self setSel:lastPos :lastPos];
	    break;
    }
    return [super keyDown:theEvent];
}

- clear:sender
{
    NXSelPt selStart, selEnd;
    [self getSel:&selStart :&selEnd];
    if (selEnd.cp < inputPosition || selStart.cp < inputPosition)
	return self;
    else
	return [super clear:sender];
}

- cut:sender
{
    NXSelPt selStart, selEnd;
    [self getSel:&selStart :&selEnd];
    if (selEnd.cp < inputPosition || selStart.cp < inputPosition)
	return self;
    else
	return [super cut:sender];
}

- paste:sender
{
    int size = [self textLength];
    NXSelPt selStart, selEnd;
    [self getSel:&selStart :&selEnd];
    if (selEnd.cp < inputPosition || selStart.cp < inputPosition)
	[self setSel:size :size];
    return [super paste:sender];
}

- mouseDown: (NXEvent *) theEvent
{
    BOOL doubleClick;
    doubleClick = (theEvent->data.mouse.click == 2);
    [super mouseDown:theEvent];
    if (theEvent->data.mouse.click == 2) {
	char theChar;
	int i;
	NXSelPt selStart, selEnd;
	[self getSel:&selStart :&selEnd];
	[self getSubstring:&theChar start:selStart.cp length:1];
	if (theChar == OPEN_PAREN) {
	    i = findMatchingCloseParen(self,theChar,CLOSE_PAREN,selEnd.cp+1);
	    if (i >= 0)
		[self setSel:selStart.cp :i];
	} else if (theChar == CLOSE_PAREN) {
	    i = findMatchingOpenParen(self,theChar,OPEN_PAREN,selEnd.cp-1);
	    if (i >= 0)
		[self setSel:i :selEnd.cp];
	}
    }
    return self;
}

- clearAll:sender
{
    [self setSel:0 :[self textLength]];
    [super clear:self];
    inputPosition = 0;
    return self;
}

- inputLisp:(const char *)theString
{
    return [theLisp inputLisp:theString];
}


- interruptLisp
{
    return [theLisp interruptLisp];
}

	

@end






