/*

	Experimental StretchText Javascript Library
	Eastgate Systems, Inc.
	Mark Bernstein
	October, 2008
	
	© 2008-2009 by Eastgate Systems, Inc. All Rights Reserved.
	bernstein@eastgate.com
	
	Requires Prototype, Scriptalicious

*/		



if(typeof Object.create != 'function'){
	Object.create=function(o) {
		var F=function(){};
		F.prototype=o;
		return new F();
		}
	}
	
	
		
// -------------------

var StretchItemList ={
	items: [],
	
	add: function(spec){this.items.push(spec); return this;},
	count: function(){return this.items.length;},
	get: function(n){return this.items[n];},
	
	register: function(kind, link) {
		if (!kind.isStretchTextLink(link)) return -1;	// not a stretchtext link
		var module=kind.moduleName(link);
		var dest=kind.destination(link);
	
		var spec={
			dest: dest,
			module: '.'+module, 
			kind: kind,
			index: this.count(),
			link: link,
			};
		this.add(spec);
		return spec.index;
		}
	};
	
	
// -------------------


var StretchItem={
	elements: [],
	
	empty: function(){
		return (this.elements.length==0);
		},
	only: function(what){
		this.elements=this.hasClass(what)
		},
	hidden: function() {
		return this.elements.select(this.isHidden);
		},
	transient: function() {
		return this.elements.select(this.isTransient);
		},	
	initial: function() {
		return this.elements.select(this.isInitial);
		},	
	notInitial: function() {
		return this.elements.reject(this.isInitial);
		},	
	replacement: function() {
		return this.elements.select(this.isReplacement);
		},	
	hasClass: function(what){
		return this.elements.select(function(e){return e.hasClassName(what)})
		},
	notClass: function(what){
		return this.elements.reject(function(e){return e.hasClassName(what)})
		},
	container: function(id,module){
		return $(id).select(module);
		},
	isHidden:	function(node){return node.hasClassName('hidden');},
	isTransient:function(node){return node.hasClassName('transient');},
	isInitial:	function(node){return node.hasClassName('initial');},
	isReplacement:function(node){return node.hasClassName('replacement');},
	isLink:		function(node){return node.tagName.toLowerCase()=='a';},
	
	
	// -------------------------------------------------------------------------
	fadeInTime:	1.0,
	fadeOutTime: 0.25,

	// -------------------------------------------------------------------------
	//    moduleName
	//		stretchtext links look like <a class="kind module_id ..."
	
	moduleName:	function(link){
		var classes=$w(link.className)
		if (classes.length<2) {return"";}
		return classes[1];	
		},
	destination: function(link){
		var classes=$w(link.className)
		if (classes.length<3) {return"";}
		return classes[2];	
		},
	isStretchTextLink: function(link){
		if (!this.isLink(link)) {return false;}
		var classes=$w(link.className)
		if (classes.length<2) {return false;}
		var kind=classes[0];
		return true;
		},
	

	// -------------------------------------------------------------------------
	revealOne: function(what) {
		what.removeClassName('hidden');
		what.addClassName('transient');
		what.setOpacity(0);
		what.show();
		what.appear({duration: this.fadeInTime});
		},
	
	reveal: function(list){
		var that=this;
		list.each(function(what){
			that.revealOne(what);
			})
		},	
		
	unrevealOne: function(what) {
		var that=this;
		what.addClassName('hidden');
		what.removeClassName('transient');
		what.fade({duration:this.fadeOutTime});
		
				
		function unrevealTransientDescendantsOf(what) {
			// when we hide an element, we also need to clean up elements inside it
			// scan the element to see if we have a stretchtext link inside it; if we're
			// hiding a stretchtext link, we need to hide the transient parts of its module
			what.descendants().select(that.isLink).each(function(x){
				var classes;
				var id, hideMe;
				if (!that.isStretchTextLink(x)){
					return;
					}
				id=that.moduleName(x);
				hideMe=$$('.'+id).select(that.isTransient);
				hideMe=hideMe.reject(that.isLink);
				
				that.unreveal(hideMe);
				});
			}
		
		unrevealTransientDescendantsOf(what);
		},

	unreveal: function(list){
		var that=this;
		list.each(function(what){
			that.unrevealOne(what);
			});				
		},		
	highlight: function(list){
		var that=this;
		list.each(function(what){what.highlight({duration: that.fadeInTime})});
		},
		
	// --------------------------------------- jump utilities	
		
	install: function(bin,that){
		if (this.empty()) return;
		
		// that is prefixed by the letters 'to'
		// in order to prevent stow() from stowing the link
		if (that.length>2) {
			that='.'+that.substring(3)
			}
				
		var f=this.container(bin,that);		
		if (f.length==0){
			this.stow(bin,that);
			return;
			}
			
		var e=this.elements.reject(this.isLink)
		if (e.length==0)e=this.elements;
		var last=e[e.length-1];	// add the new items after the last item of class 'me'

		var that=this;
			
		f.each(function(what){
				// jumps, expanders and replacer links INSIDE a jump also have our id.
				// we don't want to move the links, because they'll be moved separately
				// with the blocks that contain them
				if (that.isLink(what)){
					return;
					}
				what.remove();
				last.insert({after:what});
				last=what;
				if (!what.hasClassName('hidden')){
					what.setOpacity(0);
					what.appear({duration:that.fadeInTime});
					}
			
			})
		f[0].scrollTo();
		return;
		},
		
	stow: function(container,module){
		var bin=$(container)
		var e=$$(module)
		e.each(function(e){e.remove(); bin.appendChild(e);})
		},
		
		
	// ----------------------------------------------------------------------------------
		
	
	log: function (x){
		window.console.log(x);
		},
		
	kind: "",
	
	items:Object.create(StretchItemList),
	
	initialize: function(kind,link){					
		var token=this.items.register(kind,link);
		if (token<0) return;
		
		link.onclick=function() {kind.run(token);return false;}
		},
		
	run: function(index){
		var spec=this.items.get(index);
		var module=spec.module;
		var dest=spec.dest;
		this.elements=$$(module);
		
		if (this.empty()){return;} 
		
		var showMe=this.whatToShow(spec);
		var hideMe=this.whatToHide(spec);

		this.unreveal(hideMe)
		this.reveal(showMe)
			
		this.hasBeenShown(showMe,spec)
		
		},
		
	
	whatToShow: function(spec) { return this.hidden()},
	whatToHide: function(spec) { return this.transient()},

	hasBeenShown: function(list)		{this.highlight(list);},
		
	reset: function(what){
		var that=this;

		function resetOne(e) {
			if (!that.isInitial(e)){
				that.unrevealOne(e);
				return;
				}
			var wasTransient=that.isTransient(e);
			that.revealOne(e);
			if (!wasTransient) {
				e.removeClassName('transient');
				}
			}


		this.elements=$$(what);
		this.elements.each(resetOne);
		return;
		},
		
	}
		

