/**
 * @author Sergey Chikuyonok (gonarch@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

/**
 * Creates instance of infoscroller object
 * @param {String, Element} elem Content container ID or pointer
 * @TODO
 * Skip empty top-level text nodes while scanning
 */
function Infoscroller(elem){
	this._container=(typeof(elem) == 'string') ? document.getElementById(elem) : elem;
	this._structure=[];
	this._dragOffset={};
	
	this.setDepth(2);
	this.parseBlocks(this._container);
	
	Infoscroller.initEvents();
	Infoscroller.instance.push(this);
}

Infoscroller.TEXT='text';
Infoscroller.HEADER='header';
Infoscroller.IMAGE='image';
Infoscroller.TABLE='table';
Infoscroller.BLOCK='block';
Infoscroller.LIST='list';
Infoscroller.LIST_ITEM='list_item';
Infoscroller.UNKNOWN='unknown';

Infoscroller.Tags={};
Infoscroller.Tags[Infoscroller.TEXT]=['p', 'blockquote'];
Infoscroller.Tags[Infoscroller.HEADER]=['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'];
Infoscroller.Tags[Infoscroller.IMAGE]=['img'];
Infoscroller.Tags[Infoscroller.TABLE]=['table'];
Infoscroller.Tags[Infoscroller.BLOCK]=['div'];
Infoscroller.Tags[Infoscroller.LIST]=['ul', 'ol', 'dl'];
Infoscroller.Tags[Infoscroller.LIST_ITEM]=['li', 'dt', 'dd'];

Infoscroller.events=false;
Infoscroller.dragging=false;
Infoscroller.dragParams={};
Infoscroller.instance=[];

/**
 * Sets maximum node nesting depth level which node scanner can reach
 * @param {Number} d Depth level
 */
Infoscroller.prototype.setDepth=function(d){
	this._depth=d;
}

/**
 * Returns maximum node nesting depth level which node scanner can reach
 * @return {Number}
 */
Infoscroller.prototype.getDepth=function(){
	return this._depth;
}

/**
 * Returns element type
 * @param {Element} elem Element to test (nodeType must be 1)
 * @return {String}
 */
Infoscroller.prototype.getElementType=function(elem){
	var tag=elem.nodeName.toLowerCase();
	var _tags=this.constructor.Tags;
	var ar, il;
	for(var t in _tags){
		if(_tags[t] instanceof Array){
			ar=_tags[t];
			il=ar.length;
			for(var i=0; i<il; i++){
				if(ar[i] == tag)
					return t;
			}
		}
	}
	return Infoscroller.UNKNOWN;
}

/**
 * Sets container structure
 * @param {Array} s Block structure
 */
Infoscroller.prototype.setStructure=function(s){
	this._structure=s;
}

/**
 * Returns container structure
 * @return {Array}
 */
Infoscroller.prototype.getStructure=function(){
	return this._structure;
}

/**
 * Returns position of element relativelty to the container
 * @param {Element} elem Element to retreive position
 * @return {Object}
 */
Infoscroller.prototype.getPos=function(elem){
	var _x=0;
	var _y=0;
	if(elem && this._content){
		do{
			if(elem == this._content)
				break;
			else{
				_x+=elem.offsetLeft;
				_y+=elem.offsetTop;
			}
		}while((elem=elem.offsetParent))
	}
	
	return {x: _x, y: _y};
}

/**
 * Parse infoscoller container and retrieves important element pointers
 * @param {Element} elem Container element 
 */
Infoscroller.prototype.parseBlocks=function(elem){
	var divs=elem.getElementsByTagName('div');
	var il=divs.length;
	var c;
	for(var i=0; i<il; i++){
		if(divs[i].className){
			c=divs[i].className;
			if(c.indexOf('infoscroller-content') != -1)
				this._content=divs[i]
			else if(c.indexOf('scroll-shaft') != -1)
				this._scrollshaft=divs[i];
			else if(c.indexOf('scroll-head') != -1)
				this._scrollhead=divs[i];
		}
	}
	
	var _me=this;
	if(this._scrollhead){
		addEvent(this._scrollshaft, 'mousedown', function(evt){_me.startDrag(evt)});
	}
	
	addEvent(elem, 'mousewheel', function(evt){_me.scroll(evt);});
	addEvent(elem, 'DOMMouseScroll', function(evt){_me.scroll(evt);});
}

