/*
	pane-manager: organizes a container div into an arbitrary number of smaller, resizable subpanes
	version: 1.8

	*** Tested & working on Mozilla-based browsers, IE6, IE7, Opera ***


	Create a new pane object by calling the constructor:
	
		var mypane = new pane(m,n, mycontainer, myoptions)

	This will split the containing element 'mycontainer' into an 'm' x 'n' array of subpanes (which are div elements).
	'mycontainer' must be an element. Draggable divs ('divider' objects) are appended between the subpanes to facilitate
	resizing. Dividers observe drag events and update the subpane divs positions accordingly.
	
	Options are specified via the last argument in the constructor. The options argument takes attribute/value pairs.
	Currently, only the initial divider positions and the (global) minimum subpane width can be specified. For example:

		myoptions = { minWidth:200 , initPosX:[20,60] }

	would limit the minimum width of all subpanes to 200 pixels and position the vertical dividers at 20% and 60% of the
	total width of 'mycontainer'. Similarly, horizontal dividers can be positioned using the attribute 'initPosY'. Note
	that 'initPosX' and 'initPosY' must be arrays. Also, the number of elements of the array must equal the number of
	dividers. In the example, then, there would be two vertical dividers splitting the containing div into 3 subpanes.
	This means that the number of elements in 'initPosX' and 'initPosY' depends on 'm' and 'n'. For example, we might create
	a 2x3 array of subpanes like so:

		var mypane = new pane(2,3, mycontainer, {  initPosY:[50],  initPosX:[20,50] } )

	***Note that initPosY.length = m-1 and initPosX.length = n-1

	***Options need not be specified. By default, minWidth = 0 and the dividers are positioned with equal spacing.


	A complete example -- to create a 1x3 pane (using some Prototype js library functions):

		var container_div = document.createElement('div');
		container_div.setStyle({
			position:'absolute',
			left:'0px',
			right:'0px',
			top:'0px',
			bottom:'0px'
		});

		var mypane = newpane(1,3, mycontainer, { minWidth:200, initPosX:[20,60] });


	**** ADDITIONAL HELPFUL INFO ****	

	Subpanes are stored in a 2-d array and can be accessed via their indices (with 0 as the starting index). 
	For example, the top left subpane would be accessed as

		mypane.subpane[0][0]

	or, in general, the subpane in the second row and first column would be mypane.subpane[1][0]. Some additional 
	helper functions give some control over the subpanes. For example, we can add an already created vsviewer object 
	'myvsviewer' to a subpane using

		mypane.subpane[i][j].insertViewer(myvsviewer)

	or we can make a subpane a sidebar using

		mypane.subpane[i][j].makeSidebar()

	This sets the subpane's CSS bottom property to 0px -- thus assuring that the sidebar extends the entire height of the
	containing div. This doesn't destroy or in any way alter the original subpane elements. However, it gives the appearance 
	of a non-rectangular array of subpanes by covering any subpanes below the sidebar subpane.

	We can also add tabs to a subpane via the addTabs() function which takes an array of Strings that are the tab titles. 
	For example:

		mypane.subpane[i][j].addTabs(['Tab 1', 'Tab 2', 'Tab 3']

	would create 3 tabs in the subpane with titles 'Tab 1', 'Tab 2' and 'Tab 3'. We can write content to a tab
	(using the innerHTML property):

		mypane.subpane[i][j].tabs[k].write('content')

	or erase the contents using the erase() function.  Note: the write() function does not overwrite the existing content, 
	but instead appends to the end.

*/
var div_thickness = 5;

