var clientPC = navigator.userAgent.toLowerCase();
var clientVer = parseInt(navigator.appVersion);
var is_ie = ((clientPC.indexOf("msie") != -1) && (clientPC.indexOf("opera") == -1));
var is_nav = ((clientPC.indexOf('mozilla')!=-1) && (clientPC.indexOf('spoofer')==-1)
                && (clientPC.indexOf('compatible') == -1) && (clientPC.indexOf('opera')==-1)
                && (clientPC.indexOf('webtv')==-1) && (clientPC.indexOf('hotjava')==-1));
var is_moz = 0;
var is_win = ((clientPC.indexOf("win")!=-1) || (clientPC.indexOf("16bit") != -1));
var is_mac = (clientPC.indexOf("mac")!=-1);

function voidPutURL(context)
{       if (url=prompt('Введите ссылку','http://'))
        {
        var m=document.getElementById(context);
        if(m){
               m.focus();

             if ((clientVer >= 4) && is_ie && is_win)
             {
               sel = document.selection.createRange();

               sel.text = "<a href=\""+url+"\">"+sel.text+'</a>';
             } else
             {
               mozWrap(m, "<a href=\""+url+"\">",'</a>');
             }
             m.focus();
             }
         }
}

function voidPutTag2(context,tag)
{
        var m=document.getElementById(context);
        if(m){
               m.focus();
             if ((clientVer >= 4) && is_ie && is_win)
             {
               sel = document.selection.createRange();

               sel.text = "<"+tag+">"+sel.text+'</'+tag+'>';
             } else
             {
               mozWrap(m, "<"+tag+">", '</'+tag+'>');
             }
             m.focus();
             }
}

function voidPutTag(context,tag){
        var m=document.getElementById(context);
        if(m){

                if(document.selection){
                        m.focus();
                        sel=document.selection.createRange();
                        sel.text=tag;
                }
                else if(m.selectionStart || m.selectionStart=="0") {
                        var s=m.selectionStart;
                        var e=m.selectionEnd;
                        m.value=m.value.substring(0,s)+tag+m.value.substring(e,m.value.length);
                }else{
                        m.value += tag;
                }
                m.focus();
        }
}

function mozWrap(txtarea, open, close)
{
        var selLength = txtarea.textLength;
        var selStart = txtarea.selectionStart;
        var selEnd = txtarea.selectionEnd;
        if (selEnd == 1 || selEnd == 2)
                selEnd = selLength;

        var s1 = (txtarea.value).substring(0,selStart);
        var s2 = (txtarea.value).substring(selStart, selEnd)
        var s3 = (txtarea.value).substring(selEnd, selLength);
        txtarea.value = s1 + open + s2 + close + s3;
        return;
}


function MoveCenterScreen(objID)
{
	var innerHeight_ = window.innerHeight ? window.innerHeight : document.documentElement.offsetHeight;
	var obj = document.getElementById(objID);
	obj.style.left = ( document.body.clientWidth / 2 - obj.clientWidth / 2  + document.body.scrollLeft) + 'px';
	obj.style.top = ( document.documentElement.scrollTop + innerHeight_ / 2 - obj.clientHeight / 2 + document.body.scrollTop - 160) + 'px';
}

function showWindow(sId) {
	var obj = document.getElementById(sId);
	obj.style.display='block';
	MoveCenterScreen(sId);
}

function closeWindow(sId) {
	var obj = document.getElementById(sId);
	obj.style.display='none';
}

function showWindowStatus(sText) {
	var obj = document.getElementById('window_status_text');
	obj.innerHTML=sText;
	showWindow('window_status');
}

function closeWindowStatus() {
	closeWindow('window_status');
}


function ajaxVoteTopic(idTopic,value) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	document.getElementById('topic_rating_'+idTopic).innerHTML=req.responseJS.iRating;
            	if (req.responseJS.iRating<0) {
            		document.getElementById('topic_rating_'+idTopic).style.color="#d00000";
            	} else {
            		document.getElementById('topic_rating_'+idTopic).style.color="#008000";
            	}
            	if (value>0) {
            		showTopicVote('topic_vote_is_vote_up',idTopic);
            	} else {
            		showTopicVote('topic_vote_is_vote_down',idTopic);
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/voteTopic.php', true);
    req.send( { idTopic: idTopic, value: value } );
}