var Expander=Object.create(StretchItem);
Expander.kind="expander";

		
var Replacer=Object.create(StretchItem);
Replacer.kind="replacer"
		
var Chooser=Object.create(StretchItem);
Chooser.kind="chooser";
			
Chooser.whatToHide=function(spec){
	var hideMe=this.notClass(spec.dest);			// don't hide the things we want to show
	hideMe=hideMe.reject(this.isLink);		// don't hide the link element; it might be hidden by its container
	hideMe=hideMe.select(this.isTransient); // only hide transient things
	return hideMe;
	}
		
Chooser.whatToShow=function(spec){
	var showMe=this.replacement();
	var also=this.hasClass(spec.dest);
	return showMe.concat(also);
	};

// ------------------------------------------------
				
var Jump=Object.create(StretchItem);
Jump.kind="jump";

Jump.run=function(index){
	var spec=this.items.get(index);
	var me=spec.module;
	var bin='bin';
	var that='.'+spec.dest;
	
	this.elements=$$(me);
	this.install(bin,that);
	}	
	

var MarginNote=Object.create(StretchItem);
MarginNote.kind="margin";
		
MarginNote.hasBeenShown=function(showMe,spec){
	// in addition to highlighting, we need to position the margin note relative to the link
	var needOffset=true;
	
	this.highlight(showMe);
	showMe.each(function(e){
		var diff=0;
		e.setOpacity(0);
		e.show();

		if ( needOffset && spec.link!=null) {
			diff=e.cumulativeOffset()[1]-spec.link.cumulativeOffset()[1];
			}
		var h=e.getHeight()/2;
		var m=e.getStyle('margin-top');
		m=m.substr(0,m.length-2);
		e.setStyle({'margin-top': m-diff-h+'px' }); 
		needOffset=false; 
		})

	showMe.each(function(e){
		e.addClassName('transient')
		e.onclick=function(){MarginNote.run(spec.index);}
		})

	}
		


// ---------------------

var Stretch={


		reset: function(what) {
		StretchItem.reset(what);
		},
		
	initialize: function() {
		$$('.hidden').each(function(e) {e.style.display="none";})

		$$('a.expander').each(function(e){Expander.initialize(Expander,e)});
		$$('a.replacer').each(function(e){Replacer.initialize(Replacer,e)});
		$$('a.chooser').each( function(e){Chooser.initialize(Chooser,e)});
		$$('a.margin').each(function(e){MarginNote.initialize(MarginNote,e)});
		$$('a.jump').each( function(e){Jump.initialize(Jump,e)});
				
		},
	};
	

	
function reset_stretch(what){
	Stretch.reset(what);
	}

	

// --------- utilities

function log(x){
	window.console.log(x);
	}

	



Event.observe(window,'load',Stretch.initialize);

