/**
 * @author Volodymyr Pavlyuk
 * 
 * Set of tools for downloading static resources
 */


/**
 * Extend Function with bind method which allows set <b>this</b> in functions used as event handlers.
 * 
 * @param {Object} context
 */
Function.prototype.bind = function(context) {
    var _f = this;
    var _args = Array.prototype.slice.call(arguments);
    _args.shift();
    return function() {
        var a = Array.prototype.slice.call(arguments);
        a = _args ? a.concat(_args): a;
        return _f.apply(context, a);
    }
}


/**
 * Define root namespace
 */
ROXCalc = {
    /**
     * Calls specified function with delay.
     * 
     * @param {Number} delay - delay in miliseconds
     * @param {Object} context - execution context of the callback function
     * @param {Function} callback - function to execute
     */
    later: function(delay, context, callback) {
        setTimeout(function(){
            callback.apply(context, []);
        }, delay);
    }
};

/**
 * Configuration constants
 */
ROXCalc.config = {
	static_url: "http://calc.returnonexecution.com:80/static.php",
	calc_url: "http://calc.returnonexecution.com:80/calculate.php",
    static_root: "http://calc.returnonexecution.com:80",
    wrapper_id: "",
    container_id: "ROXCalc_app_container",
    container_css_class: "",
    progress_id: "ROXCalc_loader_progress",
    revision: '1282',
    bootstrap: {
        css: [{name: "/static/css/rox-style.css", id: 'rox_style_combined'}],
        js: [{name: "/static/app/rox-calc.js", mode: 'direct'}]
    }
};

/**
 * Set of methods for communication with server via XMLHttpRequest or via dynamic script tag
 * 
 * @param {Object} ns
 */
ROXCalc.io = function(ns) {
    
    // JSONscriptRequest -- a simple class for making HTTP requests
    // using dynamically generated script tags and JSON
    //
    // Sample Usage:
    //
    // <script type="text/javascript" src="jsr_class.js"></script>
    // 
    // function callbackfunc(jsonData) {
    //      alert('Latitude = ' + jsonData.ResultSet.Result[0].Latitude + 
    //            '  Longitude = ' + jsonData.ResultSet.Result[0].Longitude);
    //      aObj.removeScriptTag();
    // }
    //
    // request = 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&
    //            output=json&callback=callbackfunc&location=78704';
    // aObj = new JSONscriptRequest(request);
    // aObj.buildScriptTag();
    // aObj.addScriptTag();
    //
    // Constructor -- pass a REST request URL to the constructor
    //
    function JSONscriptRequest(fullUrl, doCache) {
        
        // REST request path
        this.fullUrl = fullUrl; 
        // Keep browser from caching requests
        this.noCacheIE = '';
        if(!doCache) {
            this.noCacheIE = '&noCacheIE=' + (new Date()).getTime();
        }
        // Get the DOM location to put the script tag
        this.headLoc = document.getElementsByTagName("head").item(0);
        // Generate a unique script tag id
        this.scriptId = 'JscriptId' + JSONscriptRequest.scriptCounter++;
    }
    
    // Static script ID counter
    JSONscriptRequest.scriptCounter = 1;
    
    // buildScriptTag method
    //
    JSONscriptRequest.prototype.buildScriptTag = function () {
    
        // Create the script tag
        this.scriptObj = document.createElement("script");
        
        // Add script object attributes
        this.scriptObj.setAttribute("type", "text/javascript");
        this.scriptObj.setAttribute("charset", "utf-8");
        this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheIE);
        this.scriptObj.setAttribute("id", this.scriptId);
    }
     
    // removeScriptTag method
    // 
    JSONscriptRequest.prototype.removeScriptTag = function () {
        // Destroy the script tag
        this.headLoc.removeChild(this.scriptObj);  
    }
    
    // addScriptTag method
    //
    JSONscriptRequest.prototype.addScriptTag = function () {
        // Create the script tag
        this.headLoc.appendChild(this.scriptObj);
    }
    
    return {
        XDRequest: JSONscriptRequest,
        
        /**
         * Returns XMLHttpRequest or ActiveXObject for asynchronous communication with server.
         */
        getHTTPObject: function() {
            var req = false;
            // branch for native XMLHttpRequest object
            if(window.XMLHttpRequest) {
                try { 
                    req = new XMLHttpRequest(); 
                } catch(e) {
                    req = false;
                }
            // branch for IE/Windows ActiveX version
            } else if(window.ActiveXObject) {
                try { 
                    req = new ActiveXObject('Msxml2.XMLHTTP'); 
                }
                catch(e) {
                    try {
                        req = new ActiveXObject('Microsoft.XMLHTTP');
                    }
                    catch(e) {
                        req = false;
                    }
                }
            }
            return req;
        },
        
        //Namespace for JSONP callbacks
        CallbackNS: {},
        
        //Global name of namespace
        NSName: "ROXCalc.io.CallbackNS"
    }
} (ROXCalc);


