function TagAutoComplete(opts) {
	var self = this;
	this.opts = {
		dictionary: null,
		minKeyPressDelay: 25,
		keyPressDelay: 0,
		resLimit: 5,
		delimiter: ','
	};
	this.vars = {
		tags: [],
		timeout: 0,
		rPos: null,
		value: '',
		cursor: null,
		word: '',
		wordStart: null,
		wordEnd: null,
		wordCount: 0
	};
	this.eTextInput = null;
	this.eResults = null;


	// handles key pressing
	this.handleKeyUp = function(evt) {
		// dictionary is loaded
		if (null !== dojo.isObject(self.opts.dictionary)) {
			switch (evt.keyCode) {
				// NOP, prevents suggesting after pressing ENTER
				case dojo.keys.ENTER:
					break;
				// close results
				case dojo.keys.ESCAPE:
					self.resetView();
					self.vars.tags = [];
					self.hideResults();
					break;
				// goes up in the results
				case dojo.keys.UP_ARROW:
					self.goUp();
					break;
				// goes down in the results
				case dojo.keys.DOWN_ARROW:
					self.goDown();
					break;
				default:
					// checks key press delay
					if (self.opts.keyPressDelay >= self.opts.minKeyPressDelay) {
						if (self.vars.timeout && self.vars.timeout>0) {
							clearTimeout(self.vars.timeout);
						}
						// waits 
						self.vars.timeout = setTimeout(function(){self.process();},self.opts.keyPressDelay);
					} else {
						// starts immediately
						self.process();
					}
			}
		}
		return true;	
	};


	// handles enter key
	this.handleKeyDown = function(evt) {
		// catches ENTER key
		if ((evt.keyCode === dojo.keys.ENTER) && (null !== self.vars.rPos)) {
			self.replaceView(self.vars.tags[self.vars.rPos]);
			self.hideResults();
		 	dojo.stopEvent(evt);
		}
		return true;
	};


	// handles mouse clicking
	this.handleClick = function(evt) {
		// dictionary is loaded
		if (null !== dojo.isObject(self.opts.dictionary)) {
			self.process();
		}
		return true;
	};


	// process suggestions
	this.process = function() {
		// checks text input content
		self.update();

		// input is not empty
		if (self.vars.word.length) {
			self.vars.tags = self.dictSearch(self.vars.word);
			// hides previous results
			self.hideResults();
			// shows new results
			self.showResults();
		} else {
			self.vars.tags = [];
			self.hideResults();
		}
		return true;
	};


	// parse text input content, gets cursor position, actually selected word and its borders
	this.update = function() {
		// old value
		var wC = self.vars.wordCount;

		self.vars.value = self.eTextInput.value;
		self.vars.cursor = self.getCursorPosition(self.eTextInput);
		self.vars.word = '';
		self.vars.wordStart = null;
		self.vars.wordEnd = null;
		self.vars.wordCount = 0;

		var str = self.vars.value;
		var cPos = self.vars.cursor;
		var pos = 0;
		// ignores spaces and delimiter characters
		while(pos < str.length) {
			// waits for the beginning of the word 
			if ((str.charAt(pos) !== ' ') && (str.charAt(pos) !== self.opts.delimiter)) {
				// gets end of the word
				var wEnd = str.indexOf(',',pos);
				if (wEnd === -1) {
					wEnd = str.length;
				}
				wEnd = wEnd - 1;
				// actually selected word
				if ((pos <= cPos - 1) && (cPos - 1 <= wEnd)) {
					self.vars.wordStart = pos;
					self.vars.wordEnd = wEnd;
					self.vars.word = str.substring(self.vars.wordStart, self.vars.cursor);
				}
				// skip the word
				pos = wEnd;
				self.vars.wordCount ++;
			}
			// next character
			pos ++;
		}

		// word count changed	
		if (wC !== self.vars.wordCount) {
			self.wordCountChanged(self.vars.wordCount);
		}

		return true;
	};


	// sets original value & cursor position
	this.resetView = function() {
		self.eTextInput.value = self.vars.value;
		self.setCursorPosition(self.eTextInput, self.vars.cursor);
		return true;
	};


	// sets new value & cursor position
	this.replaceView = function(tag) {
		//left side
		var left = self.vars.value.substring(0,self.vars.wordStart);
		// right
		var right = self.vars.value.substring(self.vars.wordEnd+1);
		// cursor
		var curPos = self.vars.wordStart + tag.length;

		self.eTextInput.value = left + tag + right;
		self.setCursorPosition(self.eTextInput, curPos);
		return true;
	};


	// looks up in the dictionary
	this.dictSearch = function(varStr) {
		var rTags = [];
		var dict = self.opts.dictionary;
		var strLength = varStr.length;

		if (strLength > 0) {
			var str = varStr.toLowerCase();

			if (strLength === 1) {
				// return first resLimit tags
				if (dict[str.charAt(0)]) {
					for (var key1 in dict[str.charAt(0)]) {
						for (var key2 in dict[str.charAt(0)][key1]) {
							rTags[rTags.length] = dict[str.charAt(0)][key1][key2];
							if (rTags.length === self.opts.resLimit) {
								return rTags;
							}
						}
					}
				}
			} else {
				// search for tags
				if (dict[str.charAt(0)] && dict[str.charAt(0)][str.charAt(1)]) {
					for (var key in dict[str.charAt(0)][str.charAt(1)]) {
						var word = dict[str.charAt(0)][str.charAt(1)][key];
			
						if (str === word.toLowerCase().substr(0, strLength)) {
							// skip actually written word
							if (str !== word.toLowerCase()) {
								rTags[rTags.length] = word;
								if (rTags.length === self.opts.resLimit) {
									return rTags;
								}
							}
						}
					}
				}
			}
		}
		return rTags;
	};


	// creates results
	this.showResults = function() {
		// at least one suggested word
		if (self.vars.tags.length) {
			var resList = dojo.create('ul',{},self.eResults);

			// items
			dojo.forEach(self.vars.tags,function(item,key) {
				var resItem = dojo.create('li',{},resList);
				var resLink = dojo.create('a',{href:'#',innerHTML:item},resItem);
				// selecting the result
				dojo.connect(resLink,'onclick',function(evt) {
					self.replaceView(item);
					self.hideResults();
					dojo.stopEvent(evt);
				});
				// moving mouse over results
				dojo.connect(resLink,'onmouseover',function(evt) {
					if (null !== self.vars.rPos) {
						dojo.removeClass(dojo.query('a',self.eResults)[self.vars.rPos],'selected');
					}
					dojo.addClass(this,'selected');
					self.vars.rPos = key;
				});
			});
		}
		self.vars.rPos = null;
		return true;
	};


	// removes results
	this.hideResults = function() {
		dojo.query('ul',self.eResults).orphan();
		self.vars.rPos = null;
		return true;
	};


	// listing down through results
	this.goDown = function() {
		var tagsCount = self.vars.tags.length;
		var pos = self.vars.rPos;

		if (tagsCount) {
			var links = dojo.query('a',self.eResults);
			if (pos !== null) {
				dojo.removeClass(links[pos],'selected');
				pos = pos + 1;
			} else {
				pos = 0;
			}
			if (pos === tagsCount) {
				pos = null;
				self.resetView();
			} else {
				dojo.addClass(links[pos],'selected');
				self.replaceView(self.vars.tags[pos]);
			}
		}
		self.vars.rPos = pos;
		return true;
	};


	// listing up through results
	this.goUp = function() {
		var tagsCount = self.vars.tags.length;
		var pos = self.vars.rPos;

		if (tagsCount) {
			var links = dojo.query('a',self.eResults);
			if (pos !== null) {
				dojo.removeClass(links[pos],'selected');
				pos = pos - 1;
			} else {
				pos = tagsCount - 1;
			}
			if (pos < 0) {
				pos = null;
				self.resetView();
			} else {
				dojo.addClass(links[pos],'selected');
				self.replaceView(self.vars.tags[pos]);
			}
		}
		self.vars.rPos = pos;
		return true;
	};


	// runs when word count has changed
	this.wordCountChanged = function(count) {
		
	};


	// gets current cursor position
	this.getCursorPosition = function(obj) {
		var pos = 0;
		if (document.selection) {
			obj.focus();
			var sel = document.selection.createRange();
			var selLength = document.selection.createRange().text.length;
			sel.moveStart('character',-obj.value.length);
			pos = sel.text.length - selLength;
		} else if (obj.selectionStart || obj.selectionStart === '0') {
			pos = obj.selectionStart;
		}
		return pos;
	};


	// sets new cursor position
	this.setCursorPosition = function(obj,pos) { 
		if(obj.createTextRange) { 
			var range = obj.createTextRange(); 
			range.move('character',pos);
			range.select(); 
		} else if(obj.selectionStart) { 
			obj.focus(); 
			obj.setSelectionRange(pos,pos); 
		}
		return true;
	};


	// constructor
	return function() {
		// gets elements
		self.eTextInput = dojo.byId(opts.elemTextInput);
		self.eResults = dojo.byId(opts.elemResults);

		// other options
		self.opts.dictionary = window[opts.dictionary];
		self.opts.keyPressDelay = opts.keyPressDelay;
		self.opts.delimiter = opts.delimiter;
		self.opts.resLimit = opts.resLimit;

		// turns off browser's autocomplete
		dojo.attr(self.eTextInput,{autocomplete:'off'});

		// handles events
		dojo.connect(self.eTextInput,'onkeyup',self.handleKeyUp);
		dojo.connect(self.eTextInput,'onkeydown',self.handleKeyDown);
		dojo.connect(self.eTextInput,'onclick',self.handleClick);
	}();
}
