/**
 * @author Sergey Chikuyonok (gonarch@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru) 
 */
/**
 * Class for searching and filtering elements on page
 * @constructor
 * @param {String, Element} search_elem Container to search
 * @param {String, Element} field_elem Container to search
 */
function PageSearch(search_elem, field_elem){
	this.ObjectName = 'PageSearch';
	this.setDefaultParams();
	this.setContainer(search_elem);
	this.setSearchField(field_elem);
	
	this.occurrences = [];
	this.lastSearchTime = 0;
	this.lastText = '';

	this.clearing = false;
	this.timer = null;
	
	this._markedElements=[];
}

/**
 * Set default params
 */
PageSearch.prototype.setDefaultParams=function(){
	this.params={
		mark_elem_name: 'u',
		mark_elem_class: 'search-result',
		
		/**
		 * Asterisk means 'mark all elements'.
		 * Can be empty (don't mark anything) or array with string values 'tag_name' or 'tag_name@class_name'
		 */
		found_elements: '*', 
		found_elements_class: 'found',
		filtered_container_class: 'filtered',
		word_start_only: false,
		min_text_length: 3,
		search_delay: 300,
		condition: 'or',
		exception: [] // слова исключения
	};
}

/**
 * Set search parameter
 * @param {String} name Parameter name
 * @param {Object} value Parameter value
 */
PageSearch.prototype.setParam=function(name, value){
	this.params[name]=value;
	if(name == 'found_elements'){
		this._createMarkingCache();
	}
}

/**
 * Return parameter value
 * @param {String} name Parameter name
 * @return {Object}
 */
PageSearch.prototype.getParam=function(name){
	return this.params[name];
}

/**
 * Set search container
 * @param {String, Element} elem Container to search
 */
PageSearch.prototype.setContainer=function(elem){
	this.container=this._getElement(elem);
}

/**
 * Return search container
 * @return {Element}
 */
PageSearch.prototype.getContainer=function(){
	return this.container;
}

/**
 * Set search field
 * @param {String, Element} elem Search field pointer or id
 */
PageSearch.prototype.setSearchField=function(elem){
	this.search_field=this._getElement(elem);
	this.attachEvents(this.search_field);
}

/**
 * Return search field pointer
 * @return {Element}
 */
PageSearch.prototype.getSearchField=function(){
	return this.search_field;
}

/**
 * Set matched elements array
 * @param {Array} occurrences Matched occurrences
 */
PageSearch.prototype.setOccurrences=function(occurrences){
	this.occurrences=occurrences;
}

/**
 * Get matched elements array
 * @return {Array}
 */
PageSearch.prototype.getOccurrences=function(){
	return this.occurrences;
}

/**
 * Set search string which will be used for finding occurrences
 * @param {String, RegExp} str
 */
PageSearch.prototype.setSearchString=function(str){
	this._searchStr=str;
}

/**
 * Get search string which is used for finding occurrences
 * @return {String, RegExp}
 */
PageSearch.prototype.getSearchString=function(){
	return this._searchStr;
}

/**
 * Attach events for search field
 * @param {Element} elem Search field pointer
 */
PageSearch.prototype.attachEvents=function(elem){
	var me=this;
	var f=function(evt){me.dispatchEvent(evt);}
	Common.Event.add(elem, 'keyup', f);
	Common.Event.add(elem, 'change', f);
	Common.Event.add(elem, 'search', f);
}

/**
 * Event dispatcher
 * @param {Event} evt
 */
PageSearch.prototype.dispatchEvent=function(evt){
	if((evt=Common.Event.normalize(evt))){
		switch(evt.type){
			case 'keyup':
			case 'keydown':
			case 'change':
				this.startSearch(evt.target.value);
				break;
			case 'search': //Safari extension
				if(!evt.target.value)
					this.clear();
		}
	}
}

/**
 * Start search (with delay)
 * @param {String} text Text to find
 */
PageSearch.prototype.startSearch=function(text){
	if (this.timer)
		clearTimeout(this.timer);
	var me = this;
	this.timer = setTimeout(function(){me.search(text);}, this.getParam('search_delay'));
}

/**
 * Find text
 * @param {String} text
 */