function ajaxVoteBlog(idBlog,value) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	document.getElementById('blog_rating_is_vote_down_'+idBlog).innerHTML=req.responseJS.iRating;
            	document.getElementById('blog_rating_is_vote_up_'+idBlog).innerHTML=req.responseJS.iRating;
            	document.getElementById('blog_count_vote_'+idBlog).innerHTML=req.responseJS.iCountVote;
            	if (req.responseJS.iRating<0) {
            		document.getElementById('blog_rating_is_vote_down_'+idBlog).style.color="#d00000";
            		document.getElementById('blog_rating_is_vote_up_'+idBlog).style.color="#d00000";
            	} else {
            		document.getElementById('blog_rating_is_vote_down_'+idBlog).style.color="#008000";
            		document.getElementById('blog_rating_is_vote_up_'+idBlog).style.color="#008000";
            	}
            	if (value>0) {
            		showBlogVote('blog_vote_is_vote_up',idBlog);
            	} else {
            		showBlogVote('blog_vote_is_vote_down',idBlog);
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/voteBlog.php', true);
    req.send( { idBlog: idBlog, value: value } );
}


function ajaxVoteComment(idComment,value) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	document.getElementById('comment_rating_'+idComment).innerHTML=req.responseJS.iRating;
            	if (req.responseJS.iRating<0) {
            		document.getElementById('comment_rating_'+idComment).style.color="#d00000";
            	} else {
            		document.getElementById('comment_rating_'+idComment).style.color="#008000";
            	}
            	if (value>0) {
            		showCommentVote('comment_vote_is_vote_up',idComment);
            	} else {
            		showCommentVote('comment_vote_is_vote_down',idComment);
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/voteComment.php', true);
    req.send( { idComment: idComment, value: value } );
}



function ajaxJoinLeaveBlog(idBlog,type) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);

            	document.getElementById('blog_user_count_'+idBlog).innerHTML=req.responseJS.iCountUser;

            	if (req.responseJS.sState=='join') {
            		document.getElementById('blog_action_join_'+idBlog).style.display="none";
            		document.getElementById('blog_action_leave_'+idBlog).style.display="inline";
            	}
            	if (req.responseJS.sState=='leave') {
            		document.getElementById('blog_action_join_'+idBlog).style.display="inline";
            		document.getElementById('blog_action_leave_'+idBlog).style.display="none";
            	}

            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/joinLeaveBlog.php', true);
    req.send( { idBlog: idBlog, type: type } );
}


function ajaxTopicFavourite(idTopic,type) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);

            	if (req.responseJS.bState) {
            		document.getElementById('topic_favourite_add').style.display="none";
            		document.getElementById('topic_favourite_del').style.display="inline";
            	}
            	if (!req.responseJS.bState) {
            		document.getElementById('topic_favourite_add').style.display="inline";
            		document.getElementById('topic_favourite_del').style.display="none";
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/topicFavourite.php', true);
    req.send( { idTopic: idTopic, type: type } );
}

function ajaxUserFrend(idUser,type) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);

            	if (req.responseJS.bState) {
            		document.getElementById('user_frend_add').style.display="none";
            		document.getElementById('user_frend_del').style.display="inline";
            	}
            	if (!req.responseJS.bState) {
            		document.getElementById('user_frend_add').style.display="inline";
            		document.getElementById('user_frend_del').style.display="none";
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/userFrend.php', true);
    req.send( { idUser: idUser, type: type } );
}

function ajaxBlogInfo(idBlog) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {

            } else {
            	if (document.getElementById('block_blog_info')) {
            		document.getElementById('block_blog_info').innerHTML='<p>'+req.responseJS.sText+'</p>';
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/blogInfo.php', true);
    req.send( { idBlog: idBlog } );
}

function ajaxQuestionVote(idTopic,idAnswer) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            document.getElementById('debug').innerHTML = req.responseText;
            closeWindowStatus();
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	if (document.getElementById('topic_question_area_'+idTopic)) {
            		document.getElementById('topic_question_area_'+idTopic).innerHTML='<p>'+req.responseJS.sText+'</p>';
            	}
            }
        }
    }
    showWindowStatus('Обработка голосования...');
    req.open(null, DIR_WEB_ROOT+'/include/ajax/questionVote.php', true);
    req.send( { idTopic: idTopic, idAnswer: idAnswer } );
}

function ajaxCommentDelete(idComment) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            document.getElementById('debug').innerHTML = req.responseText;
            closeWindowStatus();
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	if (document.getElementById('comment_content_'+idComment)) {
            		document.getElementById('comment_content_'+idComment).innerHTML='<font color="#c5c5c5">комментарий был удален</font>';
            	}
            	if (document.getElementById('comment_delete_'+idComment)) {
            		document.getElementById('comment_delete_'+idComment).style.display='none';
            	}
            	if (document.getElementById('comment_repair_'+idComment)) {
            		document.getElementById('comment_repair_'+idComment).style.display='inline';
            	}
            }
        }
    }
    showWindowStatus('Удаление комментария...');
    req.open(null, DIR_WEB_ROOT+'/include/ajax/commentDelete.php', true);
    req.send( { idComment: idComment } );
}

