/* (C) 2009-present Eshkol Institute. All rights reserved.
 *
 * This script file deals with commonly used javascript. It should be loaded
 * before any javascript and on all javascript-enabled webpages.
 * All methods within this script file are attempted to be cross-browser
 * supported.
 *
 * Author: Ben Barkay
 * Version: 1.0
 */
////////////////////////////////////////////////////////////////////////////////
//  - Commonly used functions.
////////////////////////////////////////////////////////////////////////////////

/**
 * Returns false. It is common that a function that returns false will be
 * pre-existant for things such as event disabling.
 */
function falsify() {
    return false;
}

////////////////////////////////////////////////////////////////////////////////
//  - Event Management
//  The event manager enables cross-browser event management support.
////////////////////////////////////////////////////////////////////////////////
// Initialize the event manager package object.
var EventManager = new Object();

/**
 * Adds an event handler to the specified element and event type, calling the
 * specified callback.
 *
 * @param element   The element to add the event handler to.
 * @param eventType The type of event to handle.
 * @param callback  The callback function handling the specified event.
 */
EventManager.addEventHandler = function(element, eventType, callback) {
    if(element.attachEvent) {
        element.attachEvent('on' + eventType, callback);
    } else if(element.addEventListener) {
        element.addEventListener(eventType, callback, false);
    } else throw 'Not element.attachEvent nor element.addEventListener supported. '
        + 'I don\'t know how to add an event handler.';
};

////////////////////////////////////////////////////////////////////////////////
//  - Canvas Control
//  The canvas package contains methods used for canvas information retrieval
//  and control.
////////////////////////////////////////////////////////////////////////////////
// Initialize the canvas package object.
var Canvas = new Object();

/**
 * Retrieves an element's textual content.
 *
 * @param e The element to retrieve content for.
 */
Canvas.getTextualContent = function(e) {
	// Check if the parameter element is not nullpointer.
	if(e != null) {
		// W3 compatible browsers should support textContent, therefore this check
		// comes first.
		if(typeof e.textContent == 'string') {
			return e.textContent;
		}
		
		// Microsoft IE and other broken browsers usually support innerText.
		if(typeof e.innerText == 'string') {
			return e.innerText;
		}
		
		// No more checks I know of, at this point we give up and
		// throw an exception
		throw 'Not element.textContent nor element.innerText are supported by this '
			+ 'browser. I don\'t know how to get textual content.';
	}
};

/**
 * Sets the textual content for the specified element.
 *
 * @param e The element to set textual content for.
 * @param t The textual content to set.
 */
Canvas.setTextualContent = function(e,t) {
  e.textContent = t;
  e.innerHTML = t;  
};

/**
 * Clears an element from all of its childs.
 */
Canvas.clearElement = function(e) {
    // Keep removing child nodes until there are no childs left.
    while(e.childNodes.length > 0) {
        // There must be a first child to remove given there are childs, this is
        // an array.
        e.removeChild(e.childNodes[0]);
    }
};

/**
 * Returns the point where the specified element is located on the canvas.
 */
Canvas.getPosition = function(o) {
    var left = 0;
    var top = 0;
    do {
        left    += o.offsetLeft;
        top     += o.offsetTop;
        o        = o.offsetParent;
    } while(o);
    
    return new Canvas.Point(left, top);
};

/**
 * Returns the dimensions of an object on the canvas.
 */
Canvas.getDimensions = function(o) {
    if(o.clip) {
        return new Canvas.Dimension(o.clip.width, o.clip.height);
    }
    
    if(o.style && o.style.pixelWidth) {
        return new Canvas.Dimension(o.style.pixelWidth, o.style.pixelHeight);
    }
    
    if(o.offsetWidth) {
        return new Canvas.Dimension(o.offsetWidth, o.offsetHeight);
    }
}

/**
 * The dimension class represents a dimension attributing width and height.
 */
Canvas.Dimension = function(width, height) {
    this.width = width;
    this.height = height;
}

/**
 * Returns this dimension's width.
 */
Canvas.Dimension.prototype.getWidth = function() {
    return this.width;
};

/**
 * Returns this dimension's height.
 */
Canvas.Dimension.prototype.getHeight = function() {
    return this.height;
};

// Define dimension attributes.
Canvas.Dimension.prototype.width = 0;
Canvas.Dimension.prototype.height = 0;