PageSearch.prototype.search=function(text){
	if(this._isClearing()){
		this.startSearch(text);
	}
	else if(text != this.lastText){
		this.clear();
		if(text.length >= this.getParam('min_text_length')){
			var _text = text.toLowerCase();
			// array with all the strings we are searching for

			var aExceptions = this.getParam('exception');
			if(aExceptions.length != 0)
			{
				var rg = new RegExp('('+aExceptions.join('|')+')', 'ig');
				text = text.replace(rg, ' ');
			}

//			if(text == '') this.startSearch('');

			var _subs = this._createSubstrings(text, this.getParam('min_text_length'));

			if(!_subs.length){
				this.onSearchEnd();
				return false;
			}

			this.aSubs = _subs; // стоки из поиска
			
			this.setSearchString(new RegExp('('+_subs.join('|')+')', 'i'));
			
			// searching
			this.setOccurrences(this.findOccurrences());
			this.markResults();
		}
		this.lastText = text;

		this.onSearch(this);
	}

	this.onSearchEnd();
}

/**
 * Событие "конец поиска", в дальнейшем, перебить в дальнейшем на что-то свое
 */
PageSearch.prototype.onSearchEnd = function(){ }

/**
 * Find occurrences of string in search container
 * @param {Element} [container] Container to search (used for recursions)
 */
PageSearch.prototype.findOccurrences = function(container){
	container=container||this.getContainer();
	var result=[];
	var condition=this.getParam('condition');	
	var node, oc, m, search_str=this.getSearchString(), elem;
	
	var ch=container.childNodes;
	for(var i=ch.length-1; i>=0; i--){
		node=ch[i];
		switch(node.nodeType){
			case 1:
				// Conditions (or,and)
				var bPush=false;
				if(condition == 'and' && this.aSubs.length != 1)
				{
					var sElem = node.innerHTML.toLowerCase();
					var iEq = 0;
					menu(this.aSubs, function(str){
						if(sElem.match(str)) iEq++;
					});
					if(iEq == this.aSubs.length) bPush=true;
				}
				else if(node.innerHTML.replace(/<\/?.+?>/g, ' ').toLowerCase().match(search_str))
				{
					bPush=true;
				}
				// проверяем а есть ли вообще такой текст внутри этой нодой и если есть - ищем дальше
				// ускоряет при сложных HTML-конструкциях и поиске уникального текста
				if(bPush){
					result=result.concat(this.findOccurrences(node));
				}
				break;
			case 3:
				if(node.nodeValue.length > 0){
					var elem_name=this.getParam('mark_elem_name');
					var elem_class=this.getParam('mark_elem_class');
					var f=10;
					
					if (m=search_str.exec(node.nodeValue)) result.push(node);
					
					/*
					while((m=search_str.exec(node.nodeValue))){
						var right_context=node.splitText(m.index + m[1].length);
						var selection_text = node.splitText(m.index);
						elem = document.createElement(elem_name);
						if(elem_class)
							elem.className = elem_class;
						elem.appendChild(selection_text);
						node.parentNode.insertBefore(elem, node.nextSibling);
						result.push(elem);
						node = right_context;
					}
					*/
				}
				break;
		}
	}
	
	return result;
}

/**
 * Mark container tree elements 
 */
PageSearch.prototype.markResults=function(){
	this._markedElements=[];
	if(this.getParam('found_elements')){
		var oc=this.getOccurrences(), elem, cont=this.getContainer();

		//find distinct nodes
		for(var i=0; i<oc.length; i++){
			elem=oc[i];
			while((elem=elem.parentNode) && elem != cont){
				if(this._allowMark(elem) && this._markedElements.contains(elem) == -1)
					this._markedElements.push(elem);
			}
		}
		
		//mark found nodes
		var _class=this.getParam('found_elements_class');
		if(_class){
			for(var i=0; i<this._markedElements.length; i++){
				Common.Class.add(this._markedElements[i], _class);
			}
		}
		
		Common.Class.add(this.getContainer(), this.getParam('filtered_container_class'));
	}
}

/**
 * Removes found marks from container elements
 */