function ajaxCommentRepair(idComment) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            document.getElementById('debug').innerHTML = req.responseText;
            closeWindowStatus();
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	if (document.getElementById('comment_content_'+idComment)) {
            		document.getElementById('comment_content_'+idComment).innerHTML=req.responseJS.sCommentText;
            	}
            	if (document.getElementById('comment_delete_'+idComment)) {
            		document.getElementById('comment_delete_'+idComment).style.display='inline';
            	}
            	if (document.getElementById('comment_repair_'+idComment)) {
            		document.getElementById('comment_repair_'+idComment).style.display='none';
            	}
            }
        }
    }
    showWindowStatus('Восстановление комментария...');
    req.open(null, DIR_WEB_ROOT+'/include/ajax/commentRepair.php', true);
    req.send( { idComment: idComment } );
}

function ajaxVoteUser(idUser,value) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            //document.getElementById('debug').innerHTML = req.responseText;
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            } else {
            	msgNoticeBox.alert(req.responseJS.sMsgTitle,req.responseJS.sMsg);
            	document.getElementById('user_rating_is_vote_down_'+idUser).innerHTML=req.responseJS.iRating;
            	document.getElementById('user_rating_is_vote_up_'+idUser).innerHTML=req.responseJS.iRating;
            	document.getElementById('user_skill_'+idUser).innerHTML=req.responseJS.iSkill;
            	document.getElementById('user_count_vote_'+idUser).innerHTML=req.responseJS.iCountVote;
            	if (req.responseJS.iRating<0) {
            		document.getElementById('user_rating_is_vote_down_'+idUser).style.color="#d00000";
            		document.getElementById('user_rating_is_vote_up_'+idUser).style.color="#d00000";
            	} else {
            		document.getElementById('user_rating_is_vote_down_'+idUser).style.color="#008000";
            		document.getElementById('user_rating_is_vote_up_'+idUser).style.color="#008000";
            	}
            	if (value>0) {
            		showUserVote('user_vote_is_vote_up',idUser);
            	} else {
            		showUserVote('user_vote_is_vote_down',idUser);
            	}
            }
        }
    }
    req.open(null, DIR_WEB_ROOT+'/include/ajax/voteUser.php', true);
    req.send( { idUser: idUser, value: value } );
}

function ajaxUploadImg(value,sToLoad) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            document.getElementById('debug').innerHTML = req.responseText;
            closeWindowStatus();
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert('Ошибка','Возникли проблемы при загрузке изображения, попробуйте еще разок. И на всякий случай проверьте правильность URL картинки');
            	showWindow('window_load_img');
            } else {
            	voidPutTag(sToLoad,req.responseJS.sText);
            }
        }
    }
    closeWindow('window_load_img');
    showWindowStatus('Загрузка изображения...');
    req.open(null, DIR_WEB_ROOT+'/include/ajax/uploadImg.php', true);
    req.send( { value: value } );
}

function ajaxTextPreview(text,save) {
    var req = new JsHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            document.getElementById('debug').innerHTML = req.responseText;
            closeWindowStatus();
            if (req.responseJS.bStateError) {
            	msgErrorBox.alert('Ошибка','Возникли проблемы при обработке предпросмотра');
            } else {
            	document.getElementById('text_preview').innerHTML = req.responseJS.sText;
            }
        }
    }
    showWindowStatus('Обработка предпросмотра...');
    req.open(null, DIR_WEB_ROOT+'/include/ajax/textPreview.php', true);
    req.send( { text: text, save: save } );
}

function submitTags(sTag) {
	window.location=DIR_WEB_ROOT+'/tag/'+sTag+'/';
	return false;
}

function matchClass( objNode, strCurrClass ) {
	return ( objNode && objNode.className.length && objNode.className.match( new RegExp('(^|\\s+)(' + strCurrClass + ')($|\\s+)') ) );
}

function getElementsByClassName(objParentNode, strNodeName, strClassName){
	var nodes = document.getElementsByTagName(strNodeName);
	if(!strClassName){
		return nodes;
	}
	var nodesWithClassName = [];
	for(var i=0; i<nodes.length; i++){

		if(matchClass( nodes[i], strClassName )){
			//nodesWithClassName.push(nodes[i]);
			nodesWithClassName[nodesWithClassName.length] = nodes[i];
		}
	}
	return nodesWithClassName;
}

function checkAllTalk(checkbox) {
	if ($('form_talks_list')) {
		var checkboxes = getElementsByClassName($('form_talks_list'),'input' ,'form_talks_checkbox');
		if (checkbox.checked == true) {
			for (var i = 0, length = checkboxes.length; i < length; i++) {
				checkboxes[i].checked = true;
			}
		} else {
			for (var i = 0, length = checkboxes.length; i < length; i++) {
				checkboxes[i].checked = false;
			}
		}
	}
}