ROXCalc.Event = function(ns) {
    
    /**
     * HTML element attribute name. This attribute stores reference to events cache.
     * 
     * @type {String}
     */
    var expando = "ROXCalc" + (new Date()).getTime(),
        uuid = 0;
    
    /**
     * Storage of all event listeners.
     * 
     * @type {Object}
     */
    var eventCache = {};
    
    /**
     * Check is the given object if HTML node/HTML node ID. 
     * If the parameter is string  it is treated as id of HTML element and true is returned.
     * If the parameter is object and has such properties as nodeName, nodeType and tagName then true is returned.
     * In other cases method returns false; 
     * 
     * @param {Object/String} obj
     */
    function isDomElement(obj) {
        return (typeof(obj) === 'string') || (obj.nodeName && obj.nodeType && obj.tagName || obj === window);
    }
    
    /**
     * Attach event listener to DOM event
     * 
     * @param {HTMLElement/String} el - HTML element or its id
     * @param {String} type - event type (without 'on' prefix)
     * @param {Function} fn - callback function
     * @param {Object} context - callback execution context
     * @param {Object} data - data that is passed to event handler
     */
    function attachDomListener(el, type, fn, context, data) {
        var old_fn = fn;        
        el = typeof(el) === 'string' ? document.getElementById(el) : el;
        fn = fn.bind(context, data);
        
        //Store references to event listeners
        var id = el[expando];
        //Compute unique id for each element
        if(!id) {
            id = el[expando] = ++uuid;
        }
        if(!eventCache[id]) {
            eventCache[id] = {};
        }
        if(!eventCache[id]['events']) {
            eventCache[id]['events'] = {};
        }
        if(!eventCache[id]['events'][type]) {
            eventCache[id]['events'][type] = [];
        }
        eventCache[id]['events'][type].push([fn, old_fn]);
        
        if (el.addEventListener) {
            el.addEventListener(type, fn, false);
            return true;
        } else if (el.attachEvent) {
            var r = el.attachEvent('on' + type, fn);
            return r;
        } else {
            el['on' + type] = fn;
            return true;
        }
        return false;
    }
    
    /**
     * Remove DOM event listener.
     * 
     * @param {HTMLElement/String} el - HTML element or its id
     * @param {String} type - event type (without 'on' prefix)
     * @param {Function} fn - callback function
     */
    function removeDomListener(el, type, fn){
        el = typeof(el) === 'string' ? document.getElementById(el) : el;
        var events,
            fn_to_remove = fn;
        
        try {
            events = eventCache[el[expando]]['events'][type];
        } catch (e) {
            //Do something
        }
        
        if(events && events.length) {
            for(var i = 0; i < events.length; i++) {
                if(fn !== undefined && events[i][1] === fn) {
                    fn_to_remove = [0];
                    break;
                } 
            }
        }
        
        if (el.removeEventListener) {
            el.removeEventListener(type, fn_to_remove, false);
            return true;
        } else if (el.detachEvent) {
            var r = el.detachEvent('on' + type, fn_to_remove);
            return r;
        } else if(el['on' + type] === fn_to_remove) {
            el['on' + type] = null;
            return true;
        }
        return false;
    }
    
    var Event = {
        //eCache: eventCache,
        domReady: {},
        
        /**
         * Subscribes one object (subscriber) to the event of another one (publisher).
         * If publisher fires event subscriber's method is invoked.
         * 
         * @param {Object} obj - publisher
         * @param {String} type - event type
         * @param {Function} fn - subscriber's method
         * @param {Object} context - method execution context
         */
        subscribe: function(obj, type, fn, context, data) {
            if(type === 'DOMReady') {
                /*
                 * Registers function +fn+ will be executed when the dom 
                 * tree is loaded without waiting for images. 
                 */
                
                // Already loaded?
                if (Event.domReady.loaded) return fn();
                
                // Observers
                var observers = Event.domReady.observers;
                if (!observers) observers = Event.domReady.observers = [];
                // Array#push is not supported by Mac IE 5
                observers[observers.length] = fn;
                
                // domReady function
                if (Event.domReady.callback) return;
                Event.domReady.callback = function() {
                  if (Event.domReady.loaded) return;
                  
                  Event.domReady.loaded = true;
                  if (Event.domReady.timer) {
                    clearInterval(Event.domReady.timer);
                    Event.domReady.timer = null;
                  }
                  
                  var observers = Event.domReady.observers;
                  for (var i = 0, length = observers.length; i < length; i++) {
                    var fn = observers[i];
                    observers[i] = null;
                    fn(); // make 'this' as window
                  }
                  Event.domReady.callback = Event.domReady.observers = null;
                };
                
                // Emulates 'onDOMContentLoaded'
                var ie = !!(window.attachEvent && !window.opera);
                var webkit = navigator.userAgent.indexOf('AppleWebKit/') > -1;
                
                if (document.readyState && webkit) {
                  
                  // Apple WebKit (Safari, OmniWeb, ...)
                  Event.domReady.timer = setInterval(function() {
                    var state = document.readyState;
                    if (state == 'loaded' || state == 'complete') {
                      Event.domReady.callback();
                    }
                  }, 50);
                  
                } else if (document.readyState && ie) {
                  
                  // Windows IE 
                  var src = (window.location.protocol == 'https:') ? '://0' : 'javascript:void(0)';
                  document.write(
                    '<script type="text/javascript" defer="defer" src="' + src + '" ' + 
                    'onreadystatechange="if (this.readyState == \'complete\') ROXCalc.Event.domReady.callback();"' + 
                    '><\/script>');
                  
                } else {
                  
                  if (window.addEventListener) {
                    // for Mozilla browsers, Opera 9
                    document.addEventListener("DOMContentLoaded", Event.domReady.callback, false);
                    // Fail safe 
                    window.addEventListener("load", Event.domReady.callback, false);
                  } else if (window.attachEvent) {
                    window.attachEvent('onload', Event.domReady.callback);
                  } else {
                    // Legacy browsers (e.g. Mac IE 5)
                    var fn = window.onload;
                    window.onload = function() {
                      Event.domReady.callback();
                      if (fn) fn();
                    }
                  }
                  
                }
            } else if(isDomElement(obj)) {
                attachDomListener(obj, type, fn, context, data)
            } else {
                obj._events = obj._events || {};
                obj._events[type] = obj._events[type] || [];
                obj._events[type].push({
                    action: fn,
                    context: context
                });
            }
        },
        
        /**
         * This method checks if publisher has subscribers of specified event
         * 
         * @param {Object} obj - publisher
         * @param {String} type - event type
         */
        hasListeners: function(obj, type) {
            return !!(obj._events && obj._events[type]);
        },
        
        /**
         * Unsubscribe from th event.
         * 
         * @param {Object} obj - publisher
         * @param {String} type - event type
         * @param {Function} fn - subcsriber's method
         * @param {Object} context - method execution context
         */
        unsubscribe: function(obj, type, fn, context) {
            if(!this.hasListeners(obj, type)) return;
            
            if(isDomElement(obj)) {
                removeDomListener(obj, type, fn);
            } else {
                for(var i = 0, lenght = obj._events[type].length; i < length; i ++) {
                    if((obj._events[type][i].action === fn) && (!context || (obj._events[type][i].context === context))) {
                        obj._events[type] = obj._events[type].splice(i, 1);
                        return;
                    }
                }
            }
        },
        
        /**
         * Fire event.
         * Additional arguments are passed to subscriber's method
         * 
         * @param {Object} obj - publisher
         * @param {String} type - event type
         */
        fire: function(obj, type) {
            if(!this.hasListeners(obj, type)) return;
            
            var args = Array.prototype.slice.call(arguments, 1);
            var listeners = [];
            var i;
            
            for (i = 0; i < obj._events[type].length; i++) {
                listeners[i] = obj._events[type][i];
            }
            
            for (i = 0; i < listeners.length; i++) {
                if(typeof(listeners[i].action) === 'function') {
                    listeners[i].action.apply(listeners[i].context || obj, args);
                }
            }
        }
    };
    
    //Make short alias
    Event.on = Event.subscribe;
    
    return Event;
} (ROXCalc);


