/*
 ViTA - Vi on TextArea by A Lee

  Author: A Lee <alee@debian.org>
  Date: 2007-02-07
  Usage: See http://alee.qubit.name/misc/vita.html

 Copyright 2006 (C) A Lee <alee@debian.org>. All rights reserved.

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of
  the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  The license can be found at http://www.gnu.org/licenses/gpl.txt
 */

var viMode = 'Insert';
var viRepeat = '';
var viBuffer = '';
var viCommand = '';
var viCaretPos = 1; // 캐럿의 컬럼 번호
var viVisualMode = 0; // Visual 모드에서 캐럿이 이동한 거리
var viCreatedKey = 0;
var viInputLang = 'En';
var viCmdLine = []; // command line 히스토리
var viCmdHPos = 0;
var viLastFind = '';
var viIEFixPos = null; // for IE


function viBeep() {
	try { // mozilla
		java.awt.Toolkit.getDefaultToolkit().beep();
	} catch (e) {};
}

// 명령(Normal) 모드에서 text field f에서 명령 c가 입력되었을 경우 실행되는 함수입니다.

function viRunCommand(f, c) {
	var m, n = viRepeat ? (1*viRepeat) : 1; // n: 명령을 반복할 횟수
	if (c != 'g' && viRepeat == 'g') viRepeat = '';

	if (c.length>1) { // 명령의 길이가 2글자 이상일 경우 쪼개서 하나씩 실행
		for (m=0; m<c.length; m++) viRunCommand(f, c.charAt(m));
		return true;
	}


	if (viCommand == 'r') {
		viInsertChar(f, c);
		viMoveSelection(f, -1, 0);
		c = viRepeat = viCommand = '';
	}
	else if (viCommand == 'R') {
		viInsertChar(f, c);
		if (viGetText(f, 1) != '\n') viMoveSelection(f, 0, 1);
		else viCommand = '';
		c = viRepeat = '';
	}
	else if (viCommand == 'f') {
		if ((m=viFindText(f, c, n)) < viFindText(f, '\n', 1)) viMoveCaret(f, m);
		c = viRepeat = viCommand = '';
	}
	else if (viCommand == 'F') {
		if ((m=viFindText(f, c, -n)) > viFindText(f, '\n', -1)) viMoveCaret(f, m);
		c = viRepeat = viCommand = '';
	}
	else if (viCommand == 'C') {
		if (c == "\n") {
			if (document.selection && viIEFixPos) {
				var sel = viIEFixPos.duplicate();
				sel.select();
				viIEFixPos=null;
			}
			
			c = viRepeat = viCommand = '';
			/**/
			var mycmd = viCmdLine[viCmdLine.length-1].replace(/^\s*/,'').replace(/\s*$/,'');
			var cc=mycmd.substr(0,1);
			var ca=mycmd.substr(1,mycmd.length-1);

			if (ca == '') ca = viLastFind;
			if (cc == '/' && ca) {
				m=viFindText(f, ca, n); viMoveCaret(f, m, m ? ca.length:null);
				viLastFind=ca;
			} else if (cc == '?' && ca) {
				m=viFindText(f, ca, -n); viMoveCaret(f, m, m ? ca.length:null);
				viLastFind=ca;
			} else if (cc == ':') {
				if (ca.match(/^[0-9]+$/)) {
					var ln=parseInt(ca);
					//viFindText(f, '\n',ln);
					m=viGoto(f,ln); viMoveCaret(f, m);
				} else if (ca == '$' || ca == '%') {
					m=viGoto(f,ca); viMoveCaret(f, m);
				} else {
					var chunk=ca.split(/\//);
					if (chunk.length > 1) {
						chunk[2] = chunk[2]||null;
						chunk[3] = chunk[3]||null;
						if (chunk[0] == 's') {
							b = null; d = null;
						} else if (chunk[0] == '%s' || chunk[0]=='1,%s') {
							b = '%'; d = null;
						} else if ((rn=chunk[0].match(/^([0-9]+)\s*,\s*([0-9]+)s/))) {
					       		b = parseInt(rn[1]); d = parseInt(rn[2]);
						} else {
							viBeep(); return;
						}
						if (chunk[3] && !chunk[3].match(/^[ig]+$/)) {
							viBeep(); return;
						}
						m = viSubReplace(f,b,d,chunk[1],chunk[2],chunk[3]);
						if (m[2]) alert('total '+ m[2]+' matches are found and replaced');
						//viMoveCaret(f,m);
					}
				}
			}
			if (mycmd == '') {
				viCmdLine.pop();
				viCmdHPos=viCmdLine.length-1;
			}
			return;
		} else {
			if (f.nextSibling && f.nextSibling.nodeName == 'DIV' &&
				f.nextSibling.className=='viCmdLine') {
				var cmd = f.nextSibling.firstChild;
				cmd.value+=c;
				return;
			} else {
				viCommand='';
			}
		}
	}
	else if (1+'^$behjklwBEGW(){}'.indexOf(c)) {
		viMoveCommand(f, c, n);
	}

	switch(c) {
		case '0': if (!viRepeat) viMoveCommand(f, c, n); break;
		case 'a': viInsertMode(f, c); break;
		case 'c':
			if (viCommand == 'c') viRunCommand(f, 'S');
			else if (viCommand) {
				if (viCommand == 'v') viInsertMode(f);
				viRepeat = viCommand = '';
				viVisualMode = 0;
			}
			else viCommand = 'c';
			break;

		case 'd':
			if (viCommand == 'd') {
				m = viCurrentLine(f,1);
				if (n > m) {
					viEditCommand(f, viCommand, viFindText(f, '\n', -1), viFindText(f, '\n', m+1)-1);
					viBuffer = viBuffer.substr(1) + '\n';
				}
				else viEditCommand(f, viCommand, viFindText(f, '\n', -1)+1, viFindText(f, '\n', n));
				viRepeat = viCommand = '';
				viRunCommand(f, '^');
			}
			else if (viCommand) {
				if (viCommand == 'v') viEditCommand(f, c);
				viRepeat = viCommand = '';
				viVisualMode = 0;
			}
			else viCommand = 'd';
			break;

		case 'f':
			if (viCommand) viRepeat = viCommand = '';
			else viCommand = 'f';
			break;

		case 'g':
			if (viRepeat == 'g') {
				viRepeat = '';
				viRunCommand(f, '1G');
			}
			else viRepeat = 'g';
			break;

		case 'i': viInsertMode(f, c); break;
		case 'o':
			if (viBlankLine(f)) viInsertChar(f, '\n');
			else viInsertChar(f, '\n', (m=viFindText(f, '\n', 1)), m-1);
			viMode = 'Insert';
			break;

		case 'p': viPasteCommand(f, c, n); break;
		case 'r':
			if (viCommand) viRepeat = viCommand = '';
			else viCommand = 'r';
			break;

		case 's': viInsertMode(f); break;
		case 'v':
			if (viCommand == 'v') viNormalMode(f);
			else if (!viCommand) {
				c = viGetText(f);
				viCommand = 'v';
				viRepeat = '';
				if (viVisualMode != 1-c.length) viVisualMode = c.length-1;
			}
			break;

		case 'x':
			if (viCommand == 'v') {
				viEditCommand(f, 'd');
				viRepeat = viCommand = '';
				viVisualMode = 0;
			}
			else viRunCommand(f, 'dl');
			break;

		case 'y':
			if (viCommand == 'y') {
				if (n > (m=viCurrentLine(f,1))) n = m+1;
				viEditCommand(f, viCommand, viFindText(f, '\n', -1)+1, viFindText(f, '\n', n)-1);
				viBuffer = viBuffer + '\n';
				viRepeat = viCommand = '';
			}
			else if (viCommand) {
				if (viCommand == 'v') {
					viEditCommand(f, 'y');
					viMoveSelection(f, 0, viVisualMode>0 ? -viVisualMode : viVisualMode);
					viVisualMode = 0;
				}
				viRepeat = viCommand = '';
			}
			else viCommand = 'y';
			break;

		case 'A': viRunCommand(f, '$a'); break;
		case 'C': viRunCommand(f, 'c$'); break;
		case 'D': viRunCommand(f, 'd$'); break;
		case 'F':
			if (viCommand) viRepeat = viCommand = '';
			else viCommand = 'F';
			break;

		case 'G': viRunCommand(f, '^'); break;
		case 'I': viRunCommand(f, '^i'); break;
		case 'J':
			while (n-- && viFindText(f, '\n', 2)) {
				e = viFindText(f, '\n', 1);
				viInsertChar(f, ' ', e, e);
				viMoveSelection(f, -1, 0);
				viCaretPos = -viFindText(f, '\n', -1);
			}
			break;

		case 'O':
			if (viBlankLine(f)) viInsertChar(f, '\n');
			else viInsertChar(f, '\n', (m=viFindText(f, '\n', -1))+1, m);
			viMoveSelection(f, -1, -1);
			viMode = 'Insert';
			break;

		case 'P': viPasteCommand(f, c, n); break;
		case 'R':
			if (viCommand) viRepeat = viCommand = '';
			else viCommand = 'R';
			break;

		case 'S':
			if (n > (m=viCurrentLine(f,1))) n = m+1;
			viEditCommand(f, 'c', viFindText(f, '\n', -1)+1, viFindText(f, '\n', n)-1);
			viBuffer = viBuffer + '\n';
			break;

		case 'u':
			f.focus();
			viKeypressCreator(f, 90,true); // ^Z
			break;

		case 'X':
			if (viCommand == 'v') {
				viEditCommand(f, 'd');
				viRepeat = viCommand = '';
				viVisualMode = 0;
			}
			else viRunCommand(f, 'dh');
			break;

		case '~':
			m = viFindText(f, '\n', 1);
			while (n-- && m--) {
				c = viGetText(f).charCodeAt(0);
				c = String.fromCharCode(c>64&&c<91 ? c+32 : c>96&&c<123 ? c-32 : c);
				viInsertChar(f, c);
				viMoveSelection(f, m?0:-1, m?1:0);
			}
			break;
		case ':':
		case '/':
		case '?':
			if (document.selection)
				viIEFixPos = document.selection.createRange().duplicate();
			viCommand = 'C';
			if (f.nextSibling && f.nextSibling.nodeName == 'DIV' &&
				f.nextSibling.className=='viCmdLine') {
				var cmd = f.nextSibling.firstChild;
				cmd.focus();
				cmd.value+=c;
			} else {
				var wrap = document.createElement('div');
				wrap.className = 'viCmdLine';
				var cmd = document.createElement('input');
				cmd.type='text';
				cmd.className = 'viCmdLine';
				cmd.style.width=f.offsetWidth;
				wrap.appendChild(cmd);
				if (!f.nextSibling) f.parentNode.appendChild(wrap);
				else f.parentNode.insertBefore(wrap,f.nextSibling);
				cmd.focus();
				cmd.value=c;
			}
			return;
			break;
	}

	if (c>0 && c<10 || c==0 && viRepeat) viRepeat = viRepeat + c;
	else if (c.charCodeAt(0)>32 && c.charCodeAt(0)<127) {
		if ('cdygfFrRvV'.indexOf(c) == -1) viRepeat = '';
		if ('cdygfFrRvV'.indexOf(c) == -1 && 'vV'.indexOf(viCommand) == -1) viCommand = '';
	}
}


// 명령(Normal) 모드에서 커서 이동 명령 c가 입력되었을 경우 실행되는 함수입니다.

function viMoveCommand(f, c, n) {
	var l, m;

	if (viCommand == 'v') viMoveSelection(f, viVisualMode>0 ? viVisualMode : 0, viVisualMode<0 ? viVisualMode : 0);

	switch(c) {
		case '0': if (!viBlankLine(f)) m = viFindText(f, '\n', -1)+1; break;
		case '^':
			if (!viBlankLine(f)) {
				if (viFindText(f, /\n\s*$/g, -1, -1)+1) m = viFindText(f, /\n\s*\S/g, -1, -1);
				else if (viGetText(f) == ' ') m = viFindText(f, /\S/, 1, 0);
			}
			break;

		case '$':
			if (viGetText(f, m=(viFindText(f, '\n', (n<(l=viCurrentLine(f,1)+1)?n:l))-1)) == '\n') m++;
			if (viCommand == 'c' || viCommand == 'd' || viCommand == 'y') m++;
			break;

		case 'b': m = viFindText(f, /\s\S|\W\w|\w[^\w\s]/g, -n, 1); break;
		case 'e': m = viFindText(f, /\S\s|\w\W|[^\w\s]\w/g, n, 0) + (!viCommand || viCommand == 'v' ? 0 : 1); break;
		case 'h': m = n < -(m=viFindText(f, '\n', -1)) ? -n : m+1; break;
		case 'j':
			if (n > (l=viCurrentLine(f,1))) n = l;
			var s = viFindText(f, '\n', n), e = viFindText(f, '\n', n+1);
			if (n) m = (viCaretPos && viCaretPos < e-s) ? viCaretPos+s : e-s==1 ? e : e-1;
			break;

		case 'k':
			if (n > (l=viCurrentLine(f)-1)) n = l;
			var s = -viFindText(f, '\n', -1-n), e = -viFindText(f, '\n', -n);
			if (n) m = (viCaretPos && viCaretPos < s-e) ? viCaretPos-s : s-e==1 ? -e : -e-1;
			break;

		case 'l': m = n < (m=viFindText(f, '\n', 1)) ? n : (viCommand == 'c' || viCommand == 'd') ? m : m-1; break;
		case 'w': m = viFindText(f, viCommand == 'c' ? /\S\s|\w\W|[^\w\s]\w/g : /\s\S|\W\w|\w[^\w\s]/g , n, 1); break;
		case 'B': m = viFindText(f, /\s\S/g, -n, 1); break;
		case 'E': m = viFindText(f, /\S\s/g, n, 0) + (!viCommand || viCommand == 'v' ? 0 : 1); break;
		case 'G':
			n = viRepeat ? n-viCurrentLine(f) : viCurrentLine(f,1);
			m = viFindText(f, '\n', n>0?n:n-1)+1;
			break;

		case 'W': m = viFindText(f, viCommand == 'c' ? /\S\s/g : /\s\S/g, n, 1); break;
		case '(': m = viFindText(f, /(^|[\.!?])(\s+\S|\n ?\n)/g, -n, -1); break;
		case ')': m = viFindText(f, /(^|[\.!?])(\n ?\n|\s+\S)/g, n, -1); break;
		case '{': m = viFindText(f, /[^ \n] ?\n ?\n/g, -n, 2); break;
		case '}': m = viFindText(f, /[^ \n] ?\n ?\n/g, n, 2); break;
	}

	if (!viCommand) {
		if (m) viMoveCaret(f, m);
		viCaretPos = (c == '$') ? 0 : (c != 'j' && c != 'k') ? -viFindText(f, '\n', -1) : viCaretPos;
	}
	else if (viCommand == 'v') {
		if (m) {
			if (viBlankLine(f)) {
				if (viVisualMode>0 && m>0) viVisualMode--;
				if (viVisualMode<0 && m<0) viVisualMode++;
			}
			viMoveCaret(f, m);
			viVisualMode += m;
			if (viBlankLine(f) && viVisualMode<0) viVisualMode--;
		}
		viCaretPos = (c == '$') ? 0 : (c != 'j' && c != 'k') ? -viFindText(f, '\n', -1) : viCaretPos;
		viMoveSelection(f, viVisualMode>0 ? -viVisualMode : 0, viVisualMode<0 ? -viVisualMode : 0);
	}
	else if (viCommand == 'c' || viCommand == 'd' || viCommand == 'y') {
		if (m) viEditCommand(f, viCommand, m<0?m:0, m<0?-1:m-1);
		else if (viCommand == 'c') viInsertMode(f, 'i');
		viCommand = '';
	}
	viRepeat = '';
}

// 현재 캐럿이 위치하고 있는 줄이 원래 비어 있어야 할 줄이면 1을 리턴합니다.

function viBlankLine(f) {
	if (viGetText(f) == ' ' && viGetText(f, -1) == '\n' && viGetText(f, 1) == '\n') return 1;
	return 0;
}


// 선택 영역의 시작/끝점을 m/n만큼 이동한 후 삽입(Insert) 모드로 들어갑니다.
// 만약 명령 c가 비어 있을 경우 명령 모드로 들어가기 전에 선택 영역을 삭제합니다.
// 만약 캐럿 위치가 원래 비어있어야 할 줄이면 현재 위치의 공백 문자를 삭제합니다.

function viInsertMode(f, c) {
	if (viBlankLine(f)) viInsertChar(f);
	else if (c == 'a') viMoveSelection(f, 1, 0);
	else if (c == 'i') viMoveSelection(f, 0, -1);
	else viEditCommand(f);
	viMode = 'Insert';
}


// 명령(Normal) 모드로 들어갑니다. 즉,
// 만약 선택 영역이 있을 경우에는 영역 크기를 줄여서 1칸으로 바꾸고,
// 선택 영역이 없을 경우에는 1칸짜리 선택 영역을 만듭니다.

function viNormalMode(f) {
	viMode = 'Normal';
	var c = viGetText(f);
	if (c.length > 1) {
		if (viVisualMode == c.length-1) viMoveSelection(f, c.length-1, 0);
		else viMoveSelection(f, 0, 1-c.length);
	}
	else if (viCommand == 'R') viMoveSelection(f, -1, -1);
	else if (!c) {
		if (viGetText(f, -1) != '\n') viMoveSelection(f, -1, 0);
		else if (viGetText(f, 1) != '\n') viMoveSelection(f, 0, 1);
		else viMoveCaret(f, 0); // 비어 있는 줄일 경우 스페이스를 하나 삽입
	}
	viRepeat = viCommand = '';
	viVisualMode = 0;
	viCaretPos = -viFindText(f, '\n', -1);
}


// r이 false면 현재 줄 번호를 리턴합니다. (맨 윗줄: 1)
// r이 true면 맨 아래에서 몇 번째 줄인지 리턴합니다. (맨 아랫줄: 0)

function viCurrentLine(f, r) {
	if (f.selectionStart+1) {
		var n = f.value.substr(0, f.selectionEnd).split('\n').length;
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var d = s.duplicate(); d.moveToElementText(f);
		s.setEndPoint('StartToStart', d);
		var n = s.text.split('\n').length;
	}
	return r ? f.value.split('\n').length-n : n;
}


// 선택 영역의 시작/끝점을 m/n만큼 이동한 후 문자 c를 삽입합니다.
// 선택 영역(캐럿 포함)이 있으면 선택 영역은 삭제되지만 버퍼에 저장하지는 않습니다.
// 만약 c가 비어있으면 백스페이스 문자가 삽입됩니다. 즉, 선택 영역이 삭제됩니다.

function viInsertChar(f, c, m, n) {
	if (m || n) viMoveSelection(f, m, n);
	if (f.selectionStart+1) viKeypressCreator(f, c?c.charCodeAt(0):0);
	else if (document.selection) {
		var s = document.selection.createRange(), t = s.text;
		s.text = c ? c : '';
		s.select();
	}
}


// 현재 캐럿 위치에 command c에 따라 viBuffer의 내용을 n번 삽입합니다.
// 만약 viBuffer의 맨 끝 글자가 \n이면 줄 단위로 처리합니다.

function viPasteCommand(f, c, n) {
	var m = 0, k = 0, t = '', b = viBlankLine(f);
	while (n--) t = t + viBuffer;
	if (b) viInsertChar(f);
	if (t.charAt(t.length-1) == '\n') {
		if (c == 'P') {
			if (b) m = 0;
			else m = viFindText(f, '\n', -1)+1;
		}
		if (c == 'p') {
			if (b) m = 0;
			else m = viFindText(f, '\n', 1);
			t = '\n' + t.substr(0, t.length-1);
			k = 1;
		}
	}
	else {
		if (!b && c == 'p') m = 1;
		k = t.length-1;
	}
	if (f.selectionStart+1) {
		var s = f.selectionStart+m, p = f.scrollTop;
		f.value = f.value.substr(0, s) + t + f.value.substr(s);
		f.scrollTop = p;
		f.setSelectionRange(s, s+1);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		s.move('character', m);
		s.text = t;
		s.move('character', -t.length);
		s.moveEnd('character', 1);
		s.select();
	}
	viMoveCaret(f, k);
}


// 선택 영역의 시작/끝점을 m/n만큼 이동한 후 선택 영역을 버퍼에 저장하고 삭제합니다.
// IE의 경우 선택 영역의 시작/끝 부분의 \n이 잘려 나가는 것을 방지하기 위해 선택 영역
// 맨 앞과 뒤에 스페이스를 하나씩 추가합니다.

function viEditCommand(f, c, m, n) {
	if (m || n) viMoveSelection(f, m, n);
	if (f.selectionStart+1) {
		viBuffer = f.value.substring(f.selectionStart, f.selectionEnd);
		if (c != 'y') viKeypressCreator(f);
	}
	else if (document.selection) {
		var s = document.selection.createRange(), d = s.duplicate();
		s.setEndPoint('EndToStart', d);
		s.text = ' ';
		s.setEndPoint('EndToEnd', d);
		s.setEndPoint('StartToEnd', d);
		s.text = ' ';
		s.setEndPoint('StartToStart', d);
		viBuffer = s.text.substr(1, s.text.length-2).replace(/\r/g, '');
		s.text = (c == 'y') ? viBuffer : '';
		if (c == 'y') s.moveStart('character', -viBuffer.length);
		s.select();
	}
	if (c == 'c') viMode = 'Insert';
	if (c == 'd') {
		if (viGetText(f, 1) != '\n') viMoveSelection(f, 0, 1);
		else if (viGetText(f, -1) != '\n') viMoveSelection(f, -1, 0);
		viMoveCaret(f, 0);
	}
	if (c == 'y' && (m || n)) viMoveSelection(f, -m, -n);
	viBuffer = viBuffer.replace('\n \n', '\n\n');
}


// n이 비어 있을 경우 선택 영역을 리턴하고, 그렇지 않을 경우 현재 캐럿 위치에서부터 n번째 
// 글자를 리턴합니다. 만약 n번째 글자가 텍스트 영역을 벗어날 경우에는 '\n'을 리턴합니다.

function viGetText(f, n) {
	if (f.selectionStart+1) {
		if (!n) return f.value.substring(f.selectionStart, f.selectionEnd);
		if (n<0 && f.selectionStart+n+1>0) return f.value.charAt(f.selectionStart+n);
		if (n>0 && f.selectionEnd+n-1<f.value.length) return f.value.charAt(f.selectionEnd+n-1);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		if (!n) return s.text ? s.text.replace(/\r/g, '') : s.compareEndPoints('StartToEnd', s) ? '\n' : '';
		var d = s.duplicate(); d.moveToElementText(f);
		if (n<0) {
			s.moveStart('character', n);
			if (s.compareEndPoints('StartToStart', d) != -1)
				if (s.text.charAt(0) && s.text.charAt(0) != '\r') return s.text.charAt(0);
		}
		if (n>0 && s.moveEnd('character', n) && s.compareEndPoints('EndToEnd', d) != 1) {
			s.setEndPoint('StartToEnd', s);
			s.moveStart('character', -1);
			if (s.text.charAt(0) && s.text.charAt(0) != '\r') return s.text.charAt(0);
		}
	}
	return '\n';
}

// 몇번째줄로 간다.
// e은 상대적인 값인지 절대값인지 FIXME
function viGoto(f, n, e) {
	var r = 1;

	if (f.selectionStart+1) {
		var t = ' \n' + f.value.substr(0, f.selectionStart+1);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var d = s.duplicate(); d.moveToElementText(f);
		s.setEndPoint('StartToStart', d);
		var t = ' \n' + s.text.replace(/\r/g, ''); // IE의 경우 \r\n을 쓰므로 \r을 삭제
	}

	if (typeof n == 'string' && (n == '$' || n == '%')) {
		ta = ' \n' + f.value.replace(/\r/g, '');
		r = 1+ta.lastIndexOf('\n');
		m= (r) ? r-1-(t.length-2) : 0;
		if (!m) viBeep();
		return m;
	}
	if (n >= 0) {
		n=n==0 ? 1:n;
		ta = ' \n' + f.value.replace(/\r/g, '');
		while (r && n--) {rr = 1+ta.indexOf('\n', r); if (rr == 0) break; r = rr;}
		m= (r) ? r-1-(t.length-2) : 0;
		if (!m) viBeep();
		return m;
	}
	viBeep();
	return 0;
}

function viSubReplace(f, b, d, re, sub,o) {
	// XXX FIXME
	var r = 1;
	var exp;

	if (typeof re == 'string') {
		try { exp = new RegExp(re,o||''); } catch (e) { viBeep(); return 0; }
	} else {
		exp = re;
	}
	if (f.selectionStart+1) {
		var t = ' \n' + f.value.substr(0, f.selectionStart+1);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var sd = s.duplicate(); sd.moveToElementText(f);
		s.setEndPoint('StartToStart', sd);
		var t = ' \n' + s.text.replace(/\r/g, ''); // IE의 경우 \r\n을 쓰므로 \r을 삭제
	}

	if (b == '%') {
		if (sub) {
			mm = viSubReplace(f,b,d,exp,null,o);
			fv = f.value.replace(/\r/g,'');
			ta = fv.replace(exp, sub);
			if (ta != fv) {
				del = fv.length - ta.length;
				tan=ta.substring(mm[0]+t.length-2-1,mm[1]+t.length-2-del);
				viEditCommand(f,'d',mm[0],mm[1]); // 지우고
				viBuffer=tan; // 버퍼에 넣고
				viPasteCommand(f,'P',1); // 붙인다.
				// 한꺼번에 replace()하지 말고 하나하나 하는게 나을까?
			}
			if (!mm) viBeep();
			return mm;
		} else {
			b=1;
			d='%';
		}
	}

	if (b == null && d == null) sr = t.length-2 , er = vFindText(f,'\n') + t.length-2;
	else {
		if (b >= 0) {
			b = b == 0 ? 1:b;
			sr = viGoto(f,b) + t.length-2;
		}
		if (d > b && d >= 0) {
			er = viGoto(f,d+1) + t.length-2 -1;
		}
		if (d == '%') {
			er = viGoto(f,'%') + t.length-2;
		}
	}
	// search
	if (sub == null) {
		ta = ' \n' + f.value.replace(/\r/g, '').substring(sr,er);
		m = 1;
		count = 0;
		if (o && o.match(/g/)) {
			rs = rr = exp.exec(ta);
			while (rr) { r = exp.exec(ta); if (!r) break; count++;rr=r;}
			ms = rs ? 2+rs.index - t.length:0;
			me = rr ? m+rr.index +rr[0].length - t.length:0;
		}
		else {
			rr = exp.exec(t);
			if (rr) {
				ms = m+rr.index - t.length;
				me = ms + rr[0].length;
				count = 1;
			} else ms = me = 0;
		}
		exp.lastIndex = 0; // 다음 검색을 위한 초기화
		if (!m) viBeep();
		
		return [ms,me,count];
	}
	// XXX
	mm = viSubReplace(f,b,d,exp,null,o);
	fv = f.value.replace(/\r/g,'');
	ta0 = fv.substring(0,mm[0]+t.length-2-1);
	ta = fv.substring(mm[0]+t.length-2-1,mm[1]+t.length-2);
	ta1 = ta.replace(exp, sub);
	if (ta != ta1) {
		del = ta.length - ta1.length;
		tan=(ta0 + ta1).substring(mm[0]+t.length-2-1,mm[1]+t.length-2-del);
		viEditCommand(f,'d',mm[0],mm[1]); // 지우고
		viBuffer=tan; // 버퍼에 넣고
		viPasteCommand(f,'P',1); // 붙인다.
		// 한꺼번에 replace()하지 말고 하나하나 하는게 나을까?
	}
	if (!mm) viBeep();
	return mm;
}

// 현재 캐럿 위치로부터 문자(또는 정규식) c를 순방향(n>0) 또는 역방향(n<0)으로
// 검색하여 현재 캐럿 위치를 기준으로 n번째로 검색된 위치를 리턴합니다.
// c가 문자일 경우 해당 문자의 위치를 리턴하고, 정규식일 경우에는 매치된 문자열에서
// m>0인 경우 m번째 문자, m<0인 경우 뒤에서부터 -m번째의 문자의 위치를 리턴합니다.
// 검색에 실패할 경우 c가 문자열이면 0을, 정규식이면 텍스트의 맨 끝 위치를 리턴합니다.

function viFindText(f, c, n, m) {
	var r = 1;

	if (f.selectionStart+1) {
		//var t = ' \n' + f.value.substr(0, f.selectionEnd);
		var t = ' \n' + f.value.substr(0, f.selectionStart+1);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var d = s.duplicate(); d.moveToElementText(f);
		s.setEndPoint('StartToStart', d);
		var t = ' \n' + s.text.replace(/\r/g, ''); // IE의 경우 \r\n을 쓰므로 \r을 삭제
	}
	if (n < 0) {
		if (typeof(c) == 'string') {
			while (r!=t.length+1 && n++) r = t.length-t.lastIndexOf(c, t.length-r-1);
			m= (r == t.length+1) ? 0 : 1-r;
			if (!m) viBeep();
			return m;
		}
		else {
			var a = new Array();
			t = t.substr(0, t.length-1); // 정규식의 경우 뒤에서부터 검색하는 것이 불가능하므로
			while (r=c.exec(t)) a.push(c.lastIndex); // 전체를 검색해 그 위치를 배열에 집어넣음
			if (a.length+n < 0) return -t.length+2;
			c.lastIndex = a[a.length+n-1];
			r = c.exec(t);
			m = (m<0) ? m+r.index+r[0].length-t.length : m+r.index-t.length;
			c.lastIndex = 0; // 다음 검색을 위한 초기화
			if (!m) viBeep();
			return m;
		}
	}
	if (n > 0) {
		t = f.value.replace(/\r/g, '').substr(t.length-3) + '\n ';
		if (typeof(c) == 'string') {
			while (r && n--) r = 1+t.indexOf(c, r);

			m= (r) ? r-1 : 0;
			if (!m) viBeep();
			return m;
		}
		else {
			if (!m) { m = 1; t = t.substr(1); }
			while (r && n--) r = c.exec(t);
			m = (r) ? (m<0) ? m+r.index+r[0].length : m+r.index : t.length-2;
			c.lastIndex = 0; // 다음 검색을 위한 초기화
			if (!m) viBeep();
			return m;
		}
	}
	viBeep(),viBeep(); //
}


// 선택 영역의 시작/끝점을 m/n만큼 이동합니다.

function viMoveSelection(f, m, n) {
	if (f.selectionStart+1) {
		f.setSelectionRange(f.selectionStart+m, f.selectionEnd+n);
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var d = s.duplicate(); d.moveToElementText(f);
		if (n<0) { s.moveStart('character', m); s.moveEnd('character', n); }
		else { s.moveEnd('character', n); s.moveStart('character', m); }
		if (s.compareEndPoints('StartToStart', d) == -1) s.setEndPoint('StartToStart', d);
		if (s.compareEndPoints('EndToEnd', d) == 1) s.setEndPoint('StartToEnd', d);
		s.select();
	}
}


// 캐럿(1칸짜리 선택 영역)을 현재 위치로부터 n칸 이동합니다.
// 만약 현재 캐럿 위치가 원래 비어있어야 할 줄이면 이동하기 전에 공백 문자를 삭제합니다.
// 만약 캐럿을 이동할 위치가 비어있는 줄이면 캐럿 모양을 만들기 위해 빈칸을 한 개 삽입합니다.
// FF의 경우 setSelectionRange()만으로 선택 영역을 이동하는 경우 선택 영역이 화면을 벗어나도
// 자동으로 스크롤되지 않기 때문에 스크롤을 위해 캐럿 위치에 키 이벤트를 만들어 넣습니다.

function viMoveCaret(f, n, l) {
	if (l==null) l=1;
	if (f.selectionStart+1) {
		var s = f.selectionStart, e = f.selectionEnd, c = f.value.charAt(s+n);
		if (viBlankLine(f) && (viCommand != 'v' || viVisualMode)) {
			if (!n) return 1;
			viKeypressCreator(f);
			if (n > 0) n--;
		}
		if (e+n > f.value.length && f.value.charAt(f.value.length-1) != '\n') {
			s = f.value.length-n-1;
			e = f.value.length-n+l-1;
		}
		else if (!c || c == '\n') e = s;
		if (s==e) f.setSelectionRange(s+n, e+n);
		else f.setSelectionRange(s+n, s+n+1);
		viKeypressCreator(f, s==e?32:f.value.charCodeAt(s+n));
		f.selectionStart--;
		f.selectionEnd+=l-1; // magic
	}
	else if (document.selection) {
		var s = document.selection.createRange();
		var d = s.duplicate(); d.moveToElementText(f);
		if (viBlankLine(f) && (viCommand != 'v' || viVisualMode)) {
			if (!n) return 1;
			s.text = '';
			if (n > 0) n--;
		}
		s.move('character', n);
		s.moveEnd('character', 1);
		if (s.compareEndPoints('StartToStart', d) == -1) s.setEndPoint('StartToStart', d);
		if (s.compareEndPoints('EndToEnd', d) == 1) {
			s.setEndPoint('StartToEnd', d); // l-1
			s.move('character', 0);
			s.moveStart('character', -1);
			if (!s.text) s.move('character', 1);
		}
		if (!s.text) {
			s.move('character', 0);
			s.text = ' ';
			s.moveStart('character', -1);
		}
		s.select();
	}
}


// Event creater and handlers

function viKeypressCreator(f, c, control) {
	var cancel=false,bubbles=false,type='keypress';
	var k = document.createEvent('KeyEvents');
	var kc = c?0:8, cc=c?c:0;
	if (control==null) control=false;
	else cancel=true,bubbles=true,type='keydown',kc=c,cc=0;

	k.initKeyEvent(type,bubbles,cancel,
			null,control,0,0,0,kc,cc);
			//null,control,0,0,0,c?0:8,c?c:0);
	viCreatedKey = 1; f.dispatchEvent(k); viCreatedKey = 0;
}

function viKeypressHandler(e) {
	if (window.event) var e = window.event, f = e.srcElement, n = f.tagName;
	else  var f = e.target, n = f.nodeName;

  	var c = String.fromCharCode((e.which || e.which == 0) ? e.which : e.keyCode);
	if (!e.ctrlKey && viCreatedKey) return true;

/*
	var cc = e.charCode ? e.charCode : e.keyCode;
	var c = (cc >= 32 && cc <=126) ? String.fromCharCode(cc):'';
	//if (e.charCode == 90 && e.ctrlKey) alert('press='+e.cancelable+','+e.bubbles);
*/
	if (n == 'TEXTAREA') {
		var s = viGetText(f);
		if (!s) viMode = 'Insert'; // 선택 영역이 없으면 Insert 모드
		if (viMode == 'Normal' && !e.ctrlKey) {
			if (e.preventDefault) e.preventDefault();
			if (s.length > 1 && viLastFind == '') {
				if (viCommand != 'V') viCommand = 'v';
				if (viVisualMode != 1-s.length) viVisualMode = s.length-1;
			}
			viRunCommand(f, c);
			return false;
		}
	}
}

function viKeydownHandler(e) {
	if (window.event) var e = window.event, f = e.srcElement, n = f.tagName;
	else  var f = e.target, n = f.nodeName;
	if (!e.ctrlKey && viCreatedKey) return true;
	//if (e.keyCode == 90 && e.ctrlKey) alert('down='+e.cancelable+','+e.bubbles+','+e.keyCode+','+e.charCode);

	if (n == 'TEXTAREA') {
		if (e.keyCode == 27) {
			viBeep();
			if (e.preventDefault) e.preventDefault();
			else e.cancelBubble = true;
			viInputLang = 'En';
			viNormalMode(f);
			if (f.tagName) {
				f.style.imeMode = 'inactive';
				f.focus();
			}
			return false;

		}

		var s = viGetText(f);
		if (!s) viMode = 'Insert'; // 선택 영역이 없으면 Insert 모드
		if (viMode == 'Normal' && !e.ctrlKey) {
			var c=null;
			if (e.keyCode >= 37 && e.keyCode <=40) {
				c = e.keyCode;
		       		c = c==37 ? 'h':(c==38 ? 'k':(c==39 ? 'l':"j"));
			} else if (e.keyCode == 13) {
				c = "\n";
			}
			if (c) {
				if (s.length > 1) {
					if (viCommand != 'V') viCommand = 'v';
					if (viVisualMode != 1-s.length) viVisualMode = s.length-1;
				}
				if (e.preventDefault) e.preventDefault();
				else e.cancelBubble = true;
				viRunCommand(f, c);
				return false;
			}
		}
	}
	if (f.type == 'text' && n == 'INPUT' || viMode == 'Insert' && n == 'TEXTAREA') {
		if (n == 'TEXTAREA' && e.keyCode == 9) { // TAB
			viInsertChar(f, "\t");
			if (e.preventDefault) e.preventDefault();
			else e.cancelBubble = true;
			return false;
		} else if (n == 'INPUT' && f.className == 'viCmdLine') {
			if (e.keyCode == 27) {
				viBeep();
				if (e.preventDefault) e.preventDefault();
				else e.cancelBubble = true;
				f.parentNode.previousSibling.focus();
				f.parentNode.parentNode.removeChild(f.parentNode);
			} else if (e.keyCode == 13) {
				if (e.preventDefault) e.preventDefault();
				else e.cancelBubble = true;
				viCmdLine.push(f.value);
				viCmdHPos=viCmdLine.length-1;
				f.value='';
				f.parentNode.previousSibling.focus();
				viRunCommand(f.parentNode.previousSibling, "\n");	
			} else if (e.keyCode == 38 && viCmdLine.length) {
				f.value=viCmdLine[viCmdHPos];
				viCmdHPos--;
				if (viCmdHPos < 0) viCmdHPos=0;	
			} else if (e.keyCode == 40 && viCmdLine.length) {
				viCmdHPos++;
				if (viCmdHPos > viCmdLine.length-1) viCmdHPos=viCmdLine.length-1;
				f.value=viCmdLine[viCmdHPos];
			}
		}
	} else if (false) {
		if (e.keyCode == 27) {
			f = document.getElementsByTagName('textarea')[0];
			viInputLang = 'En';
			if (f.tagName) {
				f.style.imeMode = 'inactive';
				f.focus();
			}
			viNormalMode(f);
		}
	}
}


// Register Event Handlers

var i = 0, s = document.getElementsByTagName('script');
while (i < s.length && s[i].src.substr(1+s[i].src.lastIndexOf('/')) != 'vita.js') i++;
if (document.addEventListener && i == s.length) {
	document.addEventListener('keypress', viKeypressHandler, true);
	document.addEventListener('keydown', viKeydownHandler, true);
}
else {
	document.onkeypress = viKeypressHandler;
	document.onkeydown = viKeydownHandler;
}