function hideCommentVoteAll(idComment) {
	document.getElementById('comment_vote_self_'+idComment).style.display='none';
	document.getElementById('comment_vote_is_vote_up_'+idComment).style.display='none';
	document.getElementById('comment_vote_is_vote_down_'+idComment).style.display='none';
	document.getElementById('comment_vote_ok_'+idComment).style.display='none';
	document.getElementById('comment_vote_anonim_'+idComment).style.display='none';
}

function showCommentVote(vote,idComment) {
	hideCommentVoteAll(idComment);
	document.getElementById(vote+'_'+idComment).style.display='inline';
}

function hideTopicVoteAll(idTopic) {
	document.getElementById('topic_vote_self_'+idTopic).style.display='none';
	document.getElementById('topic_vote_is_vote_up_'+idTopic).style.display='none';
	document.getElementById('topic_vote_is_vote_down_'+idTopic).style.display='none';
	document.getElementById('topic_vote_ok_'+idTopic).style.display='none';
	document.getElementById('topic_vote_anonim_'+idTopic).style.display='none';
}

function showTopicVote(vote,idTopic) {
	hideTopicVoteAll(idTopic);
	document.getElementById(vote+'_'+idTopic).style.display='inline';
}

function hideBlogVoteAll(idBlog) {
	document.getElementById('blog_vote_self_'+idBlog).style.display='none';
	document.getElementById('blog_vote_is_vote_up_'+idBlog).style.display='none';
	document.getElementById('blog_vote_is_vote_down_'+idBlog).style.display='none';
	document.getElementById('blog_vote_ok_'+idBlog).style.display='none';
	document.getElementById('blog_vote_anonim_'+idBlog).style.display='none';
}

function showBlogVote(vote,idBlog) {
	hideBlogVoteAll(idBlog);
	document.getElementById(vote+'_'+idBlog).style.display='inline';
}

function hideUserVoteAll(idUser) {
	document.getElementById('user_vote_self_'+idUser).style.display='none';
	document.getElementById('user_vote_is_vote_up_'+idUser).style.display='none';
	document.getElementById('user_vote_is_vote_down_'+idUser).style.display='none';
	document.getElementById('user_vote_ok_'+idUser).style.display='none';
	document.getElementById('user_vote_anonim_'+idUser).style.display='none';
}

function showUserVote(vote,idUser) {
	hideUserVoteAll(idUser);
	document.getElementById(vote+'_'+idUser).style.display='inline';
}



// для опроса
function addField(btn){
        tr = btn;
        while (tr.tagName != 'TR') tr = tr.parentNode;
        var newTr = tr.parentNode.insertBefore(tr.cloneNode(true),tr.nextSibling);
        checkFieldForLast();
}
function checkFieldForLast(){
        btns = document.getElementsByName('drop_answer');
        for (i = 0; i < btns.length; i++){
        	btns[i].disabled = false;
        }
        if (btns.length<=2) {
        	btns[0].disabled = true;
        	btns[1].disabled = true;
        }
}
function dropField(btn){
        tr = btn;
        while (tr.tagName != 'TR') tr = tr.parentNode;
        tr.parentNode.removeChild(tr);
        checkFieldForLast();
}

function submitSearch(sText) {
	window.location=DIR_WEB_ROOT+'/search/?searchfor='+sText;
	return false;
}