/**
 * Scan container
 */
Infoscroller.prototype.scan=function(){
	if(this._content){
		this.setStructure(this._scanProc(this._content, this.getDepth()));
	}
}

/**
 * Optimizes infoscroller structure
 */
Infoscroller.prototype.optimize=function(){
	var struct=this.getStructure();
	var children, s;
	var il=struct.length;
	for(var i=0; i<il; i++){
		s=struct[i];
		switch(s.type){
			case Infoscroller.TEXT:
				if(s.ptr.nodeType == 1 && s.children){
					children=[];
					this._optimizeText(s, s.children, children);
					s.children=children;
				}
				break;
			case Infoscroller.LIST:
				if(s.ptr.nodeType == 1 && s.children){
					children=[];
					this._optimizeList(s, s.children, children);
					s.children=children;
				}
				break;
			case Infoscroller.BLOCK:
				if(s.children){
					children=[];
					this._optimizeBlock(s, s.children, children);
					s.children=children;
					for(var j=0; j<children.length; j++){
						if(children[j].type == Infoscroller.TEXT && this._isRealText(children[j].ptr)){
							s.type=Infoscroller.TEXT;
							break;
						}
					}
				}
				break;
			default:
				if(s.children)
					delete s.children;
		}
	}
	
	this.setStructure(struct);
}

/**
 * Draw document structure
 */
Infoscroller.prototype.draw=function(){
	var elem=document.createElement('div');
	elem.className='structure';
	var struct=this.getStructure();
	var il=struct.length, jl, item, subitem, s, ss;
	for(var i=0; i<il; i++){
		s=struct[i];
		item=this._drawItem(s);
		if(item){
			if(s.children && s.children.length){
				ss=s.children;
				jl=ss.length;
				for(var j=0; j<jl; j++){
					subitem=this._drawItem(ss[j])
					if(subitem)
						item.appendChild(subitem);
				}
			}
			elem.appendChild(item);
		}
	}
	
	if(this._scrollshaft){
		if(this._structureContainer)
			this._structureContainer.parentNode.removeChild(this._structureContainer);
		this._scrollshaft.appendChild(elem);
		this._structureContainer=elem;
		this.drawSlider();
	}
}

Infoscroller.prototype.drawSlider=function(){
	if(this._scrollhead){
		this._scrollhead.style.height=((this._container.offsetHeight / this._content.offsetHeight)*100)+'%';
	}
}

Infoscroller.prototype.startDrag=function(evt){
	if((evt=checkEvent(evt))){
		Infoscroller.dragging=true;
		var pos=getAbsolutePos(this._scrollhead);
		var delta={x: evt.clientX, y: evt.clientY};
		var shaftHeight=this._scrollshaft.offsetHeight;
		var headHeight=this._scrollhead.offsetHeight;
		
		if((delta.y < pos.y) || (delta.y > pos.y + headHeight)){
			//position scroll head under mouse cursor
			var shaftPos=getAbsolutePos(this._scrollshaft);
			var dy=delta.y - shaftPos.y;
			var prc=dy/shaftHeight;
			var y=Math.max(Math.min(dy-prc*headHeight, shaftHeight - headHeight), 0);
			pos.y+=(y - this._scrollhead.offsetTop);
			this.moveSlider(y);
		}
		
		
		var offset={x: pos.x - delta.x, y: pos.y - delta.y};
		var _me=this;
		Infoscroller.dragParams={
			item: _me,
			head: _me._scrollhead,
			shaftHeight: _me._scrollshaft.offsetHeight,
			scrollHeight: _me._scrollhead.offsetHeight,
			offsetX: pos.x - delta.x,
			offsetY: pos.y - delta.y
		};
		return cancelEvent(evt);
	}
	return false;
}

Infoscroller.prototype.moveSlider=function(y){
	if(this._scrollhead){
		y=Math.max(Math.min(y, this._scrollshaft.offsetHeight - this._scrollhead.offsetHeight), 0);
		var prc=y/this._scrollshaft.offsetHeight;
		this._content.style.top=(-this._content.offsetHeight*prc)+'px';
		this._scrollhead.style.top=y+'px';
	}
}

Infoscroller.prototype.scroll=function(evt){
	if((evt=checkEvent(evt))){
		var dy=-evt.mouse_wheel_delta*10;
		this.moveSlider(this._scrollhead.offsetTop + dy);
	}
}

