#include <QF/keys.h>
#include <string.h>
#include "ruamoko/qwaq/qwaq-app.h"
#include "ruamoko/qwaq/editor/editor.h"
#include "ruamoko/qwaq/ui/listener.h"
#include "ruamoko/qwaq/ui/scrollbar.h"

@implementation Editor

static int
center (unsigned v, int len)
{
	return v > len / 2 ? v / 2 : 0;
}

static void
trackCursor (Editor *self)
{
	unsigned    cx = [self.buffer charPos:self.line_index at:self.char_index];
	if (self.cursor.x != cx) {
		if (self.char_index < [self.buffer getEOT]) {
			int c = [self.buffer getChar:self.char_index];
		}
	}
}

-initWithRect:(Rect) rect file:(string) filename path:(string) filepath
{
	if (!(self = [super initWithRect: rect])) {
		return nil;
	}
	self.filename = str_hold (filename);
	if (filepath != filename) {
		self.filepath = str_hold (filepath);
	} else {
		self.filepath = filename;
	}
	buffer = [[EditBuffer withFile:filepath] retain];
	line_count = [buffer countLines: {0, [buffer textSize]}];
	linebuffer = [[DrawBuffer buffer: { xlen, 1 }] retain];
	growMode = gfGrowHi;
	options = ofCanFocus | ofRelativeEvents;
	[onViewScrolled addListener:self :@selector(onScroll:)];
	[self setCursorVisible:1];

	return self;
}

+(Editor *)withRect:(Rect)rect file:(string)filename
{
	return [[[self alloc] initWithRect:rect
								  file:filename
								  path:filename] autorelease];
}

+(Editor *)withRect:(Rect)rect file:(string)filename path:(string)filepath
{
	return [[[self alloc] initWithRect:rect
								  file:filename
								  path:filepath] autorelease];
}

-(void)dealloc
{
	if (filepath != filename) {
		str_free (filepath);
	}
	str_free (filename);
	[vScrollBar release];
	[buffer release];
	[linebuffer release];
	[super dealloc];
}

-(string)filename
{
	return filename;
}

-(string)filepath
{
	return filepath;
}

-(Point)cursor
{
	return cursor;
}

-setStatusView:(EditStatus *)status
{
	self.status = status;
	[status setModified:modified];
	[status setCursorMode:cursorMode];
	[status redraw];
	return self;
}

-trackCursor:(int)fwd
{
	unsigned    tx = [buffer charPos:line_index at:char_index];

	cursorMode &= ~cmVInsert;
	if (tx != cursor.x) {
		if (char_index < [buffer getEOT]) {
			int         c = [buffer getChar:char_index];

			if (virtualInsert) {
				if (c != '\t' || cursorThroughTabs) {
					cursorMode |= cmVInsert;
					goto done;
				}
			}
			if (c == '\t' && fwd) {
				tx = [buffer charPos:line_index at:++char_index];
			}
		} else if (virtualInsert) {
			cursorMode |= cmVInsert;
			goto done;
		}
		cursor.x = tx;
	}
done:
	[status setCursorMode:cursorMode];
	[status redraw];
	return self;
}

-draw
{
	[super draw];
	unsigned lind = base_index;
	int *lbuf = [linebuffer buffer];
	for (int y = 0; y < ylen; y++) {
		lind = [buffer formatLine:lind from:base.x into:lbuf width:xlen
				highlight:selection colors: {color_palette[047],
											 color_palette[007]}];
		[textContext blitFromBuffer: linebuffer to: {xpos, ypos + y}
							from: [linebuffer rect]];
	}
	return self;
}

-resize: (Extent) delta
{
	[super resize: delta];
	[linebuffer resizeTo: {xlen, 1}];
	return self;
}

static int
handleEvent (Editor *self, qwaq_event_t *event)
{
	if (event.what & qe_mouse) {
		if (event.what == qe_mouseclick) {
			if (event.mouse.buttons & (1 << 3)) {
				[self.vScrollBar page:1 dir:0];
				return 1;
			}
			if (event.mouse.buttons & (1 << 4)) {
				[self.vScrollBar page:1 dir:1];
				return 1;
			}
			if (event.mouse.buttons & (1 << 5)) {
				[self scrollLeft: 1];
				return 1;
			}
			if (event.mouse.buttons & (1 << 6)) {
				[self scrollRight: 1];
				return 1;
			}
		}
	} else if (event.what == qe_keydown) {
		switch (event.key.code) {
			case QFK_PAGEUP:
				if (event.key.shift & qe_control) {
					[self moveBOT];
				} else {
					[self pageUp];
				}
				return 1;
			case QFK_PAGEDOWN:
				if (event.key.shift & qe_control) {
					[self moveEOT];
				} else {
					[self pageDown];
				}
				return 1;
			case QFK_UP:
				if (event.key.shift & qe_control) {
					[self linesUp];
				} else {
					[self charUp];
				}
				return 1;
			case QFK_DOWN:
				if (event.key.shift & qe_control) {
					[self linesDown];
				} else {
					[self charDown];
				}
				return 1;
			case QFK_LEFT:
				if (event.key.shift & qe_control) {
					[self wordLeft];
				} else {
					[self charLeft];
				}
				return 1;
			case QFK_RIGHT:
				if (event.key.shift & qe_control) {
					[self wordRight];
				} else {
					[self charRight];
				}
				return 1;
			case QFK_HOME:
				if (event.key.shift & qe_control) {
					[self moveBOS];
				} else {
					[self moveBOL];
				}
				return 1;
			case QFK_END:
				if (event.key.shift & qe_control) {
					[self moveEOS];
				} else {
					[self moveEOL];
				}
				return 1;
		}
	}
	return 0;
}