/**
 * Roar - Notifications
 *
 * Inspired by Growl
 *
 * @version		1.0.1
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

var Roar = new Class({

	Implements: [Options, Events, Chain],

	options: {
		duration: 4000,
		position: 'upperLeft',
		container: null,
		bodyFx: null,
		itemFx: null,
		margin: {x: 10, y: 10},
		offset: 5,
		className: 'roar',
		onShow: $empty,
		onHide: $empty,
		onRender: $empty,
		style: 'notice'
	},

	initialize: function(options) {
		this.setOptions(options);
		this.items = [];
		this.container = $(this.options.container) || document;
	},

	alert: function(title, message, options) {
		var params = Array.link(arguments, {title: String.type, message: String.type, options: Object.type});
		var items = [new Element('h3', {'html': $pick(params.title, '')})];
		if (params.message) items.push(new Element('p', {'html': params.message}));
		return this.inject(items, params.options);
	},

	inject: function(elements, options) {
		if (!this.body) this.render();
		options = options || {};

		var offset = [-this.options.offset, 0];
		var last = this.items.getLast();
		if (last) {
			offset[0] = last.retrieve('roar:offset');
			offset[1] = offset[0] + last.offsetHeight + this.options.offset;
		}
		var to = {'opacity': 1};
		to[this.align.y] = offset;

		var item = new Element('div', {
			'class': this.options.className,
			'opacity': 0
		}).adopt(
			new Element('div', {
				'class': this.options.className+'-bg',
				'opacity': 0.7
			}),
			elements
		);

		item.setStyle(this.align.x, 0).store('roar:offset', offset[1]).set('morph', $merge({
			unit: 'px',
			link: 'cancel',
			onStart: Chain.prototype.clearChain,
			transition: Fx.Transitions.Back.easeOut
		}, this.options.itemFx));

		var remove = this.remove.create({
			bind: this,
			arguments: [item],
			delay: 10
		});
		this.items.push(item.addEvent('click', remove));

		if (this.options.duration) {
			var over = false;
			var trigger = (function() {
				trigger = null;
				if (!over) remove();
			}).delay(this.options.duration);
			item.addEvents({
				mouseover: function() {
					over = true;
				},
				mouseout: function() {
					over = false;
					if (!trigger) remove();
				}
			});
		}
		item.inject(this.body).morph(to);
		return this.fireEvent('onShow', [item, this.items.length]);
	},

	remove: function(item) {
		var index = this.items.indexOf(item);
		if (index == -1) return this;
		this.items.splice(index, 1);
		item.removeEvents();
		var to = {opacity: 0};
		to[this.align.y] = item.getStyle(this.align.y).toInt() - item.offsetHeight - this.options.offset;
		item.morph(to).get('morph').chain(item.destroy.bind(item));
		return this.fireEvent('onHide', [item, this.items.length]).callChain(item);
	},

	empty: function() {
		while (this.items.length) this.remove(this.items[0]);
		return this;
	},

	render: function() {
		this.position = this.options.position;
		if ($type(this.position) == 'string') {
			var position = {x: 'center', y: 'center'};
			this.align = {x: 'left', y: 'top'};
			if ((/left|west/i).test(this.position)) position.x = 'left';
			else if ((/right|east/i).test(this.position)) this.align.x = position.x = 'right';
			if ((/upper|top|north/i).test(this.position)) position.y = 'top';
			else if ((/bottom|lower|south/i).test(this.position)) this.align.y = position.y = 'bottom';
			this.position = position;
		}
		this.body = new Element('div', {'class': this.options.className+'-body'}).inject(document.body);
		if (Browser.Engine.trident4) this.body.addClass(this.options.className+'-body-ugly');
		this.moveTo = this.body.setStyles.bind(this.body);
		this.reposition();
		if (this.options.bodyFx) {
			var morph = new Fx.Morph(this.body, $merge({
				unit: 'px',
				chain: 'cancel',
				transition: Fx.Transitions.Circ.easeOut
			}, this.options.bodyFx));
			this.moveTo = morph.start.bind(morph);
		}
		var repos = this.reposition.bind(this);
		window.addEvents({
			scroll: repos,
			resize: repos
		});
		this.fireEvent('onRender', this.body);
	},

	reposition: function() {
		var max = document.getCoordinates(), scroll = document.getScroll(), margin = this.options.margin;
		max.left += scroll.x;
		max.right += scroll.x;
		max.top += scroll.y;
		max.bottom += scroll.y;
		var rel = ($type(this.container) == 'element') ? this.container.getCoordinates() : max;
		this.moveTo({
			left: (this.position.x == 'right')
				? (Math.min(rel.right, max.right) - margin.x)
				: (Math.max(rel.left, max.left) + margin.x),
			top: (this.position.y == 'bottom')
				? (Math.min(rel.bottom, max.bottom) - margin.y)
				: (Math.max(rel.top, max.top) + margin.y)
		});
	}

});

/**
 * Autocompleter
 *
 * http://digitarald.de/project/autocompleter/
 *
 * @version		1.1.2
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

var Autocompleter = new Class({

	Implements: [Options, Events],

	options: {/*
		onOver: $empty,
		onSelect: $empty,
		onSelection: $empty,
		onShow: $empty,
		onHide: $empty,
		onBlur: $empty,
		onFocus: $empty,*/
		minLength: 1,
		markQuery: true,
		width: 'inherit',
		maxChoices: 10,
		injectChoice: null,
		customChoices: null,
		emptyChoices: null,
		visibleChoices: true,
		className: 'autocompleter-choices',
		zIndex: 42,
		delay: 400,
		observerOptions: {},
		fxOptions: {},

		autoSubmit: false,
		overflow: false,
		overflowMargin: 25,
		selectFirst: false,
		filter: null,
		filterCase: false,
		filterSubset: false,
		forceSelect: false,
		selectMode: true,
		choicesMatch: null,

		multiple: false,
		separator: ', ',
		separatorSplit: /\s*[,;]\s*/,
		autoTrim: false,
		allowDupes: false,

		cache: true,
		relative: false
	},

	initialize: function(element, options) {
		this.element = $(element);
		this.setOptions(options);
		this.build();
		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
			'delay': this.options.delay
		}, this.options.observerOptions));
		this.queryValue = null;
		if (this.options.filter) this.filter = this.options.filter.bind(this);
		var mode = this.options.selectMode;
		this.typeAhead = (mode == 'type-ahead');
		this.selectMode = (mode === true) ? 'selection' : mode;
		this.cached = [];
	},

	/**
	 * build - Initialize DOM
	 *
	 * Builds the html structure for choices and appends the events to the element.
	 * Override this function to modify the html generation.
	 */
	build: function() {
		if ($(this.options.customChoices)) {
			this.choices = this.options.customChoices;
		} else {
			this.choices = new Element('ul', {
				'class': this.options.className,
				'styles': {
					'zIndex': this.options.zIndex
				}
			}).inject(document.body);
			this.relative = false;
			if (this.options.relative) {
				this.choices.inject(this.element, 'after');
				this.relative = this.element.getOffsetParent();
			}
			this.fix = new OverlayFix(this.choices);
		}
		if (!this.options.separator.test(this.options.separatorSplit)) {
			this.options.separatorSplit = this.options.separator;
		}
		this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({
			'property': 'opacity',
			'link': 'cancel',
			'duration': 200
		}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);
		this.element.setProperty('autocomplete', 'off')
			.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this))
			.addEvent('click', this.onCommand.bind(this, [false]))
			.addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100}))
			.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100}));
	},

	destroy: function() {
		if (this.fix) this.fix.destroy();
		this.choices = this.selected = this.choices.destroy();
	},

	toggleFocus: function(state) {
		this.focussed = state;
		if (!state) this.hideChoices(true);
		this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);
	},

	onCommand: function(e) {
		if (!e && this.focussed) return this.prefetch();
		if (e && e.key && !e.shift) {
			switch (e.key) {
				case 'enter':
					if (this.element.value != this.opted) return true;
					if (this.selected && this.visible) {
						this.choiceSelect(this.selected);
						return !!(this.options.autoSubmit);
					}
					break;
				case 'up': case 'down':
					if (!this.prefetch() && this.queryValue !== null) {
						var up = (e.key == 'up');
						this.choiceOver((this.selected || this.choices)[
							(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')
						](this.options.choicesMatch), true);
					}
					return false;
				case 'esc': case 'tab':
					this.hideChoices(true);
					break;
			}
		}
		return true;
	},

	setSelection: function(finish) {
		var input = this.selected.inputValue, value = input;
		var start = this.queryValue.length, end = input.length;
		if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;
		if (this.options.multiple) {
			var split = this.options.separatorSplit;
			value = this.element.value;
			start += this.queryIndex;
			end += this.queryIndex;
			var old = value.substr(this.queryIndex).split(split, 1)[0];
			value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);
			if (finish) {
				var tokens = value.split(this.options.separatorSplit).filter(function(entry) {
					return this.test(entry);
				}, /[^\s,]+/);
				if (!this.options.allowDupes) tokens = [].combine(tokens);
				var sep = this.options.separator;
				value = tokens.join(sep) + sep;
				end = value.length;
			}
		}
		this.observer.setValue(value);
		this.opted = value;
		if (finish || this.selectMode == 'pick') start = end;
		this.element.selectRange(start, end);
		this.fireEvent('onSelection', [this.element, this.selected, value, input]);
	},

	showChoices: function() {
		var match = this.options.choicesMatch, first = this.choices.getFirst(match);
		this.selected = this.selectedValue = null;
		if (this.fix) {
			var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';
			this.choices.setStyles({
				'left': pos.left,
				'top': pos.bottom,
				'width': (width === true || width == 'inherit') ? pos.width : width
			});
		}
		if (!first) return;
		if (!this.visible) {
			this.visible = true;
			this.choices.setStyle('display', '');
			if (this.fx) this.fx.start(1);
			this.fireEvent('onShow', [this.element, this.choices]);
		}
		if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);
		var items = this.choices.getChildren(match), max = this.options.maxChoices;
		var styles = {'overflowY': 'hidden', 'height': ''};
		this.overflown = false;
		if (items.length > max) {
			var item = items[max - 1];
			styles.overflowY = 'scroll';
			styles.height = item.getCoordinates(this.choices).bottom;
			this.overflown = true;
		};
		this.choices.setStyles(styles);
		this.fix.show();
		if (this.options.visibleChoices) {
			var scroll = document.getScroll(),
			size = document.getSize(),
			coords = this.choices.getCoordinates();
			if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x;
			if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y;
			window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top));
		}
	},

	hideChoices: function(clear) {
		if (clear) {
			var value = this.element.value;
			if (this.options.forceSelect) value = this.opted;
			if (this.options.autoTrim) {
				value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);
			}
			this.observer.setValue(value);
		}
		if (!this.visible) return;
		this.visible = false;
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		this.observer.clear();
		var hide = function(){
			this.choices.setStyle('display', 'none');
			this.fix.hide();
		}.bind(this);
		if (this.fx) this.fx.start(0).chain(hide);
		else hide();
		this.fireEvent('onHide', [this.element, this.choices]);
	},

	prefetch: function() {
		var value = this.element.value, query = value;
		if (this.options.multiple) {
			var split = this.options.separatorSplit;
			var values = value.split(split);
			var index = this.element.getSelectedRange().start;
			var toIndex = value.substr(0, index).split(split);
			var last = toIndex.length - 1;
			index -= toIndex[last].length;
			query = values[last];
		}
		if (query.length < this.options.minLength) {
			this.hideChoices();
		} else {
			if (query === this.queryValue || (this.visible && query == this.selectedValue)) {
				if (this.visible) return false;
				this.showChoices();
			} else {
				this.queryValue = query;
				this.queryIndex = index;
				if (!this.fetchCached()) this.query();
			}
		}
		return true;
	},

	fetchCached: function() {
		return false;
		if (!this.options.cache
			|| !this.cached
			|| !this.cached.length
			|| this.cached.length >= this.options.maxChoices
			|| this.queryValue) return false;
		this.update(this.filter(this.cached));
		return true;
	},

	update: function(tokens) {
		this.choices.empty();
		this.cached = tokens;
		var type = tokens && $type(tokens);
		if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) {
			(this.options.emptyChoices || this.hideChoices).call(this);
		} else {
			if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;
			tokens.each(this.options.injectChoice || function(token){
				var choice = new Element('li', {'html': this.markQueryValue(token)});
				choice.inputValue = token;
				this.addChoiceEvents(choice).inject(this.choices);
			}, this);
			this.showChoices();
		}
	},

	choiceOver: function(choice, selection) {
		if (!choice || choice == this.selected) return;
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		this.selected = choice.addClass('autocompleter-selected');
		this.fireEvent('onSelect', [this.element, this.selected, selection]);
		if (!this.selectMode) this.opted = this.element.value;
		if (!selection) return;
		this.selectedValue = this.selected.inputValue;
		if (this.overflown) {
			var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin,
				top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;
			if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);
			else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);
		}
		if (this.selectMode) this.setSelection();
	},

	choiceSelect: function(choice) {
		if (choice) this.choiceOver(choice);
		this.setSelection(true);
		this.queryValue = false;
		this.hideChoices();
	},

	filter: function(tokens) {
		return (tokens || this.tokens).filter(function(token) {
			return this.test(token);
		}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i'));
	},

	/**
	 * markQueryValue
	 *
	 * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents
	 *
	 * @param		{String} Text
	 * @return		{String} Text
	 */
	markQueryValue: function(str) {
		return (!this.options.markQuery || !this.queryValue) ? str
			: str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');
	},

	/**
	 * addChoiceEvents
	 *
	 * Appends the needed event handlers for a choice-entry to the given element.
	 *
	 * @param		{Element} Choice entry
	 * @return		{Element} Choice entry
	 */
	addChoiceEvents: function(el) {
		return el.addEvents({
			'mouseover': this.choiceOver.bind(this, [el]),
			'click': this.choiceSelect.bind(this, [el])
		});
	}
});

