/*********************************************************************************
 * src.ac.js - Alteryx Connect Source Code
 *********************************************************************************
 *      Company: SRC, LLC
 *       Author: Ian Erickson
 *    Copyright: Copyright(c) 2008 SRC, LLC.  All rights reserved.
 * 
 *      Version: 0.0.1
 *      Vintage: 06/09/2008
 *
 * Dependencies: prototype-1.6.0.2.js (http://prototypejs.org/)
 *
 *      Classes: None.
 * 
 *    History:
 * 
 *  06/09/2008: Initial Library Build
 *  06/30/2009: Refactored for the Alteryx Connect namespace
 *
 *********************************************************************************/

/*********************************************************************************
 * Namespace
 *********************************************************************************
 * Description:	Utility to manage namespaces within the library.
 *
 *       Usage:	Namepsace.create('foo.bar');
 *
 *********************************************************************************/
var namespace = {
    separator: '.',
    create: function( space ) {
	return space.split( namespace.separator ).inject( window, function( parent, child) { return parent[child] = parent[child] || {}; });
    }
};

//Create the 'src' namespace
namespace.create('src');

//Create the 'src.ac' namespace
namespace.create('src.ac');

/*********************************************************************************
 * src.ac.BrowserCheck
 *********************************************************************************
 * Description:
 * 	The BrowserCheck class is responsible for validating that the browser utilized
 *  is acceptable for running the AlteryxConnect client.
 *********************************************************************************
 *********************************************************************************/
src.ac.BrowserCheck = Class.create({

    browser: null,
    version: null,
    OS: null,
    
    initialize: function ( ) {
        this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
        this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "an unknown version";
        this.OS = this.searchString(this.dataOS) || "an unknown OS";
    },

    searchString: function ( data ) {
        for (var i=0;i<data.length;i++)	{
            var dataString = data[i].string;
            var dataProp = data[i].prop;
            this.versionSearchString = data[i].versionSearch || data[i].identity;
            if (dataString) {
                if (dataString.indexOf(data[i].subString) != -1)
                    return data[i].identity;
            } else if (dataProp) {
                return data[i].identity;
            }
        }
    },
    
    searchVersion: function ( dataString ) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1)
		    return;
		
        return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },

    isBrowserAcceptable: function ( ) {
        if ((this.browser == "Explorer" && this.version < 7))
            return false;
        else
            return true;
    },

    dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],

	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	],

    CLASS_NAME: 'src.ac.BrowserCheck'
});

/*********************************************************************************
 * src.ac.Loader
 *********************************************************************************
 * Description:
 * 	The Loader class is responsible for loading script content in a
 * 	synchronous fashion and provide a callback method.  This is the basis for
 * 	on-demand loading and a progress callback.
 *********************************************************************************
 *          Base Class: Class (prototype.js)
 * 
 *    Known Subclasses:	None.
 *
 *     Methods Defined: addScript
 * 			load
 * 			setStatusMessage
 * 			clearStatusMessage
 * 			showStatusWindow
 * 			hideStatusWindow
 * 
 *         Usage Notes: None.
 *
 *********************************************************************************/