ROXCalc.Dom = function(ns) {
    
    /**
     * Concatenates all elements of the given array.
     * If the argument isn't array than it's converted to string and returned. 
     * 
     * @param {Object} data
     */
    function aggregateResult(data) {
        var result = '';
        if (data instanceof Array) {
            for(var i = 0; i < data.length; i ++) {
                if(data[i]['content']) {
                    result += data[i]['content'];
                } else {
                    result += data[i];
                }
            }
        } else {
            result = data + '';
        }
        return result;
    }
    
    return {
        /**
         * Creates <script> tag. If params.type is set to 'url' script's src attribute is set to params.data.
         * Else script's inner text is filled with params.data.
         * 
         * @param {Object} Object params
         */
        createScriptNode: function(params) {
            var head = document.getElementsByTagName('head')[0];
            var js = document.createElement('script');
            var js_text = aggregateResult(params.data);
            
            if(params.type === 'url') {
                js.src = params.data;
            } else {
                js.text = js_text;
                if(!document.all) {
                    js.innerHTML = js_text;
                }
            }
            head.appendChild(js);
        },
        
        /**
         * Creates <style> element in the head section of HTML document
         * and fills it with the value of data property of params object. 
         * 
         * @param {Object} params
         */
        createStyleNode: function(params) {
            var head = document.getElementsByTagName('head')[0];
            var css = document.createElement('style');
            var css_text = aggregateResult(params.data);
            if(!!(window.attachEvent && !window.opera)) {
                setTimeout(function(){
                    css.styleSheet.cssText = css_text;
                }, 0);
            } else {
                var styleText = document.createTextNode(css_text);
                css.appendChild(styleText);
            }

            head.appendChild(css);
        },
        
        /**
         * Returns TRUE is element has the specified class. Else returns FALSE.
         * 
         * @param {HTMLElement} ele - element
         * @param {String} cls - class name 
         */
        hasClass: function(ele,cls) {
            return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
        },
        
        /**
         * Adds class to the element.
         * 
         * @param {HTMLElement} ele - element
         * @param {Object} cls - class name
         */
        addClass: function(ele,cls) {
            if (!ROXCalc.Dom.hasClass(ele,cls)) ele.className += " "+cls;
        },
        
        /**
         * Removes class from the element.
         * 
         * @param {HTMLElement} ele - element
         * @param {Object} cls - class name
         */
        removeClass: function(ele,cls) {
            if (ROXCalc.Dom.hasClass(ele,cls)) {
                var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
                ele.className=ele.className.replace(reg,' ');
            }
        },
        
        /**
         * Returns size of HTML document including scrolled areas.
         * 
         */
        pageSize: function() {
            var pageWidth = 0, pageHeight = 0;
            // Firefox
            if( window.innerHeight && window.scrollMaxY ) {
                pageWidth = window.innerWidth + window.scrollMaxX;
                pageHeight = window.innerHeight + window.scrollMaxY;
            }
            // all but Explorer Mac
            else if( document.body.scrollHeight > document.body.offsetHeight ) {
                pageWidth = document.body.scrollWidth;
                pageHeight = document.body.scrollHeight;
            }
            // works in Explorer 6 Strict, Mozilla (not FF) and Safari
            else { 
                pageWidth = document.body.offsetWidth + document.body.offsetLeft; 
                pageHeight = document.body.offsetHeight + document.body.offsetTop; }
           
            return {width: pageWidth, height: pageHeight};
        },
        
        /**
         * Returns size of visible part of the page.
         * 
         */
        viewportSize: function() {
            var pageWidth = 0, pageHeight = 0;
            if( typeof( window.innerWidth ) == 'number' ) {
            //Non-IE
                pageWidth = window.innerWidth;
                pageHeight = window.innerHeight;
            } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
            //IE 6+ in 'standards compliant mode'
                pageWidth = document.documentElement.clientWidth;
                pageHeight = document.documentElement.clientHeight;
            } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
            //IE 4 compatible
                pageWidth = document.body.clientWidth;
                pageHeight = document.body.clientHeight;
            }
            
            return {width: pageWidth, height: pageHeight};
        },
        
        /**
         * Returns scroll position of the window.
         * 
         */
        pageScrollPos: function() {
            var sTop = document.body.scrollTop;
            var sLeft = document.body.scrollLeft;
            
            if (sTop == 0) {
                if (window.pageYOffset) {
                    sTop = window.pageYOffset;
                }
                else {
                    sTop = (document.body.parentElement) ? document.body.parentElement.scrollTop : 0;
                }
            }
            
            if (sLeft == 0) {
                if (window.pageXOffset) {
                    sLeft = window.pageXOffset;
                }
                else {
                    sLeft = (document.body.parentElement) ? document.body.parentElement.scrollLeft : 0;
                }
            }
            
            return {left: sLeft, top: sTop};
        },
        
        /**
         * Returns absolute coordinates of the specified element.
         * 
         * @param {HTMLElement/String} obj
         */
        absolutePos: function(obj) {
            obj = typeof(obj) === 'string' ? document.getElementById(obj) : obj;
            
            var curleft = 0;
            var curtop = 0;
            if (obj && obj.offsetParent) {
                while (obj && obj.offsetParent){
                    curleft += obj.offsetLeft - obj.scrollLeft;
                    curtop += obj.offsetTop - obj.scrollTop;
                    var position='';
                    if (obj.style && obj.style.position)
                        position = obj.style.position.toLowerCase();
                    if (!position)
                        if (obj.currentStyle && obj.currentStyle.position)
                            position = obj.currentStyle.position.toLowerCase();
                    if (position == 'absolute') break;
                    while (obj.parentNode && obj.offsetParent && obj.parentNode != obj.offsetParent) {
                        obj=obj.parentNode;
                        curleft -= obj.scrollLeft;
                        curtop -= obj.scrollTop;
                    }
                    obj = obj.offsetParent;
                 }
             }
             else {
                 if (obj.x)
                     curleft += obj.x;
                 if (obj.y)
                     curtop += obj.y;
             }
             return {left:curleft,top:curtop};
        },
        
        /**
         * Returns the position of cursor in text field.
         * 
         * @param {HTMLElement} oField - text input field
         */
        getCaretPos: function (oField) {
            // Initialize
            var iCaretPos = 0;
            
            // IE Support
            if (document.selection) {
                // Set focus on the element
                oField.focus ();
                
                // To get cursor position, get empty selection range
                var oSel = document.selection.createRange ();
                
                // Move selection start to 0 position
                oSel.moveStart ('character', -oField.value.length);
                
                // The caret position is selection length
                iCaretPos = oSel.text.length;
            }// Firefox support
            else if (oField.selectionStart || oField.selectionStart == '0') {
                
                iCaretPos = oField.selectionStart;
            }
            
            // Return results
            return (iCaretPos);
        },
        
        /**
         * If the first element is ancestor of the second one than TRUE is returned.
         * 
         * @param {HTMLElement} ancestor
         * @param {HTMLElement} child
         */
        isChildOf: function(ancestor, child) {
            if(ancestor === child) return false;
            while(child && child !== ancestor && child.nodeName != "BODY") {
                child = child.parentNode;
            }
            return child === ancestor;
        }
    }
} (ROXCalc);