var OverlayFix = new Class({

	initialize: function(el) {
		if (Browser.Engine.trident) {
			this.element = $(el);
			this.relative = this.element.getOffsetParent();
			this.fix = new Element('iframe', {
				'frameborder': '0',
				'scrolling': 'no',
				'src': 'javascript:false;',
				'styles': {
					'position': 'absolute',
					'border': 'none',
					'display': 'none',
					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
				}
			}).inject(this.element, 'after');
		}
	},

	show: function() {
		if (this.fix) {
			var coords = this.element.getCoordinates(this.relative);
			delete coords.right;
			delete coords.bottom;
			this.fix.setStyles($extend(coords, {
				'display': '',
				'zIndex': (this.element.getStyle('zIndex') || 1) - 1
			}));
		}
		return this;
	},

	hide: function() {
		if (this.fix) this.fix.setStyle('display', 'none');
		return this;
	},

	destroy: function() {
		if (this.fix) this.fix = this.fix.destroy();
	}

});

Element.implement({

	getSelectedRange: function() {
		if (!Browser.Engine.trident) return {start: this.selectionStart, end: this.selectionEnd};
		var pos = {start: 0, end: 0};
		var range = this.getDocument().selection.createRange();
		if (!range || range.parentElement() != this) return pos;
		var dup = range.duplicate();
		if (this.type == 'text') {
			pos.start = 0 - dup.moveStart('character', -100000);
			pos.end = pos.start + range.text.length;
		} else {
			var value = this.value;
			var offset = value.length - value.match(/[\n\r]*$/)[0].length;
			dup.moveToElementText(this);
			dup.setEndPoint('StartToEnd', range);
			pos.end = offset - dup.text.length;
			dup.setEndPoint('StartToStart', range);
			pos.start = offset - dup.text.length;
		}
		return pos;
	},

	selectRange: function(start, end) {
		if (Browser.Engine.trident) {
			var diff = this.value.substr(start, end - start).replace(/\r/g, '').length;
			start = this.value.substr(0, start).replace(/\r/g, '').length;
			var range = this.createTextRange();
			range.collapse(true);
			range.moveEnd('character', start + diff);
			range.moveStart('character', start);
			range.select();
		} else {
			this.focus();
			this.setSelectionRange(start, end);
		}
		return this;
	}

});