/**
 * Represents canvas point object.
 *
 * @param x The X coordinate for the point.
 * @param y The Y coordinate for the point.
 */
Canvas.Point = function(x,y) {
    this.x = x;
    this.y = y;
};

/**
 * Returns the X coordinate for this point.
 */
Canvas.Point.prototype.getX = function() {
    return this.x; 
}

/**
 * Returns the Y coordinate for this point.
 */
Canvas.Point.prototype.getY = function() {
    return this.y;
}

// Declare point's attributes.
Canvas.Point.prototype.x = -1;
Canvas.Point.prototype.y = -1;

/**
 * The canvas fader class enables element fading through styles.
 *
 * @param e The element to fade.
 */
Canvas.Fader = function(e) {
    this.element = e;
};

// Define fader's constants (show/hide.)
Canvas.Fader.SHOW = 1;
Canvas.Fader.HIDE = 2;

/**
 * Hides the element this fader handles using the specified settings.
 *
 * @param fps           The amount of frames to display per second.
 * @param speed         The amount of time it takes to fade the element. 1 being
 *                      100 seconds (1 second for each percent of opacity
 *                       movement.)
 * @param callback      If specified, this function will be called upon fading
 *                      completion.
 * @param recursiveCall Whether or not this has been called in a recursive
 *                      manner (called by a Canvas.Fader function.)
 */
Canvas.Fader.prototype.hide = function(fps, speed, callback, recursiveCall) {
    // Check if a show request has been initiated by an outside entity.
    if(recursiveCall && this.action == Canvas.Fader.SHOW) {
        // The hide operation should be ceased, an outside entity began show.
        return;
    }
    
    // Check if this method has been called by an outside entity. If so, a new
    // fading operation should begin from this standing point.
    if(!recursiveCall) {
        // The entity requested a hide, which begins right now. Set the first
        // action to now.
        this.firstAction = new Date().getTime();
        
        // Set the last action time.
        this.lastAction = new Date().getTime();
        
        // The action this fader is performing is hide now.
        this.action = Canvas.Fader.HIDE;
        
        // Set the callback function for when this operation has been completed.
        this.callback = callback;
        
        // Set the starting opacity for this element.
        this.startOpacity = this.getElementOpacity();
        
        // Recurse and return.
        this.hide(fps, speed, callback, true);
        return;
    }
    
    // If we are here then this is a recursive call to hide the element.
    // Calculate the time passed since the time hide has been called.
    var timeFactor = new Date().getTime() - this.firstAction;
    
    // Calculate the amount of opacity that needs to be hidden within this
    // hide operation.
    var opacityFraction = this.startOpacity;
    
    // Calculate the time (in seconds) it should take for the element to
    // complete the fade according to the speed.
    var completionTime = (opacityFraction * 100) / speed;

    // Calculate the opacity that should be set at this time.
    var opacity = this.startOpacity - (opacityFraction *
        (timeFactor / completionTime));
    
    // If the opacity is less than or equal to 0, the operation has been
    // complete.
    var operationComplete = opacity <= 0;
    
    // If operation is complete, set the opacity to 0 to prevent negative
    // opacity percentage.
    if(operationComplete)
        opacity = 0;
    
    //alert('timeFactor: ' + timeFactor + "\nopacityFraction: " + opacityFraction
    //    + "\ncompletionTime: " + completionTime);
    
    // Set the opacity to the element.
    this.setElementOpacity(opacity);
    
    // If operation is complete, call the callback function.
    if(operationComplete) {
        this.performCallback();
    } else {
        // The operation is not yet complete. Meaning we should display another
        // frame soon.
        
        // Prepare a pointer, pointing to this object.
        var fader = this;
        
        // Calculate the time that should pass between frames.
        var timeGap = 1000 / fps;
        
        // Calculate the time that has passed since the last call to show
        // a frame.
        var timePassed = new Date().getTime() - this.lastAction;
        
        // Calculate the amount of time left until performing the next action.
        var nextAction = Math.floor(timeGap - timePassed);
        
        // We can't schedule an action to the past.
        if(nextAction < 0)
            nextAction = 0;
        
        // Set a timeout calling next frame processing.
        setTimeout(function() {
            fader.hide(fps, speed, callback, true);
        }, nextAction);
        
        // Set the last action.
        this.lastAction = new Date().getTime();
    }
};

