/**
 * Copyright (c) 2011, MagicWeb.org LLC
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *     - Redistributions of source code must retain the above copyright notice, this
 *       list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

/**
 * MagicWeb.org JavaScript library
 * 
 * @author vativa
 */

var Mw = {

    /**
     * Alias for Mw.Core.domReady()
     */
    ready: function(f) {
        this.Core.domReady(f);
    },

    Ajax: {

        // --------------------------------------------------------------------
        // 
        // Ajax form validation - single elements and complete form
        // 
        // --------------------------------------------------------------------
    
        $form: null,
        formClass: null,
        $formElements: null,
        
        initValidation: function(form, formClass) {
            var $form = $(form);
            Mw.Ajax.$formElements = $(form).find('input[type=text],input[type=password],select,textarea');
            Mw.Ajax.$form = $form;
            Mw.Ajax.formClass = formClass;
            
            Mw.Ajax.$formElements.blur(function() {
                if (this.id) {
                    Mw.Ajax.doValidate(this.id);
                }
                return false;
            })
        },

        validateAll: function(callback) {
            $.ajax({
                url: '/ajax/validateallformelements',
                data: Mw.Ajax.getData(),
                type: 'post',
                success: function(resp) {
                    if (Mw.Util.objectLength(resp)) {
                        Mw.Ajax.appendErrorMessages(resp);
                    } else {
                        callback();
                    }
                }
            });
        },

        getData: function() {
            var data = Mw.Ajax.$form.serializeArray();
            data.push({name : 'formClass', value : Mw.Ajax.formClass});
            return data;
        },
        
        doValidate: function(elementId) {
            $.ajax({
                url: '/ajax/validatesingleformelement',
                data: Mw.Ajax.getData(),
                type: 'post',
                dataType: 'json',
                success: function(resp) {
                    Mw.Ajax.appendErrorMessages(resp, elementId);
                }
            });

        },
        
        appendErrorMessages: function(response, elementId) {
            Mw.Ajax.$formElements.each(function(ind, element) {
                // Filter out different elements then the given one on single validation
                if (elementId && this.id != elementId) return true;//continue
                // Remove and append message if the selected element has id-attribute
                if (this.id) {
                    var $anchor = $('#'+this.id).closest('.form-element-skin-wrapper');
                    if (!$anchor.length) {
                        $anchor = $('#'+this.id);
                    }
                    $anchor.nextAll('ul.errors').remove();
                    if (response[this.id]) {
                        $anchor.after(Mw.Ajax.getErrHtml(response[this.id]));
                    }
                }
            });
        },
        
        getErrHtml: function(errArray){
            var html = '<ul class="errors">';
            $.each(errArray, function(key,value) {
                html += '<li>' + value + '</li>';
            });
            html += '</ul>';
            return html;
        },
        
        // --------------------------------------------------------------------
        // 
        // Loading content through AJAX
        // 
        // --------------------------------------------------------------------
    
        load: function(url, selector, data) {
            $.ajax({
                url: url,
                data: data,
                type: 'post',
                success: function(resp) {
                    $(selector).html(resp);
                }
            });
            return false;
        }
    
    },
    
    // ------------------------------------------------------------------------
    // 
    // YouTube functionality
    // 
    // ------------------------------------------------------------------------

    YouTube: {
        
        play: function(elem, playerSize) {
            $.popup({
                url: '/video/play?url=' + elem.href + '&playerSize=' + playerSize
            });
            return false;
        },
        
        /**
         * Mark current youtube video entry
         */
        setActive: function(elem) {
            var $entry = $(elem);
            $entry.siblings().removeClass('active');
            $entry.addClass('active');
        }
        
    },

    Core: {

        /**
         * Checks to see if the DOM is ready for navigation
         */
        domReady: function(f) {
            // If we already figured out that the page is ready, execute the function right away
            if (this.domReady.done) f();
            // Queue functions to be executed later
            if (this.domReady.queue && this.domReady.queue.constructor === Array) {
                this.domReady.queue.push(f);
            } else {
                this.domReady.queue = [f];
                this.domReady.timer = setInterval(this.checkDom, 10);
            }
        },

        /**
         * Provides scheduled checks for necessary DOM elements and functions
         * Context is the window objec
         */
        checkDom: function() {
            // Check one DOM element and one DOM function
            if (document.body && document.getElementById) {
                // If they're ready, we can stop checking
                clearInterval(Mw.Core.domReady.timer);
                // Execute all the functions we have on the stack
                while (Mw.Core.domReady.queue.length) {
                    Mw.Core.domReady.queue.shift()();
                }
                // Remember that we're now done
                Mw.Core.domReady.done = true;
            }
        },
        
        /**
         * Detect browser
         */
        detectBrowser: function() {
            var useragent = navigator.userAgent;
            var mapdiv = document.getElementById("map_canvas");
    
            if (useragent.indexOf('iPhone') != -1 || useragent.indexOf('Android') != -1 ) {
                mapdiv.style.width = '100%';
                mapdiv.style.height = '100%';
            } else {
                mapdiv.style.width = '600px';
                mapdiv.style.height = '800px';
            }
        }

    },

    Traversing: {

        /**
         * Search for elements with particular class
         */
        getElementsByClass: function(name, type) {
            var arr = [];
            // Locate the class name (allows for multiple class names)
            var re = new RegExp('(^|\\s)' + name + '(\\s|$)');
            // Limit search by type, or look through all elements
            var elements = document.getElementsByTagName(type || '*');
            for ( var i in elements) {
                // If the element has the class, add it for return
                if (re.test(elements[i].className))
                    arr.push(elements[i]);
            }
            // Return the list of matched elements
            return arr;
        },
        
        /**
         * Find the previous sibling in relation to an element
         */
        prev: function(elem) {
            do {
                elem = elem.previousSibling;
            } while (elem && elem.nodeType != 1);
            return elem;
        },

        /**
         * Find the next sibling in relation to an element
         */
        next: function(elem) {
            do {
                elem = elem.nextSibling;
            } while (elem && elem.nodeType != 1);
            return elem;
        },
        
        first: function( elem ) { 
            elem = elem.firstChild; 
            return elem && elem.nodeType != 1 ? 
            next ( elem ) : elem; 
        },

        last: function( elem ) { 
            elem = elem.lastChild; 
            return elem && elem.nodeType != 1 ? 
            prev ( elem ) : elem; 
        },

        parent: function( elem, num ) { 
            num = num || 1; 
            for ( var i = 0; i < num; i++ ) 
                if ( elem != null ) elem = elem.parentNode; 
            return elem; 
        },
        
        tag: function(name, elem) { 
            // If the context element is not provided, search the whole document 
            return (elem || document).getElementsByTagName(name); 
        }

    },

    Manipulation: {

        /**
         * Create element respecting namespaces
         */
        create: function(elem) {
            return document.createElementNS ?
            document.createElementNS('http://www.w3.org/1999/xhtml', elem) :
            document.createElement(elem);
        },
        
        insertBefore: function(elem, anchor) {
            anchor.parentNode.insertBefore(this.checkElem(elem), anchor);
        },
        
        insertAfter: function(elem, anchor) {
            var next = Mw.Traversing.next(anchor);
            this.insertBefore(this.checkElem(elem), next);
        },
        
        /**
         * Insert an element before another element
         */
        before: function( parent, before, elem ) { 
            // Check to see ifno parent node was provided 
            if ( elem == null ) { 
                elem = before; 
                before = parent; 
                parent  = before.parentNode; 
            }
            parent.insertBefore(this.checkElem(elem), before); 
        },

        /**
         * Appending an element as a child of another element
         */
        append: function( parent, elem ) { 
            parent.appendChild(this.checkElem(elem)); 
        },
        
        /**
         * Load Javascript script on demand
         */
        loadScript: function(src) {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.src = src;
            document.body.appendChild(script);
        },
        
        /**
         * A helper function for the before() and append() functions
         */
        checkElem: function( elem ) { 
            // If only a string was provided, convert it into a Text Node 
            return elem && elem.constructor == String ? 
            document.createTextNode( elem ) : elem; 
        },
        
        /**
         * Function for Removing a Node from the DOM
         */
        remove: function remove(elem) { 
            if (elem) elem.parentNode.removeChild(elem); 
        },
        
        /**
         * A Function for Removing All Child Nodes from an Element
         */
        empty: function( elem ) { 
            while ( elem.firstChild ) 
                remove( elem.firstChild ); 
        },
        
        /**
         * Getting and setting values of element attributes
         */
        attr: function(elem, name, value) {
            // Check element
            if (typeof elem === 'undefined')
                return false;
            // Check name
            if (!name || name.constructor != String)
                return false;
            // Figure out if the name is one of the weird naming cases
            var newName = {
                'for': 'htmlFor',
                'class': 'className'
            }
            [name] || name;
            // Set value if provided
            if (typeof value !== 'undefined') {
                if (elem.setAttribute) {
                    elem.setAttribute(name, value);
                } else {
                    elem[newName] = value;
                }
            }
            // Retrieve attribute as string (cross-browser)
            return new String(elem.getAttribute(name) || elem[newName] || false);
        },
        
        /**
         * A function for embedding CSS styles
         * 
         * @param DOM elem
         * @param Object css
         */
        style: function(elem, css) {
            // Hold all already existing styles
            var ocss = new Object();
            var styles = this.attr(elem, 'style');
            if (styles) {
                // If we have styles already, then convert them to to an object
                styles = styles.split(';');
                for (var i = 0; i < styles.length; i++) {
                    var pair = styles[i].split(':');
                    // We define an alias for the trim function
                    var trim = Mw.Util.trim;
                    var key  = trim(pair[0]);
                    var val  = trim(pair[1]);
                    ocss[key] = val;
                }
            }
            // Merge css and ocss
            var ncss = Mw.Util.merge(ocss, css);// MIL
            
            // Join ncss and assign as style attribute
            var style = this.serializeCss(ncss);
            this.attr(elem, 'style', style);
        },
        
        /**
         * A helper function converting a css object into css style string representation
         */
        serializeCss: function(css) {
            var style = '';
            for (var key in css) {
                style += key + ':' + css[key] + ';';
            }
            return style;
        },
        
        /**
         * Retrieve the text of an elem (cross-browser)
         */
        text: function(elem, value) {
            if (value) {
                // Set value
                if (undefined === elem.innerText) {
                    elem.textContent = value;
                } else {
                    elem.innerText = value;
                }
            }
            return elem.innerText || elem.textContent;
        },
        
        /**
         * Retrieve the source of an element
         */
        html: function(elem) {
            return elem.innerHTML;
        },
        
        hasClass: function(name, tag) {
            var re = new RegExp("(^|\\s)" + name + "(\\s|$)");
            // Limit search by tag, or look through all elements 
            var e = document.getElementsByTagName(tag || "*");
            for (var j = 0; j < e.length; j++) {
                if (re.test(e[j])) return true;
            }
            return false;
        },
        
        /**
         * A function for hiding (using display) an element
         */
        hide: function(elem) {
            // Find out what its current display state is
            var curDisplay = getStyle(elem, 'display');
            // Remember its display state for later
            if (curDisplay != 'none') {
                elem.$oldDisplay = curDisplay;
            }
            // Set the display to none (hiding the element)
            elem.style.display = 'none';
        },

        /**
         * A function for showing (using display) an element
         */
        show: function(elem) {
            // Set the display property back to what it use to be, or use 'block', if no previous display had been saved
            elem.style.display = elem.$oldDisplay || '';
        },
        
        /**
         * A function for adjusting the opacity level of an element
         * Set an opacity level for an element (where level is a number 0-100)
         */
        setOpacity: function(elem, level) {
            // If filters exist, then this is IE, so set the Alpha filter
            if (elem.filters) {
                elem.style.filters='alpha(opacity=' + level + ')';
            } else {// Otherwise use the W3C opacity property
                elem.style.opacity = level/100;
            }
        }
        
    },

    Effect: {
      
        /**
         * A function for slowly revealing a hidden element by increasing its opacity over a matter of one second
         */
        fadeIn: function(elem) {
            // Start the opacity at 0
            Mw.Manipulation.setOpacity(elem, 0);
            //Show the element (but you can not see it, since the opacity is 0)
            Mw.Manipulation.show(elem);
            // We're going to do a 20 'frame' animation that takes place over one second
            for (var i = 0; i <= 100; i += 5) {
                // A closure to make sure that we have the right 'i'
                (function(){
                    var pos = i;
                    // Set the timeout to occur at the specified time in the future
                    setTimeout(function() {
                        // Set the new opacity of the element
                        setOpacity(elem,pos);
                    }, (pos + 1) * 10);
                })();
            }
        },
        
        /**
         *
         */
        slideDown: function(elem) {
            // Start the slide down at 0
            elem.style.height = '0px';
            // Show the element (but you can not see it, since the height is 0)
            Mw.Manipulation.show(elem);
            // Find the full potential height of the element
            var h = fullHeight(elem);
            // We're going to do a 20 'frame' animation that takes place over one second
            for (var i = 0; i <= 100; i += 5) {
                // A closure to make sure that we have the right 'i'
                (function(){
                    var pos = i;
                    // Set the timeout to occur at the specified time in the future
                    setTimeout(function() {
                        // Set the new height of the element
                        elem.style.height = ((pos / 100) * h) + 'px';
                    }, (pos + 1) * 10);
                })();
            }
        }

    },

    Event: {

        stopBubbling: function(e) {
            // If an event object is provided, then this is a non-IE browser
            if (e && e.stopPropagation) {
                e.stopPropagation();
            } else {
                // Otherwise, we need to use the IE way
                window.event.cancelBubble = true;
            }
        },
        preventDefault: function(e) {
            // Prevent the default browser action (W3C)
            if (e && e.preventDefault) {
                e.preventDefault();
            } else {
                // IE way to prevent the default action
                window.event.returnValue = false;
            }
            return false;
        },

        /**
         * addEvent/removeEvent written by Dean Edwards
         * http://dean.edwards.name/weblog/2005/10/add-event/
         */
        add: function(elem, type, handler) {
            // Assign each event handler a unique ID
            if (!handler.uid) {
                handler.uid = this.uid++;
            }
            // Create a hash table of event types for the element
            if (!elem.events) {
                elem.events = {};
            }
            // Create a hash table of eventhandlers
            if (!elem.events[type]) {
                elem.events[type] = {};
                // Store the existing event handler (if there is one)
                if (elem['on' + type]) {
                    elem.events[type][0] = elem['on' + type];
                }
            }
            // Store the eventhandler in the hash table
            elem.events[type][handler.uid] = handler;
            // Assign a global eventhandler to do all the work
            elem['on' + type] = this.handle;
        },

        // A counter used to create unique IDs
        uid: 1,

        remove: function(element, type, handler) {
            // Delete the eventhandler from the hash table
            if (element.events && element.events[type]) {
                delete element.events[type][handler.$$guid];
            }
        },

        handle: function(event) {
            var returnValue = true;
            // Grab the event object (IE uses a global event object)
            event = event || this.fix(window.event);
            // Get a reference to the hash table of event handlers
            var handlers = this.events[event.type];
            // Execute each event handler
            for (var key in handlers) {
                if (!handlers[key](event)) {
                    returnValue = false;
                }
            }
            return returnValue;
        },

        // Add some "missing" methods to the IE's event object
        fix: function(event) {
            // Add W3C standard event methods
            event.preventDefault = this.w3c.preventDefault;
            event.stopPropagation = this.w3c.stopPropagation;
            return event;
        },

        w3c: {
            preventDefault: function() {
                this.returnValue = false;
            },

            stopPropagation: function() {
                this.cancelBubble = true;
            }
        }

    },

    Offset: {

        /**
         * Find the X (Horizontal, Left) position of an element
         */
        pageX: function(elem) {
            return elem.offsetParent ? elem.offsetLeft + this.pageX(elem.offsetParent) : elem.offsetLeft;
        },

        /**
         * Find the Y (Vertical, Top) position of an element
         */
        pageY: function(elem) {
            // Seeifwe'reattherootelement,ornot
            return elem.offsetParent ?
            // Ifwecanstillgoup,addthecurrent offset and recurse upwards
            elem.offsetTop + pageY(elem.offsetParent) :
            // Otherwise,justgetthecurrentoffset
            elem.offsetTop;
        },

        /**
         * Find the horizontal position of the cursor
         */
        getX: function(e) {
            // Normalize the event object
            e = e || window.event;
            // Check for the non-IE position, then the IE position
            return e.pageX || e.clientX + document.body.scrollLeft;
        },

        /**
         * Find the vertical position of the cursor
         */
        getY: function(e) {
            // Normalize the event object
            e = e || window.event;
            // Check for the non-IE position, then the IE position
            return e.pageY || e.clientY + document.body.scrollTop;
        },
        
        /**
         * Get the X position of the mouse relative to the element target
         * used in event object 'e'
         */
        getElementX: function(e) {
            // Find the appropriate element offset
            return (e && e.layerX) || window.event.offsetX;
        },

        /**
         * Get the Y position of the mouse relative to the element target
         * used in event object 'e'
         */
        getElementY: function(e) {
            // Find the appropriate element offset
            return (e && e.layerY) || window.event.offsetY;
        }
        
    },
    
    Viewport: {
        
        /**
         * Returns the height of the web page
         * (could change if new content is added to the page)
         */
        pageHeight: function() {
            return document.body.scrollHeight;
        },

        /**
         * Returns the width of the web page
         */
        pageWidth: function() {
            return document.body.scrollWidth;
        },
        
        /**
         * A function for determining how far horizontally the browser is scrolled
         */ 
        scrollX: function() {
            // A shortcut, in case we're using Internet Explorer 6 in StrictMode
            var de = document.documentElement;
            // If the pageXOffset of the browser is available, use that
            return self.pageXOffset ||
            // Otherwise, try to get the scroll left off of the root node
            (de && de.scrollLeft) ||
            // Finally, try to get the scroll left off of the body element
            document.body.scrollLeft;
        },

        /**
         * A function for determining how far vertically the browser is scrolled
         */
        scrollY: function() {
            // A shortcut, in case we're using Internet Explorer 6 in StrictMode
            var de = document.documentElement;
            // If the pageYOffset of the browser is available, use that
            return self.pageYOffset ||
            // Otherwise, try to get the scroll top off of the root node
            (de && de.scrollTop) ||
            // Finally, try to get the scroll top off of the body element
            document.body.scrollTop;
        },
        
        /**
         * Find the height of the viewport
         */
        getHeight: function() {
            // A shortcut, in case we're using IE6 in StrictMode
            var de = document.documentElement;
            // If the innerHeight of the browser is available, use that
            return self.innerHeight ||
            // Otherwise, try to get the height off of the root node
            (de && de.clientHeight) ||
            // Finally, try to get the height off of the body element
            document.body.clientHeight;
        },

        /**
         * Find the width of the viewport
         */
        getWidth: function() {
            // A shortcut, in case we're using IE6 in StrictMode
            var de = document.documentElement; 
            // If the innerWidth of the browser is available, use that
            return self.innerWidth ||
            // Otherwise, try to get the width off of the root node
            (de && de.clientWidth) ||
            // Finally, try to get the width off of the body element
            document.body.clientWidth;
        }
        
    },
    
    Form: {
        
        /**
         * Find all input elements that have a specified name
         * (good for dealing with checkboxes or radio buttons)
         */
        getInputsByName: function(name) {
            // The array of input elements that will be matched
            var results = [];
            // Keep track of how many of them were checked
            results.numChecked = 0;
            // Find all the input elements in the document
            var inputs = document.getElementsByTagName('input');
            for (var i in inputs) {
                // Find all the fields that have the specified name
                if (name === inputs[i].name) {
                    // Save the result, to be returned later
                    results.push(inputs[i]);
                    // Remember how many of the fields were checked
                    if (inputs[i].checked) {
                        results.numChecked++;
                    }
                }
            }
            
            // Return the set of matched fields
            return results;
        },
        
        /**
         * A function for validating all fields within a form.
         * The 'form' argument should be a reference to a form element
         * The 'load' argument should be a boolean referring to the fact that
         * the validation function is being run on page load, versus dynamically
         */
        validate: function(form) {
            var valid = true;
            // Go through all the field elements in the form 
            // form.elements is an array of all fields in a form
            for (var i = 0; i < form.elements.length; i++) {
                // Hide any error messages, if they're being shown
                this.hideErrors(form.elements[i]);
                // Check to see if the field contains valid contents, or not
                if (!this.validateField(form.elements[i])) {
                    valid = false;
                }
            }
            // Return false if a field does not have valid contents, or true for all valid
            return valid;
        },
        
        /**
         * Validate a single field's contents
         */
        validateField: function(elem) {
            var errors = [];
            // Go through all the possible validation techniques
            var errMsgObj = this.errMsg;
            for (var name in errMsgObj) {
                // See if the field has the classs pecified by the error type
                var re = new RegExp('(^|\\s)' + name + '(\\s|$)');
                // Add error message to the list if the second test failes
                if (re.test(elem.className) && !errMsgObj[name].test(elem)) {
                    errors.push(errMsgObj[name].msg);
                }
            }
            // Show/hide error messages
            if (errors.length) {
                this.showErrors(elem, errors);
            } else {
                this.hideErrors(elem);
            }
            // Return false if the field fails any of the validation routines
            return !errors.length;
        },
        
        /**
         * Hide any validation error messages that are currently shown
         */
        hideErrors: function(elem) {
            // Find the next element after the current field
            var next = elem.nextSibling;
            // If the next element is an ul and has a class of errors
            if(next && next.nodeName == 'UL' && next.className == 'errors') {
                // Remove it (which is our means of 'hiding')
                elem.parentNode.removeChild(next);
            }
        },

        /**
         * Show a set of errors messages for a specific field within a form
         */
        showErrors: function(elem, errors) {
            // Find the next element after the field
            var next = elem.nextSibling;
            // If the field isn't one of our special error-holders
            if (next && next.nodeName == 'UL' && next.className == 'errors') {
                this.hideErrors(elem);
            }
            // We need to make one instead
            next = document.createElement('ul');
            next.className = 'errors';
            next.style.width = elem.style.width;
            var a = Mw.Offset.pageX(elem);
            var b = Mw.Offset.pageX(elem.parentNode);
            next.style.marginLeft = (a - b) + 'px';
            // And then insert into the correct place in the DOM
            elem.parentNode.insertBefore(next, elem.nextSibling);
            // Now that we have a reference to the error holder UL
            // We then loop through all the error messages
            for (var i = 0; i < errors.length; i++) {
                // Create a new li wrapper for each
                var li = document.createElement('li');
                li.innerHTML = errors[i];
                // And insert it into the DOM
                next.appendChild(li);
            }
        },
        
        /**
         * A standard set of rules and descriptive error messages for building a basic validation engine
         */
        errMsg: {
            
            /**
             * Check for when a specified field is required
             */
            required: {
                msg: 'This field is required.',
                test: function(elem) {
                    // Make sure that no text was entered in the field and that
                    // we aren't checking on page load (showing 'field required' messages
                    // would be annoying on page load)
                    return elem.value && elem.value !== elem.defaultValue;
                }
            },
            
            /**
             * Make sure that the field is a valid email address
             */
            email: {
                msg: 'Not a valid email address.',
                test: function(elem) {
                    return elem.value && /^[a-z0-9_+.-]+\@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/i.test(elem.value);
                }
            },
            
            /**
             * Make sure that the field is avalid URL
             */
            url: {
                msg: 'Not a valid URL',
                test: function(elem) {
                    return elem.value && /^(https?:\/\/)?(w{3})?([a-z0-9-]+\.)+[a-z0-9]{2,4}.*$/i.test(elem.value);
                }
            }
        }
    
    },
    
    Util: {
        
        /**
         * Get object length
         */
        objectLength: function(obj) {
            var len = 0;
            for (var i in obj) {
                len++;
            }
            return len;
        },
        
        /**
         * Test array for a value
         */
        in_array: function(needle, haystack) {
            var inArray = false;
            for (var i in haystack) {
                if (needle === haystack[i]) {
                    inArray = true;
                    break;
                }
            }
            return inArray;
        },
        
        /**
         * Trim
         */
        trim: function(value, chars) {
            return Mw.Util.ltrim(Mw.Util.rtrim(value, chars), chars);
        },

        ltrim: function(value, chars) {
            if (value && String === value.constructor) {
                chars = chars || '\\s';
                //var re = /\s*((\S+\s*)*)/;
                var re = '/^[' + chars + ']*([^' + chars + ']+)$/';
                return value.replace(re, '$1');
            }
            return false;
        },

        rtrim: function(value, chars) {
            if (value && String === value.constructor) {
                chars = chars || '\\s';
                //var re = /((\s*\S+)*)\s*/;
                var re = '/^([^' + chars + ']+)[' + chars + ']*$/';
                return value.replace(re, '$1');
            }
            return false;
        },

        /**
         * Adding contextual asterisks (*) and help messages to required form field labels 
         */
        asterisk: function(label) {
            var ast = Mw.Manipulation.create('span');
            Mw.Manipulation.attr(ast, 'class', 'required asterisk');
            Mw.Manipulation.text(ast, '*');
            Mw.Manipulation.insertAfter(ast, label);
        },
        
        /**
         * Merge two objects into one.
         * MIL: most important last
         */
        merge: function(obj1, obj2) {
            var obj = new Object();
            for (var i in obj1) {
                obj[i] = obj1[i];
            }
            for (var j in obj2) {
                obj[j] = obj2[j];
            }
            return obj;
        },
        
        /**
         * Send form data as POST
         */
        sendPost: function(form)
        {
            $.ajax({
                url: form.action,
                type: 'post',
                data: $(form).serializeArray(),
                success: function(html) {
                    $(form).closest('div.reload-container').html(html);
                    // Disable loading animation
                    Mw.Util.loading(false);
                    $('div.p3').trigger('resize');
                }
            });
            Mw.Util.loading(true);
        
        
            // Do not allow the form to be sent
            return false;
        },
	    
        /**
         * Waiting for loading data
         */
        loading: function(bool)
        {
            var $wrapper = $('<div class="loading-wrapper opaque5"></div>');
            $wrapper.html('<div class="loading-icon"></div>');
            if (bool) {
                $wrapper.appendTo('body');
            } else {
                $('div.loading-wrapper').remove();
            }
        },
    
        /*
         * Dump variable
         */
        var_dump: function(obj) {
            var msg = '';
            for (var key in obj) {
                msg += key+': '+obj[key]+'<br/>';
            }
            Popup.open(msg);
        },
        
        /**
         * Functionality to check all checkboxes through one in the thead;
         * thead and tbody tags are required;
         */
        checkAll: function(checkAll)
        {
            $(checkAll).closest('table').find('tbody input[type=checkbox]').each(function(){
                $(this).attr('checked', checkAll.checked).click(function() {
                    $(checkAll).attr('checked', false);
                });
            });
        }
    
    },
    
    /**
     * Cookie manipulation
     */
    Cookie: {
        get: function(cookie_name) {
            var result = document.cookie.match('(^|;)?' + cookie_name + '=([^;]*)(;|$)');
            return result ? unescape(result[2]) : null;
        },

        set: function(name, value, days, path, domain, secure) {
            var cookie_string = name + "=" + escape(value);
            if (days) {
                var expires = new Date();
                expires.setDate(expires.getDate() + days);
                cookie_string += "; expires=" + expires.toGMTString();
            }
            if (path) cookie_string += "; path=" + escape(path);
            if (domain) cookie_string += "; domain=" + escape(domain);
            if (secure) cookie_string += "; secure";
            document.cookie = cookie_string;
        },

        remove: function(name) {
            var cookie_date = new Date(); // current date & time
            cookie_date.setTime(cookie_date.getTime() - 1);
            document.cookie = name += "=; expires=" + cookie_date.toGMTString();
        }
    },
    
    /**
     * Sitewide specific operations
     */
    Site: {
        
        init: function() {
            
            // Attempt to gather the IP address
            var intCount = 0;
            var intId = setInterval(function() {
                try{
                    var pageTracker = _gat._getTracker(__gaPropertyId);
                    pageTracker._trackPageview();// Purpose not clear???
                    pageTracker._setVar(__ip);
                    clearInterval(intId);
                } catch(e) {
                    d(e);
                }
                // Clear interval on failure explicitely
                if (intCount > 30) {
                    clearInterval(intId);
                    d('Interval has been forced to stop.');
                }
                // Keep track of interval executions
                intCount++;
            }, 2000);
            
        }
        
    },
    
    /**
     * String representation
     */
    toString: function() {
        return 'MagicWeb.org JavaScript Library';
    }

}