function pane (rows,cols,container, options) {
	this.element = new Element ('div',{ 'class':'pane' });

	this.n_cols = cols;
	this.n_rows = rows;

	// Create two dimensional array of subpanes
	this.subpane = new Array(rows);
	for (var i = 0; i<rows; i++)
		this.subpane[i] = new Array(cols);

	this.subpane_list = new Array(rows*cols);	// this stores subpanes in a 1D array; makes for easier accessing

	this.v_divider = [];
	this.h_divider = [];
	this.options = options;
	(this.options.minWidth ? this.minWidth = this.options.minWidth : this.minWidth = 0);
	(this.options.effects ? this.effects = this.options.effects : this.effects = 'full');

	// Set size to fit container div
	this.element.setStyle({
		position: 'absolute',
		overflow: 'hidden',
		top: 0 + 'px',
		left: 0 + 'px',
		width:'100%',
		height:'100%'
	});

	// Update all subpanes positions by calling the update functions of all dividers
	this.updateAll = function () {
		this.v_divider.each( function (d) { d.eventOnEnd(); } );
		this.h_divider.each( function (d) { d.eventOnEnd(); } );
	}
	// Similarly, resize all subpanes -- called on resize event
	this.resizeAll = function () {
		var new_width = this.element.getWidth(); var old_width = this.width;
		var new_height = this.element.getHeight(); var old_height = this.height;
		this.v_divider.each ( function (d) { d.setInitialOffset(); d.setPosition(d.getPosition() / old_width * new_width) } );
		this.h_divider.each ( function (d) { d.setInitialOffset(); d.setPosition(d.getPosition() / old_height * new_height) } );

		this.width = new_width; this.height = new_height;
		this.updateAll();
	}
	this.attachTo(container);
}
	pane.prototype.swapViewers = function(i,j) {
		i_row = Math.floor(i/this.n_cols);
		i_col = i - i_row*this.n_cols;
		j_row = Math.floor(j/this.n_cols);
		j_col = j - j_row*this.n_cols;
		
		s_i = this.subpane[i_row][i_col];
		s_j = this.subpane[j_row][j_col];		

		viewer_i = s_i.getViewer().detach();
		viewer_j = s_j.getViewer().detach();
		viewer_i.attachTo(s_j.element);
		viewer_j.attachTo(s_i.element);
		s_i.insertViewer(s_j);
		s_j.insertViewer(s_i);
	}

	// Initializes the pane object and appends to containing element
	pane.prototype.attachTo = function (el) {
		el.appendChild(this.element);
	
		this.width = this.element.getWidth();
		this.height = this.element.getHeight();
		
		// Create subpanes and dividers
		for ( i = 0 ; i < this.n_rows ; i++ ) {
			for (j=0; j< this.n_cols; j++) {
				this.subpane[i][j] = new subpane(this);
				this.subpane[i][j].element.id = i+','+j;
				this.subpane_list.push(this.subpane[i][j]);
			}
		}
		for (i=0;i<this.n_cols-1;i++) {
			this.v_divider[i] = new divider ( this, i, true , this.minWidth);
			(this.options.initPosX ?
				this.v_divider[i].setPosition(this.options.initPosX[i] / 100 *this.width) :
				this.v_divider[i].setPosition((i+1)/this.n_cols*this.width) )
		}
		for (i=0;i<this.n_rows-1;i++) {
			this.h_divider[i] = new divider ( this, i, false , this.minWidth);
			(this.options.initPosY ?
				this.h_divider[i].setPosition(this.options.initPosY[i] / 100 * this.height) :
				this.h_divider[i].setPosition((i+1)/this.n_rows*this.height) )
		}
/*		
		this.intersect = new Element('div');
		this.intersect.setStyle({
			position:'absolute',
			border:'1px solid black',
			width:'20px',
			height:'20px',
			zIndex:10
		});
		this.element.appendChild(this.intersect);
		this.intersect.observe('mouseover', this.active.bind(this));
*/
		Event.observe(window,'resize',this.resizeAll.bindAsEventListener(this));
		this.updateAll();
	}

	// Returns all vsviewer objects in this pane
	pane.prototype.getViewers = function () { 
		var viewers = [];
		for (i=0;i<this.n_rows;i++) {
			for (j=0;j<this.n_cols;j++) {
				if (this.subpane[i][j].isViewer)
					viewers.push(this.subpane[i][j].getViewer())
			}
		}
		return viewers;
	}
	// Returns all subpane objects in this pane
	pane.prototype.getSubpanes = function () {
		var subpanes = [];
		for (i=0;i<this.n_rows;i++) {
			for (j=0;j<this.n_cols;j++) {
				subpanes.push(this.subpane[i][j])
			}
		}
		return subpanes;		
	}