/**
 * Shows the element this fader handles using the specified settings.
 *
 * @param fps           The amount of frames to display per second.
 * @param speed         The amount of time it takes to fade the element. 1 being
 *                      100 seconds (1 second for each percent of opacity
 *                       movement.)
 * @param callback      If specified, this function will be called upon fading
 *                      completion.
 * @param recursiveCall Whether or not this has been called in a recursive
 *                      manner (called by a Canvas.Fader function.)
 */
Canvas.Fader.prototype.show = function(fps, speed, callback, recursiveCall) {
    // Check if a hide request has been initiated by an outside entity.
    if(recursiveCall && this.action == Canvas.Fader.HIDE) {
        // The show operation should be ceased, an outside entity began hide.
        return;
    }
    
    // Check if this method has been called by an outside entity. If so, a new
    // fading operation should begin from this standing point.
    if(!recursiveCall) {
        // The entity requested a show, which begins right now. Set the first
        // action to now.
        this.firstAction = new Date().getTime();
        
        // Set the last action.
        this.lastAction = new Date().getTime();
        
        // The action this fader is performing is show.
        this.action = Canvas.Fader.SHOW;
        
        // Set the callback function for when this operation has been completed.
        this.callback = callback;
        
        // Set the starting opacity for this element.
        this.startOpacity = this.getElementOpacity();
        
        // Recurse and return.
        this.show(fps, speed, callback, true);
        return;
    }
    
    // If we are here then this is a recursive call to hide the element.
    // Calculate the time passed since the time hide has been called.
    var timeFactor = new Date().getTime() - this.firstAction;
    
    // Calculate the amount of opacity that needs to be shown within this
    // show operation.
    var opacityFraction = 1 - this.startOpacity;
    
    // Calculate the time (in seconds) it should take for the element to
    // complete the fade according to the speed.
    var completionTime = (opacityFraction * 100) / speed;
    
    // Calculate the opacity that should be set at this time.
    var opacity = this.startOpacity + (opacityFraction *
        (timeFactor / completionTime));
    
    // If the opacity is greater than or equals to 1, the operation has been
    // complete.
    var operationComplete = opacity >= 1;
    
    // If operation is complete, set the opacity to 1 to prevent overflowing
    // opacity percentage.
    if(operationComplete)
        opacity = 1;
    
    // Set the opacity to the element.
    this.setElementOpacity(opacity);
    
    // If operation is complete, call the callback function.
    if(operationComplete) {
        this.performCallback();
    } else {
        // The operation is not yet complete. Meaning we should display another
        // frame soon.
        
        // Prepare a pointer, pointing to this object.
        var fader = this;
        
        // Calculate the time that should pass between frames.
        var timeGap = 1000 / fps;
        
        // Calculate the time that has passed since the last call to show
        // a frame.
        var timePassed = new Date().getTime() - this.lastAction;
        
        var nextAction = Math.floor(timeGap - timePassed);
        
        if(nextAction < 0)
            nextAction = 0;
        
        // Set a timeout calling next frame processing.
        setTimeout(function() {
            fader.show(fps, speed, callback, true);
        }, nextAction);
        
        // Set the last action.
        this.lastAction = new Date().getTime();
    }
};

/**
 * Performs a callback to the callback function if existant, sending the current
 * action as a parameter.
 */
Canvas.Fader.prototype.performCallback = function() {
    // Check that a callback has been specified and that it is not
    // nullpointed.
    if(this.callback && this.callback != null) {
        // Call the callback function passing the type of operation as
        // a parameter.
        this.callback(this.action);
    }
}

/**
 * Sets this fader's element opacity.
 *
 * @param opacity   The opacity, ranging from 0 to 1, to set upon the element
 *                  of this fader.
 */
Canvas.Fader.prototype.setElementOpacity = function(opacity) {
    // Set the element's opacity for W3 compatiby browsers.
    this.element.style.opacity = opacity;
    
    // Set the element's opacity for microsoft browsers.
    this.element.style.filter = 'alpha(opacity=' + Math.ceil(opacity * 100)
        + ')';
};

/**
 * Returns this fader's element opacity.
 */