Infoscroller.prototype._update=function(){
	this.scan();
	this.optimize();
	this.draw();
	
	//reposition slider
	if(this._content.offsetTop + this._content.offsetHeight < this._container.offsetHeight)
		this._content.style.top=(this._container.offsetHeight - this._content.offsetHeight)+'px';
	
	var prc=this._content.offsetTop/this._content.offsetHeight;
	this.moveSlider(this._scrollshaft.offsetHeight * -prc); 
}

/**
 * Draw document structure's item
 * @param {Object} item Item to draw
 * @return {Element, null}
 */
Infoscroller.prototype._drawItem=function(item){
	var d=null;
	switch(item.type){
		case Infoscroller.TABLE:
			d=this._drawTable(item);
			break;
		case Infoscroller.UNKNOWN:
			break;
		default:
			if(item.ptr.nodeType == 1){
				d=document.createElement('div');
				d.className='type-'+item.type;
				if(item.ptr.nodeType == 1)
					d.className+=' tag-'+item.ptr.nodeName.toLowerCase();
				else
					d.className+=' text-node';
			}
			break;
		case Infoscroller.TABLE:
			d=this._drawTable(item);
			break;
	}
	
	if(d){
		d.style.left=(item.left*100)+'%';
		d.style.top=(item.top*100)+'%';
		d.style.width=(item.width*100)+'%';
		d.style.height=(item.height*100)+'%';
	}
	return d;
}

Infoscroller.prototype._drawTable=function(item){
	//first we have to calculate total rows and columns, skipping nested tables
	var cols=0, rows=0, _cols=0;
	var c, sc, il, jl;
	//browsers like TBODY element
	var tbody=item.ptr;
	c=tbody.childNodes;
	il=c.length;
	for(var i=0; i<il; i++){
		if(c[i].nodeType == 1 && c[i].nodeName == 'TBODY'){
			tbody=c[i];
			break;
		}
	}
	
	c=tbody.childNodes;
	il=c.length;
	for(var i=0; i<il; i++){
		if(c[i].nodeType == 1 && c[i].nodeName == 'TR'){
			rows++;
			sc=c[i].childNodes;
			jl=sc.length;
			_cols=0;
			for(var j=0; j<jl; j++){
				if(sc[j].nodeType == 1 && (sc[j].nodeName == 'TD' || sc[j].nodeName == 'TH'))
					_cols++;
			}
			if(_cols > cols)
				cols=_cols;
		}
	}
	
	var table=document.createElement('table');
	tbody=document.createElement('tbody');
	var tr, td;
	var width=Math.floor(100/cols);
	var height=Math.floor(100/rows);
	for(var i=0; i<rows; i++){
		tr=document.createElement('tr');
		for(var j=0; j<cols; j++){
			td=document.createElement('td');
			td.style.width=width+'%';
			td.style.height=height+'%';
			tr.appendChild(td);
		}
		tbody.appendChild(tr);
	}
	table.appendChild(tbody);
	return table;
}


/**
 * Main node scanning routinne
 * @param {Element} elem Element to scan
 * @param {Number} lev Depth level
 */
Infoscroller.prototype._scanProc=function(elem, lev){
	var struct=[];
	lev=lev||0;
	var ar=elem.childNodes;
	var il=ar.length;
	var s, _w, _h, _x, _y, pos;
	var w=this._content.offsetWidth;
	var h=this._content.offsetHeight;
	for(var i=0; i<il; i++){
		switch(ar[i].nodeType){
			case 1: //element
				pos=this.getPos(ar[i]);
				_w=ar[i].offsetWidth;
				_h=ar[i].offsetHeight;
				_x=pos.x;
				_y=pos.y;
				
				s={
					type: this.getElementType(ar[i]),
					left: _x/w,
					top: _y/h,
					width: _w/w,
					height: _h/h,
					leftAbs: _x,
					topAbs: _y,
					widthAbs: _w,
					heightAbs: _h,
					ptr: ar[i]
				};
				if(ar[i].childNodes && ar[i].childNodes.length && lev)
					s.children=this._scanProc(ar[i], lev-1);
				struct.push(s);
				break;
			case 3: //text node
				struct.push({
					type: Infoscroller.TEXT,
					ptr: ar[i]
				});
				break;
		}
	}
	return struct;
}

