// Ortho -  A Javascript Graphics library, built on top of the Prototype JavaScript library

// Copyright (c) 2007  Robert Jones, Craic Computing LLC  http://www.craic.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// orthoBase is a set of functions that are used in the main classes

var orthoBase = {

	getParents: function(parent) {
		// Get the parent and the panel objects that this object
		// resides in
		this.parentObject = parent;
		if(parent != null) {
			while(parent.orthoClass != 'orthoPanel') {
				parent = parent.parentObject;
			}
		}
		this.panelObject = parent;
		this.panelObject.childObjects.push(this);
	},

	setCssClasses: function(options, default_class) {
		this.cssClassName = (options.cssClass == undefined) ? default_class : options.cssClass;
		delete options.cssClass;
      	this.element.addClassName(this.cssClassName);
		// use a default name for highlight - or pick it up from the options
		this.cssClassNameHighlight = this.cssClassName + '-highlight';
	},

	// Utility functions to set and get arbitrary metadata (user data)

	setMetadata: function(proplist) {
		if(this.metadata == null) {
			this.metadata = new Hash();
		}
		this.metadata.merge(proplist);		
	},
	
	getMetadata: function(key) {
		if(this.metadata == null) {
			return null;
		}
		if(key != undefined) {
			return (this.metadata[key] == undefined) ? null : this.metadata[key];
		} else {
			// return the JSON of the hash for all keys
			return this.metadata.toJSON();
		}
	},

	dumpMetadata: function() {
		// Dump the entire hash as a JSON string
		if(this.metadata == null) {
			return null;
		}
		return this.metadata.toJSON();
	},
	
	// Utility functions to move an element and set Z index

 	setZIndex: function(zindex) {
		// if no zindex is specified then place the item at the
		// front (highest zindex), else place it according to the
		// supplied integer. If the zindex is the string 'front' then
		// move it to the front, if 'back' then move it to the back
		if(zindex == undefined) {
			this.zindex = this.panelObject.maxZIndex++;		
		} else if(zindex == 'front') {
			this.zindex = this.panelObject.maxZIndex++;		
		} else if(zindex == 'back') {
			this.panelObject.minZIndex--
			this.zindex = this.panelObject.minZIndex;		
		} else {
			this.zindex = zindex;
			if(zindex > this.panelObject.maxZIndex) {
				this.panelObject.maxZIndex = zindex;
			}
		}
		this.element.setStyle({zIndex: this.zindex});	
	},
	
	moveRelative: function(x, y) {
		// move the div by x and y (not to x and y)
		element = this.element;
		var xnew = parseInt(element.getStyle('left')) + x;
		var ynew = parseInt(element.getStyle('top')) + y;
		element.setStyle({left: xnew + 'px', top: ynew + 'px'});
		return element;
    },

	moveAbsolute: function(x, y) {
		element = this.element;
		element.setStyle({left: x + 'px', top: y + 'px'});
		return element;
    }

// need a destructor function but 'delete' won't work with 
// objects defined with var...
//	remove: function() {
//		// Delete the element and this object
//		this.element.remove();
//	}

}

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

var ortho = Class.create();

Object.extend(ortho, {

	// This function ensures completed preloading of any
	// images before calling the user's main()
	// Default name of that is 'main' but any other can be 
	// passed in the function arg
	main: null,
	
	init: function(usermain) {

		// Default main function is called 'main'
		ortho.main = (usermain) ? usermain : main;
		
		if(orthoImage.imagesTotal == 0) {
			// If there are no images then run main() directly
			ortho.main();
		} else {
			// Preload the images and only run main after they are all loaded
			orthoImage.images.each(function(img) {
				orthoImage.preload(img);
			});
		}
	}
	
});


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

var orthoPanel = Class.create();

Object.extend(orthoPanel, {
	count: 0
});

orthoPanel.prototype = {

	orthoClass: 'orthoPanel',
	// Array of all child objects
	childObjects: null,
	// The highest current Z Index for any element on this panel
	// Set this to a non-zero value so there is room to push objects
	// to the back
	maxZIndex: 100,
	minZIndex: 100,

	initialize: function(id, left, top, width, height) {

		var options = arguments[5] || {};

		if(id != null) {
			// Use a pre-existing div
			var div = $(id);
		} else {
			var div = document.createElement('div');
			div = $(div);
			div.id = this.orthoClass + '_' + orthoPanel.count++;
	    	div.setStyle({position: 'absolute',
							  left: left + 'px',
					 	       top: top + 'px'});
			var body = document.getElementsByTagName("body").item(0);
			body.appendChild(div);
		}

		div.setAttribute('name', this.orthoClass);
		this.element = div;

		this.cssClassName = (options.cssClass == undefined) ? this.orthoClass : options.cssClass;
      	div.addClassName(this.cssClassName);
		delete options.cssClass;

	    div.setStyle({width: width + 'px',
					 height: height + 'px'});

		// Any remaining options are treated as styles
		// Just passing the hash works in Firefox, not in Safari....
		div.setStyle(options);


		this.childObjects = new Array();
	},

	getObjectFromElement: function(element) {
		// Given an element in a specific panel, search the objects
		// on that panel and return the one that 'contains' that element
		var obj = null;
		this.childObjects.each( function(child) {
			if(child.element.id == element.id) {
				obj = child;
				return;
			}
		});
		return obj;
	}
	
}


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

