//
//		(c) AlwinBlok 2004-2009, The Netherlands
//

function Map(){
	var _idcount = 0;
	var _entries = this.entries   = {};
	this.length  = 0;
	var scope = this;

	this.newKey = function(){
	  return "uid_"+_idcount++;
	}

	this.insert = function(arg1, val){
		var key = val ? arg1 : "uid_"+_idcount++;
		var val = val ? val  : arg1;
		if(!_entries[key]) scope.length++;
			_entries[key] = val;
		return key;
	}

	this.remove = function(key){
		if(_entries[key]){
			var r = _entries[key];
			delete _entries[key];
			scope.length--;
			return r;
		}
	}

	this.empty = function(){
	  this.length = 0;
		_entries = this.entries = {};
	}

}

//
//  Selection of Animation related code
//

function RunLoop(){
	var _map = new Map();
	var _t = 0;
	var _startTime = new Date();
	var _running = false;

	this.getLength = function(){return _map.length};
	this.getTime = function(){return _t};
	this.remove = _map.remove;
	this.newKey = _map.newKey

	this.insert = function(obj){
		_map.insert(obj.id, obj);
		if(!_running){
			_running = true;
			_startTime = (new Date());
			_t = 0;
			_mainloop();
		}
		obj.startTime = _t;
	}

	var _jobs = _map.entries;
	var _mainloop = function(){
		var pos = (new Date()) - _startTime;
		var dt = pos - _t; _t = pos;
	  for(var i in _jobs) _jobs[i].tick(_t, dt);
		if(_map.length > 0) window.setTimeout(_mainloop, 20);
		else _running = false;
	}
	
}

window.animLoop = window.animLoop || new RunLoop();

// minimalistic linear Animation
uAnim = function(){
	this.id = window.animLoop.newKey();
}

uAnim.prototype.start = function(from,to,dur,callback){
	var incr = (to - from)/dur;
	this.tick = function(t,dt){
		from += dt*incr;
		// Note: startTime gets set by the runLoop
		if(t - this.startTime >= dur){
			window.animLoop.remove(this.id);
			from = to; callback(to);
		}
		else callback(from);
	}
	window.animLoop.insert(this);
}


//
//	Selection of Event related code
//

Events = {};
Events.preventDefaultOn = function(evt){
	evt = (evt||window.event||{});
	evt._preventDefault = true;
	if(evt.preventDefault) evt.preventDefault();
	evt.returnValue = false;
	return false;//evt;
}

Events.normalizeEvent = function(evt){
	var evt = evt||window.event||{};
	var n = "number";
	if(!evt.target && evt.srcElement) evt.target = evt.srcElement;
	if(typeof evt.layerX  !== n) try{evt.layerX = evt.x}catch(e){};
	if(typeof evt.layerY  !== n) try{evt.layerY = evt.y}catch(e){};
	if(typeof evt.pageX   !== n) evt.pageX   = evt.clientX + document.body.scrollLeft;
	if(typeof evt.offsetX !== n) evt.offsetX = evt.pageX   - layout.getPagePosition(evt.target).x;
	if(typeof evt.pageY   !== n) evt.pageY   = evt.clientY + document.body.scrollTop;
	return evt;
}



//
//  Selection of dom / layout utilities
//

dom = {};

dom.isInput = function(e){
 	return (e.tagName && 
	e.tagName == 'BUTTON'   || e.tagName == 'INPUT'    ||
	e.tagName == 'SELECT'   || e.tagName == 'TEXTAREA' ||
	e.tagName == 'OPTION');
}

dom.ufValue = function(e){
	if(!e) return;
	var tag = e.tagName;
	return (dom.isInput(e) ? e.value : (tag == 'ABBR' || tag == 'HTML:ABBR') ? e.title : e.innerHTML);
}

dom.byClass = function(e, classn){
	var children = e.childNodes;
	var result = [];
	for(var i=0; i<children.length; i++){
		if(dom.hasClass(children[i], classn)) result.push(children[i]);
		result = result.concat(dom.byClass(children[i], classn));
	}
	return result;
}

dom.replaceClass = function(e, name1, name2){
	if(!e) return;
	var cls = e.className || '';
	if(!name1) e.className = name2+' '+cls;
	else e.className = cls.replace(new RegExp('(\\s+|^)'+name1+'(\\s+|$)'), ' '+name2+' ');
}

dom.hasClass = function(e, cls){
	return (e && e.className)
		? !! e.className.match(new RegExp('(\\s+|^)' + cls + '(\\s+|$)'))
		: false
}

layout = {}

// fixme.. prevent skipping over parent if not an offset parent.
layout.getOffset = function(e,parent){
	var x = 0; var y = 0;
	while(e && e !== parent && e.offsetParent) {
		x += e.offsetLeft; y += e.offsetTop;
		if(layout.getComputedStyle(e).position === 'fixed'){
			var p = window.getScrollPosition();
			x += p.x; y += p.y;
			e = null;
		}
		else e = e.offsetParent;
	}
	return {x:x, y:y};
}

layout.getPagePosition = layout.getOffset

layout.getComputedStyle = function(e){
  var style = (typeof(e.currentStyle) !== 'undefined') ? e.currentStyle : 
		document.defaultView.getComputedStyle(e, null);
  return style;
}


//
//  Selection of GUI components
//