-handleEvent:(qwaq_event_t *) event
{
	[super handleEvent: event];

	if (handleEvent (self, event)) {
		event.what = qe_none;
	}
	return self;
}

-scrollLeft:(unsigned) count
{
	if (base.x > count) {
		base.x -= count;
	} else {
		base.x = 0;
	}
	[self redraw];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-scrollRight:(unsigned) count
{
	if (1024 - base.x > count) {
		base.x += count;
	} else {
		base.x = 1024;
	}
	[self redraw];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-scrollTo:(unsigned)target
{
	if (target > base.y) {
		base_index = [buffer nextLine:base_index :target - base.y];
	} else if (target < base.y) {
		base_index = [buffer prevLine:base_index :base.y - target];
	}
	base.y = target;
	[vScrollBar setIndex:base.y];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-pageUp
{
	[self recenter:0];
	unsigned count = cursor.y;

	if (count > ylen) {
		count = ylen;
	}
	if (count) {
		cursor.y -= count;
		[vScrollBar setIndex:[vScrollBar index] - count];
		line_index = [buffer prevLine:line_index :count];
		char_index = [buffer charPtr:line_index at:cursor.x];
		[self trackCursor:1];

		[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	}
	return self;
}

-pageDown
{
	[self recenter:0];
	unsigned count = line_count - cursor.y;

	if (count > ylen) {
		count = ylen;
	}
	if (count) {
		cursor.y += count;
		[vScrollBar setIndex:[vScrollBar index] + count];
		line_index = [buffer nextLine:line_index :count];
		char_index = [buffer charPtr:line_index at:cursor.x];
		[self trackCursor:1];

		[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	}
	return self;
}

-linesUp
{
	while (line_index > 0) {
		line_index = [buffer prevLine: line_index];
		cursor.y--;
		unsigned p = [buffer nextNonSpace: line_index];
		if ([buffer getChar:p] == '\n') {
			continue;
		}
		int x = [buffer charPos:line_index at:p];
		if (x <= cursor.x) {
			break;
		}
	}
	char_index = [buffer charPtr:line_index at:cursor.x];

	[self recenter:0];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];

	return self;
}

-linesDown
{
	unsigned end = [buffer getEOT];
	unsigned last = [buffer getBOL:end];
	while (line_index < last) {
		line_index = [buffer nextLine: line_index];
		cursor.y++;
		unsigned p = [buffer nextNonSpace: line_index];
		if (p >= end || [buffer getChar:p] == '\n') {
			continue;
		}
		int x = [buffer charPos:line_index at:p];
		if (x <= cursor.x) {
			break;
		}
	}
	char_index = [buffer charPtr:line_index at:cursor.x];

	[self recenter:0];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];

	return self;
}

-charUp
{
	[self recenter:0];
	if (cursor.y < 1) {
		return self;
	}
	cursor.y--;
	line_index = [buffer prevLine:line_index :1];
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:1];

	if (base.y > cursor.y) {
		[vScrollBar setIndex:cursor.y];
	}
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-charDown
{
	[self recenter:0];
	if (cursor.y >= line_count) {
		return self;
	}
	cursor.y++;
	line_index = [buffer nextLine:line_index :1];
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:1];

	if (base.y + ylen - 1 < cursor.y) {
		[vScrollBar setIndex:cursor.y + 1 - ylen];
	}
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-charLeft
{
	[self recenter:0];
	if (cursor.x < 1) {
		return self;
	}
	cursor.x--;
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:0];

	if (base.x > cursor.x) {
		[hScrollBar setIndex:cursor.x];
	}
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-charRight
{
	[self recenter:0];
	// FIXME handle horizontal scrolling and how to deal with max scroll
	cursor.x++;
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:1];

	if (base.x + xlen - 1 < cursor.x) {
		[hScrollBar setIndex:cursor.x + 1 - xlen];
	}
	if (base.x + xlen - 1 < cursor.x) {
		cursor.x = base.x + xlen - 1;
	}
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-wordLeft
{
	[self recenter:0];
	//unsigned oc = char_index;
	Point    b = base;

	do {
		if (char_index && char_index == line_index) {
			line_index = [buffer prevLine: line_index];
			char_index = [buffer getEOL: line_index];
			if (--cursor.y < b.y) {
				b.y = cursor.y;
			}
		}
		char_index = [buffer prevWord:char_index];
	} while (char_index && char_index < [buffer getEOT]
			 && ![buffer isWord:char_index]);

	cursor.x = [buffer charPos:line_index at:char_index];
	if (cursor.x < b.x) {
		b.x = cursor.x;
	} else if (cursor.x >= b.x) {
		b.x = center (b.x, xlen);
	}
	base.x = b.x;
	[vScrollBar setIndex:b.y];
	trackCursor (self);
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];

	return self;
}

-wordRight
{
	[self recenter:0];
	//unsigned oc = char_index;
	Point    b = base;

	if (char_index >= [buffer getEOT]) {
		return self;
	}
	if ([buffer getChar:char_index] == '\n') {
		while ([buffer getChar:char_index] == '\n') {
			char_index = line_index = [buffer nextLine:line_index];
			if (++cursor.y >= b.y + ylen) {
				b.y = cursor.y + 1 - ylen;
			}
			if (char_index >= [buffer getEOT]) {
				break;
			}
			if (![buffer isWord:char_index]) {
				char_index = [buffer nextWord: char_index];
			}
		}
	} else {
		char_index = [buffer nextWord: char_index];
	}

	cursor.x = [buffer charPos:line_index at:char_index];
	if (cursor.x < b.x) {
		b.x = cursor.x;
	} else if (cursor.x >= b.x) {
		b.x = center (b.x, xlen);
	}
	base.x = b.x;
	[vScrollBar setIndex:b.y];
	trackCursor (self);
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];

	return self;
}

-moveBOT
{
	line_index = base_index = char_index = 0;;
	cursor = nil;
	base.x = 0;
	[vScrollBar setIndex:0];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-moveEOT
{
	char_index = [buffer getEOT];
	line_index = [buffer getBOL:char_index];
	cursor.x = [buffer charPos:line_index at:char_index];
	cursor.y = line_count;
	[self recenter:0];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-moveBOS
{
	line_index = base_index;
	cursor.y = base.y;
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-moveEOS
{
	unsigned count = line_count - base.y;
	if (count > ylen - 1) {
		count = ylen - 1;
	}
	line_index = [buffer nextLine:base_index :count];
	cursor.y = base.y + count;
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self trackCursor:1];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-moveBOL
{
	char_index = line_index;
	cursor.x = 0;
	base.x = 0;
	[self recenter:0];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-moveEOL
{
	char_index = [buffer getEOL:line_index];
	cursor.x = [buffer charPos:line_index at:char_index];
	[self recenter:0];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-(void)onScroll:(id)sender
{
	base.x = scroll.x;
	if (base.y != scroll.y) {
		[self scrollTo:scroll.y];
	}
}

-setVerticalScrollBar:(ScrollBar *)scrollbar
{
	[super setVerticalScrollBar:scrollbar];
	[vScrollBar setRange:line_count];
	[vScrollBar setPageStep: ylen];
	return self;
}

-recenter:(int) force
{
	if (force || cursor.y < base.y || cursor.y - base.y >= ylen) {
		[self scrollTo: center (cursor.y, ylen)];
	}
	return self;
}

-gotoLine:(unsigned) line
{
	if (line > cursor.y) {
		line_index = [buffer nextLine:line_index :line - cursor.y];
	} else if (line < cursor.y) {
		line_index = [buffer prevLine:line_index :cursor.y - line];
	}
	cursor.y = line;
	char_index = [buffer charPtr:line_index at:cursor.x];
	[self recenter:0];
	[self moveCursor: {cursor.x - base.x, cursor.y - base.y}];
	return self;
}

-highlightLine
{
	selection.start = line_index;
	selection.length = [buffer nextLine: line_index] - line_index;
	if (!selection.length) {
		selection.length = [buffer getEOL: line_index] - line_index;
	}
	return self;
}

-(string)getWordAt:(Point) pos
{
	if (pos.x < 0 || pos.y < 0 || pos.x >= xlen || pos.y >= ylen) {
		return nil;
	}
	pos.x += base.x;
	unsigned lind = [buffer nextLine:base_index :pos.y];
	unsigned cind = [buffer charPtr:lind at:pos.x];
	eb_sel_t word = [buffer getWord: cind];
	return [buffer readString:word];
}

@end