PageSearch.prototype.unmarkResults=function(){
	if(this._markedElements.length){
		var _class=this.getParam('found_elements_class');
		if(_class){
			for(var i=0; i<this._markedElements.length; i++){
				Common.Class.remove(this._markedElements[i], _class);
			}
		}
	}
	Common.Class.remove(this.getContainer(), this.getParam('filtered_container_class'));
}

/**
 * Callback function fired when search is performed
 * @param {PageSearch} obj
 */
PageSearch.prototype.onSearch=function(obj){
	return obj;
}

/**
 * Check if last search call found something
 * @return {Boolean}
 */
PageSearch.prototype.isFound=function(){
	return (this.getOccurrences().length > 0);
}

/**
 * Clear search results
 */
PageSearch.prototype.clear=function(){
	this.clearing=true;
	this.unmarkResults();
	
	var oc=this.getOccurrences(), _o, _parent, parents=[];
	
	for(var i = oc.length-1; i >= 0; i--){
		if(oc[i]){
			_o = oc[i];
			if(_o && _o.firstChild){
				_parent = _o.parentNode;
				_parent.insertBefore(_o.firstChild, _o);
				_parent.removeChild(_o);
				
				if(parents.contains(_parent) == -1)
					parents.push(_parent);
			}
		}
	}
	
	
	//normalize node structure
	if(document.all && !window.opera){
		for(var i=0; i<parents.length; i++)
			parents[i].innerHTML = parents[i].innerHTML;
	}
	else{
		for(var i=0; i<parents.length; i++)
			parents[i].normalize();
	}
	
	this.setOccurrences([]);
	this.clearing=false;
}

/**
 * Creates helper cache structure to test if arbitrary element can be marked 
 */
PageSearch.prototype._createMarkingCache=function(){
	var val=this.getParam('found_elements');
	this._markStruct=[];
	
	if(val instanceof Array){
		var re_tag=/^(\w+)$/;
		var re_tag_with_class=/^(\w+)@([\w\-]+)$/;
		var _m;
		for(var i=0; i<val.length; i++){
			if((_m=re_tag_with_class.exec(val[i])))
				this._markStruct.push({tagName: _m[1].toUpperCase(), className: _m[2]});
			else if((_m=re_tag.exec(val[i])))
				this._markStruct.push({tagName: _m[1].toUpperCase()});
		}
	}
}

/** @return {Array} */
PageSearch.prototype._getMarkingCache=function(){
	return this._markStruct;
}

/**
 * Check if passed element can be marked as found
 * @param {Element} elem Element to test
 * @return {Boolean}
 */
PageSearch.prototype._allowMark=function(elem){
	var val=this.getParam('found_elements');
	if(val == '*')
		return true;
	else if(!val)
		return false;
	else if(val instanceof Array){
		var struct=this._getMarkingCache();
		for(var i=0; i<struct.length; i++){
			if(struct[i].tagName == elem.tagName){
				if(struct[i].className && !Common.Class.match(elem, struct[i].className))
					return false;
				return true;
			}
		}
	}
	return false;
}

/**
 * Returns if search processor clears result
 * @return {Boolean}
 */
PageSearch.prototype._isClearing=function(){
	return this.clearing;
}

/**
 * Creates substring helper objects
 * @param {String} text Text to search
 * @param {Number} min_text_length Minimum substring length
 * @return {Array}
 */
PageSearch.prototype._createSubstrings = function(text, min_text_length){
	var result=[];

	var _m = text.split(/([,\.]?\s)/g);
	//console.log(text,_m)

	var word_start = this.getParam('word_start_only');
	
	if(word_start){
		result.push(['\\b'+text, 0, word_start]);
		for(var i=0; i<_m.length; i++)
			if(_m[i].length >= min_text_length){
				result.push('\\b'+_m[i]);
			}
	}
	else{
		for(var i=0; i<_m.length; i++) 
			if(_m[i].length >= min_text_length)
				result.push(_m[i]);
	}
	return result;
}

/**
 * Returns element pointer
 * @param {String, Element} elem Element pointer or ID
 */
PageSearch.prototype._getElement=function(elem){
	return (typeof(elem) == 'string') ? document.getElementById(elem) : elem;
}


		
		