function TabbedPanel(dom_obj, config_obj){
	this.current = [null,null];
	this.config = config_obj || {'activeClass' : 'selected', 'inactiveClass' : ''};
	var tabs = dom_obj.getElementsByTagName('A');
	var _loc = window.location.hash.split('#')[1];
	for(var i=0; i< tabs.length; i++) this.initAnchor(tabs[i], i, _loc);
	tabs[0].onclick();
};

TabbedPanel.prototype.initAnchor = function(a, i, _loc){
	var scope = this;
	var hash = a.href.split('#')[1];
	var ref  = hash ? document.getElementById(hash) : null;
	if(hash === _loc) this.selected = a;
	dom.replaceClass(a, this.config.activeClass, this.config.inactiveClass);
	dom.replaceClass(ref, this.config.activeClass, this.config.inactiveClass);
	a.onclick = function(evt){
		Events.preventDefaultOn(evt||window.event||{})
		scope.select(a, ref, i)
	};
};

TabbedPanel.prototype.select = function(anchor, ref, index){
	dom.replaceClass(this.current[0], this.config.activeClass, this.config.inactiveClass);
	dom.replaceClass(this.current[1], this.config.activeClass, this.config.inactiveClass);
	dom.replaceClass(anchor, this.config.inactiveClass, this.config.activeClass);
	dom.replaceClass(ref, this.config.inactiveClass, this.config.activeClass);
	this.current = [anchor, ref];
	if(typeof(this.onselect) === 'function') this.onselect({anchor:anchor, index:index, target:anchor});
};


function LinkStepper(dom_obj){
	this.anchors = dom_obj.getElementsByTagName('A');
	this.position = 0;
};

LinkStepper.prototype.next = function(){
	this.position = (this.position+1)%(this.anchors.length);
	this.anchors[this.position].onclick();
};

LinkStepper.prototype.prev = function(){
	this.position = (this.position-1)%(this.anchors.length);
	if(this.position < 0) this.position = this.anchors.length+this.position;
	this.anchors[this.position].onclick();
};

LinkStepper.prototype._getPosition = function(){
	return this.position;
};

LinkStepper.prototype._setPosition = function(i){
	this.position = (i)%(this.anchors.length);
};


// @frame: container-frame, @cframe: content-frame
function HScrollView(frame,cframe){
	this.position = 0;
	this.frame = frame;
	this.cframe = cframe;
	this.posPx = 0;
	this.cframe.style.position = 'relative';
	if(document.all) this.cframe.style.styleFloat = 'left';
	else this.cframe.style.cssFloat = 'left';
}

// @pos: position on a scale from 0 .. 1
HScrollView.prototype.scrollToPosition = function(pos){
	this.position = pos;
	var fwidth = parseFloat(layout.getComputedStyle(this.frame).width);
	// var cwidth = parseFloat(layout.getComputedStyle(this.cframe).width);
	var cwidth = this.cframe.offsetWidth; // IE returs a computed style of 'auto'. This works too
	this.posPx = -((pos*cwidth)-(pos*fwidth))
	this.cframe.style.left = this.posPx+'px';
}


function Slider(elem, min, value, max){
	// dom refs
	this.input = dom.byClass(elem, 'value').pop();
	this.track = document.createElement('div');
	this.handle = document.createElement('a');
	// object values
	this.value = value || parseFloat(dom.ufValue(this.input)) || 0;
	this.min   = min   || parseFloat(dom.ufValue(dom.byClass(elem, 'min').pop())) || 0;
	this.max   = max   || parseFloat(dom.ufValue(dom.byClass(elem, 'max').pop())) || 1;
	// dom setup
	this.track.className  = 'slider-track';
	this.handle.className = 'slider-handle';
	elem.style.position   = 'relative';
	elem.appendChild(this.track);
	elem.appendChild(this.handle);
	// initialization and event-handlers 
	this.setValue(this.value);
	this.setupEventHandlers(elem);
}

Slider.prototype.setupEventHandlers = function(elem){
	var me = this;
	var startpos = 0;
	var startoff = 0;

	var setValuePx = function(e){
		e = Events.normalizeEvent(e);
		Events.preventDefaultOn(e);
		var width = parseFloat(layout.getComputedStyle(elem).width);
		var x = (e.clientX - startpos);
		me.setValue(me.min+((me.max-me.min)*(x/width)));
		if(typeof(me.onchange) === 'function') me.onchange({target:me, type:'change', value:me.value});
		return false;
	};

	// fixme: make elem and track detect events too.
	me.handle.onmousedown = function(e){
		e = Events.normalizeEvent(e);
		Events.preventDefaultOn(e);
		var onmousemove = 'onmousemove'; // MS: onmouseover doesn't work well in IE
		document[onmousemove] = setValuePx;
		startoff  = (e.target === me.handle) ? e.offsetX : 0;
		startpos  = e.clientX - e.layerX + e.offsetX;

		document.onmouseup = function(){
			document[onmousemove] = null;
			if(typeof(me.onchanged) === 'function') me.onchanged({target:me, type:'changed', value:me.value});
		};
	};

	// prevent IE from selecting the handle
	this.handle.onselectstart = Events.preventDefaultOn;
	this.handle.onselect      = Events.preventDefaultOn;
}

Slider.prototype.setValue = function(val){
	var val = (val < this.min === this.min < this.max) ? this.min : val;
	var val = (val > this.max === this.max > this.min) ? this.max : val;
	this.value = this.input.value = val;
	var perc = (100*(val-this.min))/(this.max-this.min);
	this.track.style.width = perc+'%';
}