var orthoRectangle = Class.create();

Object.extend(orthoRectangle, {
	count: 0
});

orthoRectangle.prototype = {
	orthoClass: 'orthoRectangle',

	initialize: function(parent, left, top, width, height) {

		var options = arguments[5] || {};

	    var div = document.createElement('div');
		div = $(div);
		div.id = this.orthoClass + '_' + orthoRectangle.count++;
	    div.setAttribute('name', this.orthoClass);
		this.element = div;
		this.getParents(parent);
		this.setZIndex();
		this.setCssClasses(options, this.orthoClass);

	    div.setStyle({position: 'absolute',
					visibility: 'hidden',
						   top: top + 'px',
						  left: left + 'px',
						 width: width + 'px',
					    height: height + 'px' });

		// Any remaining options are treated as styles
		div.setStyle(options);

	    this.parentObject.element.appendChild(div);
		
		var borderWidth = parseInt(div.getStyle('border-left-width'));
		if(borderWidth == '' || isNaN(borderWidth)) {
			borderWidth = 0;
		}

   		div.setStyle({ width: (width  - (2*borderWidth)) + 'px',
			       	  height: (height - (2*borderWidth)) + 'px'});

		// Make the object visible, unless overidden by an option
		if(options.visibility == undefined || options.visibility != 'hidden') {
   		   div.setStyle({ visibility: 'visible'});
		}
	}
}

Object.extend(orthoRectangle.prototype, orthoBase);


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

var orthoLine = Class.create();
Object.extend(orthoLine, {
	count: 0
});

orthoLine.prototype = {
	orthoClass: 'orthoLine',

	initialize: function(parent, left, top, width, height) {

		var options = arguments[5] || {};

		var linetype = null;
		if(width > 0 && height == 0) {
			linetype = 'horizontal';
		} else if(width == 0 && height > 0) {
			linetype = 'vertical';
		} else {
			// error - but default to horizontal
			linetype = 'horizontal';
		}

		var name = this.orthoClass + linetype.capitalize();
		
	    var div = document.createElement('div');
		div = $(div);
		div.id = name + orthoLine.count++;
	    div.setAttribute('name', name);
		this.element = div;
		this.getParents(parent);		
		this.setZIndex();
		this.setCssClasses(options, name);
		
	    div.setStyle({position: 'absolute',
					visibility: 'hidden',
						  left: left   + 'px',
						   top: top    + 'px',
						 width: width  + 'px',
						height: height + 'px'});

		// Any remaining options are treated as styles
		div.setStyle(options);

	    parent.element.appendChild(div);

		// Make the object visible, unless overidden by an option
		if(options.visibility == undefined || options.visibility != 'hidden') {
   		   div.setStyle({ visibility: 'visible'});
		}

	}
}

Object.extend(orthoLine.prototype, orthoBase);


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

var orthoLabel = Class.create();
Object.extend(orthoLabel, {
	count: 0
});