Canvas.Fader.prototype.getElementOpacity = function() {
    // Check if opacity has been set via styles.
    if(this.element.style.opacity) {
        // This browser is W3 compatible. Opacity is set as a setting by
        // CSS. Just return the style value.
        return this.element.style.opacity;
    } else if(this.element.style.filter) {
        // Parse opacity from the filter settings. This is probably internet
        // explorer.
        var start = this.element.style.filter.indexOf('opacity=') + 8;
        var end = this.element.style.filter.indexOf(')', start);
        
        // Return the opacity divided by 100.
        return parseInt(this.element.style.filter.substring(start, end)) / 100;
    }
    
    // If we could not determine the opacity, it probably means it's not yet
    // set. If opacity is not set, the element should be displayed fully by the
    // browser. Therefore, we return 1 (full opacity.)
    return 1;
};

// Define the Canvas.Fader prototype objects.
Canvas.Fader.prototype.element = null;
Canvas.Fader.prototype.callback = null;
Canvas.Fader.prototype.startOpacity = 1;
Canvas.Fader.prototype.firstAction = -1;
Canvas.Fader.prototype.lastAction = -1;
Canvas.Fader.prototype.action = Canvas.Fader.SHOW;

////////////////////////////////////////////////////////////////////////////////
//  - Ajax functions
//  Easy implementation of ajax within various scripts.
////////////////////////////////////////////////////////////////////////////////
// Create the ajax package object.
var Ajax = new Object();

/**
 * Opens a URL reading its content.
 *
 * @param url       The URL to open.
 * @param params    POST parameters to send. If null or undefined, the request
 *                  will be using the GET method.
 * @param callback  The callback method to call with the information about the
 *                  request. If not specified, the request will be synchronous.
 */
Ajax.open = function(url, params, callback) {
    // Initiate a new XMLHttpRequest instance for the task.
    var httpr = new XMLHttpRequest();
    
    // POST/Asynchronous flags.
    var post    = typeof params != 'undefined' && params != null;
    var async   = typeof callback != 'undefined' && callback != null;
    
    // Create an empty parameter string. In case we are sending a POST request
    // this will be a string representing the parameters specified for the
    // request.
    var paramString = '';
    
    // Open the right connection to the server.
    httpr.open(post ? 'POST' : 'GET', url, async);
    
    // If we are doing a POST request, send the appropriate headers.
    if(post) {
        // Create the parameter string.
        for(var key in params) {
            paramString += (paramString.length > 0 ? '&' : '')
                + encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
        }
        
        // Send the appropriate HTTP headers.
        httpr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        httpr.setRequestHeader('Content-Length', paramString);
        httpr.setRequestHeader('Connection', 'close');
    }
    
    // If we are being asynchronous, apply the callback, and perform the
    // request.
    if(async) {
        // Configure the callback function.
        httpr.onreadystatechange = function() {
            if(httpr.readyState == 4) {
                callback(new Ajax.Response(httpr.status, httpr.statusText,
                    httpr.responseText, httpr.responseXML));
            }
        };
        
        // Do the request, and return.
        httpr.send(post ? paramString : null);
        return true;
    }
    
    // We are not being asynchronous if we reach this point.
    // Perform the request, and return the results.
    httpr.send(post ? paramString : null);

    return new Ajax.Response(httpr.status, httpr.statusText, httpr.responseText, httpr.responseXML);
};

/**
 * An object to serve ajax responses.
 */
Ajax.Response = function(statusCode, statusText, responseText, responseXML) {
    this.statuscode = statusCode;
    this.statusText = statusText;
    this.responseText = responseText;
    this.responseXML = responseXML;
};

/**
 * Returns the status code of this response.
 */
Ajax.Response.prototype.getStatusCode = function() {
    return this.statusCode;
};

/**
 * Returns the status text of this response.
 */
Ajax.Response.prototype.getStatusText = function() {
    return this.statusText;
};

/**
 * Returns the response's text.
 */
Ajax.Response.prototype.getText = function() {
    return this.responseText;
};

/**
 * Returns the response's XML.
 */
Ajax.Response.prototype.getXML = function() {
    return this.responseXML;
};

// Set the ajax response prototype attributes.
Ajax.Response.prototype.statusCode;
Ajax.Response.prototype.statusText;
Ajax.Response.prototype.responseText;
Ajax.Response.prototype.responseXML;