/**
 * Recource loader
 * 
 * @param {Object} ns
 */
ROXCalc.Loader = function(ns) {
    
    /**
     * 
     * @param {Object} container
     * @param {String} baseName
     */
    function getUniqueName(container, baseName) {
        var inst_number = 0;
        var result;
        
        do {
            result = baseName + (inst_number ? '_' + inst_number : '');
            inst_number ++;
        } while(container[result])
        
        return result;
    }
    
    return {
        /**
         * This method downloads resources via dynamic script tag and passes it to callback function.
         * 
         * @param {Object/Array} Object resource
         * @param {Object} String
         * @param {Function} Function callback
         */
        getResource: function(resource, type, callback) {
            var cb_method;
            var cb_name; 
            var url;
            var res_name;
            var rev = ROXCalc.config.revision ? '&rev=' + ROXCalc.config.revision : '';
            
            if(resource instanceof Array) {
                var names = [];
                var cb_parts = []
                for(var i = 0; i < resource.length; i ++) {
                    names.push(resource[i].id + ":" + resource[i].name);
                    cb_parts.push(resource[i].id);
                }
                res_name = names.join('|');
                cb_method = cb_parts.join('_');
            } else {
                res_name = resource.name;
                cb_method = 'm' + (resource.id ? resource.id : (new Date()).getTime());
            }
            
            cb_method = getUniqueName(ROXCalc.io.CallbackNS, cb_method);
            
            cb_name = ROXCalc.io.NSName + '.' + cb_method;
            url = resource.url || ROXCalc.config.static_url + '?resource=' + res_name + '&type=' + type + '&callback=' + cb_name + rev;
            
            /*
             * We can directly attach script to the page ar do it via dynamic script tag.
             * If callback call isn't needed the first way is better, in other cases don't specify resource.mode.
             * 
             * On production server noCache parameter should be removed
             */
            if(resource.mode === 'direct' && type === 'js') {
                var cacheParam = '#noCache=' + (rev ? ROXCalc.config.revision : (new Date()).getTime()); 
                ROXCalc.Dom.createScriptNode({type: 'url', data: resource.url || ROXCalc.config.static_root + resource.name + cacheParam});
            } else {
                var req = new ROXCalc.io.XDRequest(url, rev);
                ROXCalc.io.CallbackNS[cb_method] = function(data) {
                    if(!data.error) {
                        switch(type) {
                            case 'js':
                                ROXCalc.Dom.createScriptNode({type: 'text', data: data.result});
                                break;
                            case 'css':
                                ROXCalc.Dom.createStyleNode({type: 'text', data: data.result});
                                break;
                        }
                    } else {
                        //TODO: implement error processing
                    }
                    
                    req.removeScriptTag();
                    req = null;
                    
                    if(typeof(callback) === 'function') {
                        callback(data, type);
                    }
                    
                    /*setTimeout(function() {
                        delete ROXCalc.io.CallbackNS[cb_method];
                    }, 0);*/
                }
                
                req.buildScriptTag();
                req.addScriptTag();
            }
        },
        
        /**
         * Call remote method
         * 
         * @param {String} method
         * @param {Object} params - key-value pairs
         * @param {Function} callback
         */
        callRemoteMethod: function(method, params, callback) {
            var cb_method = 'm_' + getUniqueName(ROXCalc.io.CallbackNS, method);
            var cb_name = ROXCalc.io.NSName + '.' + cb_method; 
            var url;
            var params_to_pass = '';
            var rev = ROXCalc.config.revision ? '&rev=' + ROXCalc.config.revision : '';
            
            //Convert array/object of arguments to string
            if(typeof(params) === 'object') {
                if(params instanceof Array) {
                    params_to_pass = params.join('|');
                } else {
                    var temp = [];
                    for(var k in params) {
                        temp.push(k + ':' + params[k]);
                    }
                    params_to_pass = temp.join('|');
                }
            } else {
                params_to_pass = params;
            }
            
            url = ROXCalc.config.calc_url + '?do=' + method + '&callback=' + cb_name + '&params=' + params_to_pass + rev;
            
            var req = new ROXCalc.io.XDRequest(url, rev);
            ROXCalc.io.CallbackNS[cb_method] = function(data){
                req.removeScriptTag();
                req = null;
                
                if(typeof(callback) === 'function') {
                    callback(data);
                }
                /*setTimeout(function() {
                    delete ROXCalc.io.CallbackNS[cb_method];
                }, 0);*/
            }
            req.buildScriptTag();
            req.addScriptTag();
        }
    };
}(ROXCalc);