/* compatibility */

Autocompleter.Base = Autocompleter;

/**
 * Autocompleter.Request
 *
 * http://digitarald.de/project/autocompleter/
 *
 * @version		1.1.2
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

Autocompleter.Request = new Class({

	Extends: Autocompleter,

	options: {/*
		indicator: null,
		indicatorClass: null,
		onRequest: $empty,
		onComplete: $empty,*/
		postData: {},
		ajaxOptions: {},
		postVar: 'value'

	},

	query: function(){
		var data = $unlink(this.options.postData) || {};
		data[this.options.postVar] = this.queryValue;
		var indicator = $(this.options.indicator);
		if (indicator) indicator.setStyle('display', '');
		var cls = this.options.indicatorClass;
		if (cls) this.element.addClass(cls);
		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
		this.request.send({'data': data});
	},

	/**
	 * queryResponse - abstract
	 *
	 * Inherated classes have to extend this function and use this.parent()
	 */
	queryResponse: function() {
		var indicator = $(this.options.indicator);
		if (indicator) indicator.setStyle('display', 'none');
		var cls = this.options.indicatorClass;
		if (cls) this.element.removeClass(cls);
		return this.fireEvent('onComplete', [this.element, this.request]);
	}

});

Autocompleter.Request.JSON = new Class({

	Extends: Autocompleter.Request,

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.request = new Request.JSON($merge({
			'url': url,
			'link': 'cancel'
		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(response) {
		this.parent();
		this.update(response);
	}

});

Autocompleter.Request.HTML = new Class({

	Extends: Autocompleter.Request,

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.request = new Request.HTML($merge({
			'url': url,
			'link': 'cancel',
			'update': this.choices
		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(tree, elements) {
		this.parent();
		if (!elements || !elements.length) {
			this.hideChoices();
		} else {
			this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) {
				var value = choice.innerHTML;
				choice.inputValue = value;
				this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
			}, this);
			this.showChoices();
		}

	}

});

/* compatibility */

Autocompleter.Ajax = {
	Base: Autocompleter.Request,
	Json: Autocompleter.Request.JSON,
	Xhtml: Autocompleter.Request.HTML
};

/**
 * Observer - Observe formelements for changes
 *
 * - Additional code from clientside.cnet.com
 *
 * @version		1.1
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */
var Observer = new Class({

	Implements: [Options, Events],

	options: {
		periodical: false,
		delay: 1000
	},

	initialize: function(el, onFired, options){
		this.element = $(el) || $$(el);
		this.addEvent('onFired', onFired);
		this.setOptions(options);
		this.bound = this.changed.bind(this);
		this.resume();
	},

	changed: function() {
		var value = this.element.get('value');
		if ($equals(this.value, value)) return;
		this.clear();
		this.value = value;
		this.timeout = this.onFired.delay(this.options.delay, this);
	},

	setValue: function(value) {
		this.value = value;
		this.element.set('value', value);
		return this.clear();
	},

	onFired: function() {
		this.fireEvent('onFired', [this.value, this.element]);
	},

	clear: function() {
		$clear(this.timeout || null);
		return this;
	},

	pause: function(){
		if (this.timer) $clear(this.timer);
		else this.element.removeEvent('keyup', this.bound);
		return this.clear();
	},

	resume: function(){
		this.value = this.element.get('value');
		if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this);
		else this.element.addEvent('keyup', this.bound);
		return this;
	}

});

var $equals = function(obj1, obj2) {
	return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2));
};