src.ac.Loader = Class.create({

    /* Private reference to the scripts that have been loaded (or are loading). */
    _sources: [],

    /* Private reference to the head tag. */
    _head: null,

    /* Private reference to a boolean flag that indicates whether or not we're in the process of loading. */
    _loading: false,

    /* Private reference to the Loader splash screen. */
    _splash: null,

    initialize: function() {

	this._sources = [];
	this._head = document.getElementsByTagName( 'head' );
	if ((this._head) && (this._head.length > 0))
	    this._head = this._head[0];
	else
	    throw "Invalid HTML.  There is no <head/> tag present in the document.";

	this._initUI();
    },

    _disableSelection: function( e ) {

	if (!e)
	    return;

	var elt = e.element();
	if (elt.tagName.toUpperCase() !== 'INPUT') {
	    Event.stop( e );
	    return false;
	} else {
	    return true;
	}
    },

    _initUI: function() {

	var body = document.getElementsByTagName( 'body' );

	if ((!body) || (body.length != 1))
	    throw "Invalid HTML.  There is no <body/> tag present in the document.";

	body = $(body[0]);

	//body.observe( 'selectstart', this._disableSelection.bindAsEventListener( this ) );

	body.insert( {top: this._templates['splash'].evaluate( {id:'svuss0001'} )} );
	this._splash = body.select( 'div[id="svuss0001"]' );

	if ((this._splash) && (this._splash.length === 1)) {
	    this._splash = this._splash[0];
	} else {
	    this._splash = null;
	    return;
	}

	var dim = this._splash.getDimensions();
	var doc = document.viewport.getDimensions();

	this._splash.setStyle({
	    top:'' + Math.round((doc.height / 2) - (dim.height / 2 )) + 'px',
	    left:'' + Math.round((doc.width / 2) - (dim.width /2 )) + 'px'
	});

	this.showStatusWindow();
    },

    /*****************************************************************************
     * setStatusMessage( message )
     *****************************************************************************
     * Description:
     *		Sets the status message on the Loader splash screen.
     *****************************************************************************
     * Parameters:
     *        message: The message to display.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    setStatusMessage: function( message ) {

	var m = this._splash.select( 'div.status' )[0];
	m.update( message );
    },

    /*****************************************************************************
     * clearStatusMessage()
     *****************************************************************************
     * Description:
     *		Clears the status message on the Loader splash screen.
     *****************************************************************************
     * Parameters:
     *   None.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    clearStatusMessage: function() {

	this.setStatusMessage('');
    },

    /*****************************************************************************
     * hideStatusWindow()
     *****************************************************************************
     * Description:
     *		Hides the Loader splash screen.
     *****************************************************************************
     * Parameters:
     *   None.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    hideStatusWindow: function() {

	if (this._splash) {
	    this._splash.hide();
	}
    },

    /*****************************************************************************
     * showStatusWindow()
     *****************************************************************************
     * Description:
     *		Shows the Loader splash screen.
     *****************************************************************************
     * Parameters:
     *   None.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    showStatusWindow: function() {

	if (this._splash) {
	    this._splash.show();
	}
    },

    /*****************************************************************************
     * addScript( source, callback )
     *****************************************************************************
     * Description:
     *		Adds a script to be loaded into the DOM.  Provides a callback
     * 		method to provide a status on the load process.
     *****************************************************************************
     * Parameters:
     *         source: The source to be loaded.
     * 	     callback: The status callback method. Called once when the script starts loading and once when it's complete.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    addScript: function( source, callback ) {

	//The source can only be a string
	if ((!source) || (typeof(source) !== 'string'))
	    return;

	//If we don't get a callback then it's because the user doesn't want to know anything about the load process.
	callback = ((callback) && (typeof(callback) ==='function'))?callback:Prototype.emptyFunction;

	//We don't want to load a single script more than once...so we check before we add it to the _sources array.
	var dup = false;
	this._sources.each( function( s ) {
	   if (s.source.toLowerCase() === source.toLowerCase()) {
		dup = true;
		throw $break;
	   }
	});

	//If it's not a duplicate then we add it to the array.
	if (dup !== true)
	    this._sources.push( {source: source, callback: callback, loaded: false} );

	return;
    },

    /*****************************************************************************
     * load()
     *****************************************************************************
     * Description:
     *		Forces a load of all *unloaded* scripts in the _sources array.
     *****************************************************************************
     * Parameters:
     *   None.
     * 
     * Returns:
     *   None.
     *****************************************************************************/
    load: function() {

	//Iterate through the _sources array and load only the unloaded scripts.
	this._sources.each( function( s ) {
	    if (s.loaded !== true) {
		this._loadSingle( s );
		throw $break;
	    }
	}.bind( this ));
    },

    _loadSingle: function( s, iteration ) {

	iteration = iteration || 0;

	if (iteration >= 40) 
	    throw "The script '" + s.source + "' could not be loaded.";

	if (this._loading === true) {
	    iteration++;
	    this._loadSingle.bind( this, s, iteration).delay( 0.5 );
	    return;
	}

	this._insertScript( s );
    },

    _insertScript: function( o ) {

	this._loading = true;

	var s = document.createElement( 'script' );
	s.type = 'text/javascript';
	s.src = o.source;

	var fx = this._script_onLoadComplete.bind( this );

	s.onload = function() { fx( o ); };
	s.onreadystatechange = function() {
	    if ((this.readyState === 'loaded') || (this.readyState === 'complete')) {
		fx( o );
	    }
	};

	o.callback( o.source, false );

	this._head.appendChild( s );
    },

    _script_onLoadComplete: function( o ) {
        if (o.loaded === true)
            return;

	this._loading = false;
	o.loaded = true;

	o.callback( o.source, true );

	this._sources.each( function( s ) {
	    if (s.loaded !== true) {
		this._loadSingle( s );
		throw $break;
	    }
	}.bind( this ));
    },

    _templates: {
	splash: new Template(
	'<div id="#{id}" class="src_ac_Loader" style="display:none;">' + 
	'<div style="position:relative; width:100%; height:100%;">' + 
	'<div class="status" style="position:absolute; bottom:10px; left:4px;"></div>' + 
	'</div>' + 
	'</div>')
    },

    /* Static CLASS_NAME */
    CLASS_NAME: 'src.ac.Loader'
});