orthoLabel.prototype = {
	orthoClass: 'orthoLabel',

	initialize: function(parent, left, top, string, placement) {
		var options = arguments[5] || {};

		var div = document.createElement('div');
		div = $(div);
		div.id = this.orthoClass + '_' + orthoLabel.count++;
	    div.setAttribute('name', this.orthoClass);
		this.element = div;
		this.getParents(parent);	
		this.setZIndex();
		this.setCssClasses(options, this.orthoClass);

	    div.setStyle({position: 'absolute',
					visibility: 'hidden',
					   	  left: left + 'px',
						   top: top + 'px' });
		var textNode = document.createTextNode('');
		div.appendChild(textNode);

		// Any remaining options are treated as styles
		div.setStyle(options);

	    parent.element.appendChild(div);
	
	    // Update the div to add the real string
		div.update(string);

		// Handle the image placement - default is bottom-right - like rectangle
		var labelDims = div.getDimensions();

		if(placement == undefined) {
			placement = 'bottom-right';
		}
		placement = placement.toLowerCase();

		var mode = (options['relation'] != undefined) ? 'relative' : 'absolute';

		if(mode == 'relative') {
			// Get dimensions of the containing element
			var elementDims = parent.element.getDimensions();
			var xOffset = Math.floor(elementDims.width/2  - labelDims.width/2);
			var yOffset = Math.floor(elementDims.height/2 - labelDims.height/2);

			if(options['relation'] != undefined && options['relation'].match(/inside/i)) {
				// INSIDE the parent object
				if(placement != undefined && !placement.match(/center/i)) {
					// Horizontal placement
					if(placement.match(/left/i)) {
						xOffset = 0;
					} else if(placement.match(/right/i)) {
					    xOffset = elementDims.width - labelDims.width - 1;	
					}
					// Vertical placement
					if(placement.match(/top/i)) {
					    yOffset = 0;	
					} else if(placement.match(/bottom/i)) {
						yOffset = elementDims.height - labelDims.height - 1;
					}
			    }
		    } else {
				// OUTSIDE the parent object
				if(placement != undefined && !placement.match(/center/i)) {
					// Horizontal placement
					if(placement.match(/left/i)) {
					    xOffset = - labelDims.width - 1;	
					} else if(placement.match(/right/i)) {
						xOffset = elementDims.width;
					}
					// Vertical placement
					if(placement.match(/top/i)) {
					    yOffset = - labelDims.height;	
					} else if(placement.match(/bottom/i)) {
						yOffset = elementDims.height;
					}
			    }
		    }

		} else {
			// Absolute placement - relative to the supplied left and top values

			var xOffset = left - Math.round(labelDims.width/2);
			var yOffset = top  - Math.round(labelDims.height/2);
			
			if(placement != undefined && !placement.match(/center/i)) {
				// Horizontal placement
				if(placement.match(/left/i)) {
					xOffset = left - labelDims.width;
				} else if(placement.match(/right/i)) {
				    xOffset = left;	
				}
				// Vertical placement
				if(placement.match(/top/i)) {
					yOffset = top - labelDims.height;
				} else if(placement.match(/bottom/i)) {
				    yOffset = top;
				}
			}
		}
	    div.setStyle({ left: xOffset + 'px',
					    top: yOffset + 'px'});

		// Make the object visible, unless overidden by an option
		if(options.visibility == undefined || options.visibility != 'hidden') {
   		   div.setStyle({ visibility: 'visible'});
		}

	}
}

Object.extend(orthoLabel.prototype, orthoBase);

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

var orthoImage = Class.create();
Object.extend(orthoImage, {
	imagesTotal: 0,
	imagesLoaded: 0,
	count: 0,
	src: new Hash(),
	images: new Array(),
	
	loadImages: function() {
		// Load the images into an array - but don't display them - this
		// kicks off image preloading
		for(var i=0; i< arguments.length; i++) {
			var name = arguments[i].gsub(/^.*\//, '');	
			orthoImage.src.set(name, arguments[i]);	
			orthoImage.imagesTotal++;

			var img = document.createElement('img');
			img = $(img);
			img.setAttribute('src', arguments[i]);

			img.setStyle({visibility: 'hidden',
							 display: 'none'
						});
			orthoImage.images.push(img);
		}
	},
	
	preload: function(element) {

		// Check that an image has finished loading - loop with a delay
		// until done, and then call ortho.main()
		
		if(element.complete) {
 			orthoImage.imagesLoaded++;
			if(orthoImage.imagesLoaded == orthoImage.imagesTotal) {
				ortho.main();
			}
		} else {
			orthoImage.preload.delay(0.25, element);
		}
		
	}

});


orthoImage.prototype = {
	orthoClass: 'orthoImage',

	initialize: function(parent, left, top, name, placement) {

		var options = arguments[5] || {};
		// Default placement is 'bottom-right'
		if(placement == undefined) {
			placement = 'bottom-right';
		}
		placement = placement.toLowerCase();

// Look into using cloneNode() on the original image object...

		var div = document.createElement('img');
		div = $(div);
		div.id = this.orthoClass + '_' + orthoImage.count++;
	    div.setAttribute('name', this.orthoClass);
		this.element = div;
		this.getParents(parent);
		this.setZIndex();
		this.setCssClasses(options, this.orthoClass);

	    div.setAttribute('src', orthoImage.src.get(name));

	    div.setStyle({position: 'absolute',
					visibility: 'hidden' });

		// Any remaining options are treated as styles
		div.setStyle(options);

		// To be added... detect if we're trying to display a PNG format image
		// under Internet Explorer... if so add a IE Filter to the style info
		// to handle Alpha transparency....		

	    parent.element.appendChild(div);

		// get image dimensions after display
		var dims = div.getDimensions();
		var imageWidth = dims.width;
		var imageHeight = dims.height;

		var xOffset = Math.floor(imageWidth/2);
		var yOffset = Math.floor(imageHeight/2);

		// Horizontal placement
		if(placement.match(/left/i)) {
		    xOffset = imageWidth;
		} else if(placement.match(/right/i)) {
			xOffset = 0;
		}
		// Vertical placement
		if(placement.match(/top/i)) {
		    yOffset = imageHeight;
		} else if(placement.match(/bottom/i)) {
			yOffset = 0;
		}

	    div.setStyle({left: (left - xOffset) + 'px',
				  		top: (top  - yOffset) + 'px'});

		// Make the object visible, unless overidden by an option
		if(options.visibility == undefined || options.visibility != 'hidden') {
   		   div.setStyle({ visibility: 'visible'});
		}

	}	
}

Object.extend(orthoImage.prototype, orthoBase);

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