// subpane objects are created when a pane object is instantiated. They are the child elements of the pane object.
function subpane (par) {
	this.element = new Element('div', {'class':'subpane'});
	
	this.parent_pane = par;	
	this.tabs = [];
	this.tab_handles = [];
	this.isViewer = false;

	// Initialize to fit entire pane, but we'll resize later

	this.element.setStyle({
		position:'absolute',
		top: '0px',
		left: '0px',
		width: '100%',
		height: '100%'
	});
	this.parent_pane.element.appendChild(this.element);
}
	// The following methods set the edge position in pixels -- measured from the top left of parent pane
	subpane.prototype.setRight   = function (val) { this.element.setStyle({ width: (val - this.element.offsetLeft) + 'px' }); }
	subpane.prototype.setLeft    = function (val) { 
		this.element.setStyle({ left: val + 'px' });
		this.setRight(this.parent_pane.width);
	}
	subpane.prototype.setTop     = function (val) { if(this.isSidebar) return;
		this.element.setStyle({ top: val + 'px' }); 
		this.setBottom(this.parent_pane.height);
	}
	subpane.prototype.setBottom  = function (val) { if(this.isSidebar) return; 
		this.element.setStyle({ height: (val - this.element.offsetTop) + 'px' }); 
	}
	subpane.prototype.makeSidebar = function () { this.element.setStyle({ top:'0px', height: '100%', zIndex:5 }); this.isSidebar = true; return this; }

	// Inserts viewer object into subpane
	subpane.prototype.insertViewer = function (viewer) {
		this.isViewer = true;
		this.viewer = viewer;
	}
	subpane.prototype.getViewer = function () { return this.viewer }
	subpane.prototype.addTabs = function (titles) {
		var tab_menu = new Element('div',{'class':'tab-menu'});

		this.content.appendChild(tab_menu);
		
		// add tabs and tab handles
		n = titles.length;
		for (i=0; i < n; i++) {
			this.tab_handles[i] = new Element('div', {'class':'tab-handle tab-unselected'} );
			this.tab_handles[i].setStyle({
				float:'left',
				top: 0 + 'px',
				height: '100%'
			});
			this.tab_handles[i].innerHTML = titles[i];
			tab_menu.appendChild(this.tab_handles[i]);
	
			new_tab = new tab(this, this.tab_handles[i]);
			this.tabs.push ( new_tab );
			this.content.appendChild( new_tab.element );
	
			// observe click -- show clicked tab
			this.tab_handles[i].observe('click', this.tabs[i].select.bindAsEventListener(this.tabs[i]));
		}
	
		this.resizeTabs();
	}
	// Keep viewer position fixed relative to the screen
	subpane.prototype.moveViewer = function (currOffset, initOffset, isVertical) {
		if (!this.isViewer) return;
		if (isVertical)
			this.viewer.element.style.left = (this.viewer.offsetX - currOffset + initOffset) + 'px';
		else
			this.viewer.element.style.top = (this.viewer.offsetY - currOffset + initOffset) + 'px';
	}
	// Handles the tab sizes -- this could be improved & expanded upon to allow min / max tab sizes
	subpane.prototype.resizeTabs = function () {
		if (this.tabs.length < 1) return;
		
		var n = this.tab_handles.length;
		var w = Math.max((this.element.getWidth()) / n - 2, 0); // FIX THIS!!
		this.tab_handles.each( function(th,i) {
			th.setStyle({
				left: i * w + 'px',
				width: w + 'px'
			});
		});
		var tabbody_w = Math.max(this.element.offsetWidth - 69, 0)
		this.tabs[0].element.style.width = tabbody_w + 'px';
		/*
		this.tabs.each( function(t,i) {
			t.element.setStyle({
				width: tabbody_w + 'px'
			});
		});
		*/
	}