//Start initializing of the widget on DOMReady event
ROXCalc.Event.subscribe(window, 'DOMReady', function(){
    
    //CSS for progress indicator
    var loader_css = "#ROXCalc_loader_progress { border: 1px solid #ccc;" +  
            "background: white url(###STATIC_ROOT###/static/images/loadinfo.gif) center center no-repeat;" + 
            "padding: 8px; height: 150px; width: 800px;" +
            "font-family:Trebuchet MS; font-size: 14px; color: #777;" + 
            "position: absolute; top: 0px; left: 0px;" + 
            "}" + 
            "#" + ROXCalc.config.container_id + " { position: relative }";
            
    loader_css = loader_css.replace('###STATIC_ROOT###', ROXCalc.config.static_root);
    ROXCalc.Dom.createStyleNode({data: loader_css});
    
    //Create container div and put it into wrapping element
    var container = document.createElement('div');
    var wrapper = document.getElementById(ROXCalc.config.wrapper_id);
    container.id = ROXCalc.config.container_id;
    if(wrapper) {
        wrapper.appendChild(container);
    } else {
        throw('Wrapper element ID is not specified');
    }
    
    //Add progress indicator to the page
    var progress_html = '<div id="' + ROXCalc.config.progress_id + '">Loading resources...</div>';
    container.innerHTML = progress_html;
    
    //Check SVN revision
    if(!parseInt(ROXCalc.config.revision)) {
        ROXCalc.config.revision = 0;
    } 
    
    //Load all required resources
    setTimeout(function(){
        for(var t in ROXCalc.config.bootstrap) {
            for(var i = 0; i < ROXCalc.config.bootstrap[t].length; i++) {
                ROXCalc.Loader.getResource(ROXCalc.config.bootstrap[t][i], t, null)
            }
        }
    }, 100);
    
});