/**
 * Check if passed node is a real text (i.e. not whitespace)
 * @param {Element} node Text node
 * @return {Boolean}
 */
Infoscroller.prototype._isRealText=function(node){
	var text=node.nodeValue;
	return text.search(/[^\s\t\n]/);
}

/**
 * Optimizes text structure
 * @param {Object} structRoot Structure's root element
 * @param {Array} structElem Structure elements to optimize
 * @param {Array} ptrOpt Pointer to optimized structure
 */
Infoscroller.prototype._optimizeText=function(structRoot, structElem, ptrOpt){
	var il=structElem.length;
	var elem, s;
	for(var i=0; i<il; i++){
		elem=structElem[i];
		if(elem.type != Infoscroller.TEXT && elem.type != Infoscroller.UNKNOWN){
			ptrOpt.push(this._recalcItem(elem, structRoot));
		}
		if(elem.children)
			this._optimizeText(structRoot, elem.children, ptrOpt);
	}
}

/**
 * Optimizes list structure
 * @param {Object} structRoot Structure's root element
 * @param {Array} structElem Structure elements to optimize
 * @param {Array} ptrOpt Pointer to optimized structure
 */
Infoscroller.prototype._optimizeList=function(structRoot, structElem, ptrOpt){
	var il=structElem.length;
	var elem, s;
	for(var i=0; i<il; i++){
		elem=structElem[i];
		if(elem.type == Infoscroller.LIST_ITEM && structRoot.type == Infoscroller.LIST){
			ptrOpt.push(this._recalcItem(elem, structRoot));
		}
		if(elem.children)
			this._optimizeList(structRoot, elem.children, ptrOpt);
	}
}

/**
 * Optimizes block structure
 * @param {Object} structRoot Structure's root element
 * @param {Array} structElem Structure elements to optimize
 * @param {Array} ptrOpt Pointer to optimized structure
 */
Infoscroller.prototype._optimizeBlock=function(structRoot, structElem, ptrOpt){
	var il=structElem.length;
	var elem, s, val;
	for(var i=0; i<il; i++){
		elem=structElem[i];
		if(elem.type == Infoscroller.TEXT && elem.ptr.nodeType == 3){
			//found text node, check if this is not an empty node
			val=elem.ptr.nodeValue;
			if(val.length && val.match(/[^\s\t]+/))
				 ptrOpt.push(elem);
		}
		else if(elem.type != Infoscroller.TEXT && elem.type != Infoscroller.BLOCK && elem.type != Infoscroller.UNKNOWN){
			ptrOpt.push(this._recalcItem(elem, structRoot));
		}
		if(elem.children)
			this._optimizeText(structRoot, elem.children, ptrOpt);
	}
}

/**
 * Recalculates item's dimensions using parent size and position
 * @param {Object} item Current item
 * @param {Object} parentItem Parent item
 * @return {Object}
 */
Infoscroller.prototype._recalcItem=function(item, parentItem){
	return {
		type: item.type,
		left: (item.leftAbs - parentItem.leftAbs)/parentItem.widthAbs,
		top: (item.topAbs - parentItem.topAbs)/parentItem.heightAbs,
		width: item.widthAbs/parentItem.widthAbs,
		height: item.heightAbs/parentItem.heightAbs,
		leftAbs: item.leftAbs,
		topAbs: item.topAbs,
		widthAbs: item.widthAbs,
		heightAbs: item.heightAbs,
		ptr: item.ptr
	};
}

Infoscroller.initEvents=function(){
	if(!Infoscroller.events){
		addEvent(document, 'mousemove', Infoscroller.doDrag);
		addEvent(document, 'mouseup', Infoscroller.stopDrag);
		addEvent(window, 'resize', Infoscroller.update);
		document.ondragstart=document.ondrag=function(){return !Infoscroller.dragging;}
		Infoscroller.events=true;
	}
}

Infoscroller.doDrag=function(evt){
	if(Infoscroller.dragging && (evt=checkEvent(evt))){
		var d=Infoscroller.dragParams;
		d.item.moveSlider(evt.clientY + d.offsetY);
		//d.head.style.top=y+'px';
	}
}

Infoscroller.stopDrag=function(){
	Infoscroller.dragging=false;
}

Infoscroller.update=function(){
	var il=Infoscroller.instance.length;
	for(var i=0; i<il; i++){
		Infoscroller.instance[i]._update();
	}
}