/* 
	divider objects are draggable and allow resizing of subpanes. A divider may be either horizontal or vertical as specified
	by the 'vert' argument. 
*/
function divider (par, i, vert, minWidth) {
	this.element = new Element('div', {'class':'divider'});

	this.parent_pane = par;
	this.isVertical = vert;
	this.i = i;
	this.thickness = div_thickness;
	this.minWidth = minWidth;

	// Make our divider a Draggable object
	if (this.isVertical) {
		this.draggable = new Draggable(this.element, { constraint:'horizontal', zindex:2, bounded:'true', starteffect:'',endeffect:'',
			onStart:this.setInitialOffset.bind(this), change:this.eventOnDrag.bind(this),onEnd:this.eventOnEnd.bind(this) });
		this.element.setStyle({ width: this.thickness + 'px', top: '0px', height:'100%', cursor:'E-resize', position:'absolute' });
	} else {
		this.draggable = new Draggable(this.element, { constraint:'vertical', zindex:2, bounded:'true', starteffect:'',endeffect:'',
			onStart:this.setInitialOffset.bind(this), change:this.eventOnDrag.bind(this), onEnd:this.eventOnEnd.bind(this)});
		this.element.setStyle({ height: this.thickness + 'px', left: '0px', width:'100%', cursor:'N-resize', position:'absolute' });
	}

	this.eventDblClick = [this.collapse.bind(this), this.expand.bind(this)];
	this.element.observe('dblclick', this.eventDblClick[0]);

	this.init();
	this.parent_pane.element.appendChild(this.element);

	this.element.observe('mouseover', function(event) { Event.element(event).style.opacity = 0.3 });
	this.element.observe('mouseout', function(event) { Event.element(event).style.opacity = 1.0 });
}
	// these functions interact with the Draggable object to "bound" the element -- that is, limit it to a certain range of positions
	divider.prototype.setBound = function (property, val) { this.draggable.setBound(property, val) }
	divider.prototype.getMinBound = function () { return (this.isVertical ? this.draggable.leftbound : this.draggable.topbound) }
	divider.prototype.getMaxBound = function () { return (this.isVertical ? this.draggable.rightbound : this.draggable.bottombound) }

	// getPosition() and setPosition() both are in terms of pixels offset from the left (or top)
	divider.prototype.getPosition = function () { return (this.isVertical ? (this.element.offsetLeft) : (this.element.offsetTop) ) }
	divider.prototype.setPosition = function (pos) { 
		(this.isVertical ? 
			this.element.setStyle({ left: pos + 'px' }) :
			this.element.setStyle({ top: pos + 'px' }) )
	}
	divider.prototype.moveTo = function (pos) {
		(this.isVertical ?
			new Effect.Move(this.element, {x:pos, y:0, mode:'absolute', duration:0.2, afterUpdate:this.eventOnDrag.bind(this), afterFinish:this.eventOnEnd.bind(this) }) :
			new Effect.Move(this.element, {x:0, y:pos, mode:'absolute', duration:0.2, afterUpdate:this.eventOnDrag.bind(this), afterFinish:this.eventOnEnd.bind(this) }) )
	}
	divider.prototype.setInitialOffset = function() { this.initOffset = this.getPosition() }

	// Initializes the subpanes that are adjacent to the divider --
	//		lower_subpanes are to the left or above the divider for vertical or horizontal dividers, respectively
	//		upper_subpanes are to the right or below the divider for vertical or horizontal dividers, respectively
	divider.prototype.init = function () {
		var m = this.parent_pane.n_rows; var i = this.i;
		this.lower_subpanes = []; this.upper_subpanes = [];
		if (this.isVertical) {
			for (k = 0; k < m ; k++) {
				this.lower_subpanes.push(this.parent_pane.subpane[k][i]);
				this.upper_subpanes.push(this.parent_pane.subpane[k][i + 1]);
			}
		} else {
			this.lower_subpanes = this.parent_pane.subpane[i];
			this.upper_subpanes = this.parent_pane.subpane[i+1];
		}
	}
	
	// called onDrag -- Update positions of adjacent divs
	divider.prototype.eventOnDrag = function () {
		if (this.isVertical) {
			for (var k=0; k<this.lower_subpanes.length; k++) {
				this.lower_subpanes[k].setRight(this.getPosition());
				this.lower_subpanes[k].resizeTabs(); 
				}
			for (var k=0; k<this.upper_subpanes.length; k++) {
				this.upper_subpanes[k].setLeft(this.getPosition() + this.thickness); 
				this.upper_subpanes[k].resizeTabs();
				this.upper_subpanes[k].moveViewer(this.getPosition(),this.initOffset,this.isVertical);
			}
		} else {
			for (var k=0; k<this.lower_subpanes.length; k++) {
				this.lower_subpanes[k].setBottom(this.getPosition());
				//this.lower_subpanes[k].resizeTabs(); 
			}
			for (var k=0; k<this.upper_subpanes.length; k++) {
				this.upper_subpanes[k].setTop(this.getPosition() + this.thickness);
				//this.upper_subpanes[k].resizeTabs();
				this.upper_subpanes[k].moveViewer(this.getPosition(),this.initOffset,this.isVertical);
			}
		}

		if (window.opera) {
                	document.body.style += '';
            	}
		
	}
	// called onEnd (of drag) -- Update positions of adjacent divs and the bounds of adjacent Draggables
	divider.prototype.eventOnEnd = function () {
		var minWidth = this.minWidth;
		this.eventOnDrag();

		if (this.isVertical) {
			(this.i > 0 ? this.parent_pane.v_divider[this.i-1].draggable.setBound('right' , (this.getPosition() - this.thickness -minWidth) ) : this.setBound('left', minWidth ));
			(this.i+1 < this.parent_pane.n_cols-1 ? 
				this.parent_pane.v_divider[this.i+1].setBound( 'left' , this.getPosition() + this.thickness +minWidth ) : 
				this.setBound( 'right' , (this.parent_pane.element.getWidth() - this.element.getWidth() -minWidth) )
			);
		} else {
			(this.i > 0 ? this.parent_pane.h_divider[this.i-1].setBound( 'bottom' , this.getPosition() - this.thickness -minWidth) : this.setBound( 'top' , minWidth ) );
			(this.i+1 < this.parent_pane.n_rows-1 ? 
				this.parent_pane.h_divider[this.i+1].setBound( 'top' , this.getPosition() +minWidth) : 
				this.setBound( 'bottom' , (this.parent_pane.element.getHeight() - this.thickness - minWidth) )
			);
		}
		this.parent_pane.getViewers().each( function(v) { v.offsetX = v.element.offsetLeft; v.offsetY = v.element.offsetTop; v.setBounds() });
	}

	// Collapse a divider (& adjacent subpane) -- called on double click event
	divider.prototype.collapse = function () { 
		this.setInitialOffset();
		 (this.initOffset -this.getMinBound() < this.getMaxBound() - this.initOffset ?
			this.moveTo(this.getMinBound() - this.minWidth) :
			this.moveTo(this.getMaxBound() + this.minWidth) )

		Event.stopObserving(this.element, 'dblclick',this.eventDblClick[0]);	
		this.element.observe('dblclick', this.eventDblClick[1]);

		this.draggable.disable();
		this.eventOnEnd();
	}
	// Expand a divider (& adjacent subpane) -- called on double click event
	divider.prototype.expand = function () {
		var last_pos = this.initOffset;
		this.setInitialOffset();
		this.moveTo(last_pos);

		this.element.stopObserving('dblclick',this.eventDblClick[1]);
		this.element.observe('dblclick', this.eventDblClick[0]);

		this.draggable.enable();
		this.eventOnEnd();
	}

// tab objects are just that -- tabs
function tab (par, handle) {
		this.parent_subpane = par;
		this.handle = handle;

		this.element = new Element('div', {'class':'tab'});
}
	tab.prototype.select = function () {
		this.parent_subpane.tabs.without(this).each( function(tab) { tab.deselect(); } ); // deselect the tabs that weren't clicked
		this.element.show();
		this.handle.removeClassName('tab-unselected');
		this.handle.addClassName('tab-selected');
		
		if (window.opera) {
		    document.body.style += '';
		}
	}
	tab.prototype.deselect = function () {
		this.element.hide();
		this.handle.removeClassName('tab-selected');
		this.handle.addClassName('tab-unselected');
	}
	tab.prototype.write = function (content) { this.element.innerHTML += '<p>'+content; }
	tab.prototype.erase = function () { this.element.innerHTML = ''; }

function debug (msg) { $('debug').innerHTML = msg; }
function redraw() { if (window.opera) { document.body.style += '' } }