//Create a simple extendProto object so we can extend the Element object of Prototype - some value-add for SRC.
var extendProtoSRC = {
    getInnerDimensions: function(element) {
	if (!element)
	    return {width:0, height:0};

	var dim = element.getDimensions();
	if (!dim) 
	    return {width:0, height:0};

	var bt = bl = br = bb = 0;

	try {
	    bt = parseInt(e.getStyle('border-top-width'),0);
	    bl = parseInt(e.getStyle('border-left-width'),0);
	    br = parseInt(e.getStyle('border-right-width'),0);
	    bb = parseInt(e.getStyle('border-bottom-width'),0);
	    bt = isNaN(bt)?0:bt;
	    bl = isNaN(bl)?0:bl;
	    br = isNaN(br)?0:br;
	    bb = isNaN(bb)?0:bb;
	} catch (err) {}

	return {width:(dim.width - (bl + br)), height:(dim.height - (bt + bb))};
    },

    disableSelection: function(element) {
	if (!element)
	    return;

	element.onselectstart = function() { return false; };
	element.unselectable = 'on';
	element.style.MozUserSelect = 'none';
    }
};
Element.addMethods( extendProtoSRC );

Number.prototype.addCommas = function() {
    var s = '';
    var n = this.toString();
    var x = n.split('.');
    var x1 = x[0];
    var x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1))
		x1 = x1.replace(rgx, '$1' + ',' + '$2');
	return x1 + x2;
};

Number.prototype.roundTo = function( d ) {
	return Math.round( this * Math.pow( 10, d )) / Math.pow( 10, d );
};

Array.prototype.sortNum = function () {
    function sortNum ( a, b ) {
        return a-b;
    }
    
    return this.sort( sortNum );
};

String.prototype.parse = function( delimiter, ignoreQuotes ) {

    var s = this;
    delimiter = delimiter || ',';
    ignoreQuotes = (ignoreQuotes === true ? true : false);

	var inQuote = false;
    var e = [];
    var n = 0;
    while((n < s.length) && (s.length > 0)) {
        var c = s.substr(n, 1);
        if (c == delimiter) {
		    if ((ignoreQuotes) || ((!ignoreQuotes) && (!inQuote))) {
			    var v = s.substr(0, n).replace( /^\s*|\s*$/gi, '');
			    v = (ignoreQuotes ? v : v.replace( /^"|"$/gi, ''));
                e.push( v );
                s = s.substring(n + 1);
                n = 0;
                continue;
		    }
        }
            
        if (c == '"') {
            inQuote = !inQuote;
            n++;
            continue;
        }

		n++;
    }

	if (s.length > 0) {
	    s = s.replace( /^\s*|\s*$/gi, '');
	    s = (ignoreQuotes ? s : s.replace( /^"|"$/gi, ''));
        e.push( s );
	}
        
    return e;
};

src.ac.ArgParser = Class.create({

    initialize: function() {
    },

    getParameters: function( url ) {

	url = url || window.location.href;

	var s = '';
	if (url.indexOf('?') > 0) {
	    var paramStart = url.indexOf('?') > 0 ? url.indexOf('?') + 1 : 0;
	    var paramEnd = url.indexOf('#') > 0 ? url.index('#') : url.length;
	    s = url.substring( paramStart, paramEnd );
	}

	var o = {};
	params = s.split( /[&;]/ );
	params.each( function( p ) {
	    var kv = p.split( '=' );
	    if (kv[0]) {
		var key = decodeURIComponent( kv[0] );
		var value = kv[1] || '';

		var values = value.split(',');
		for (var j = 0; j < values.length; j++) 
		    values[j] = decodeURIComponent( values[j] );

		if (values.length == 1)
		    values = values[0];

		o[key] = values;
	    }
	});

	return o;
    },

    /* Static CLASS NAME */
    CLASS_NAME: 'src.ac.ArgParser'
});
