/**
 * Element Extensions
 * **************************************************
 */
Element.implement({

    overlay: function (className) {
        // create & return element that matches the coordinates of "this" element
        var el = this;
        var coords = el.getCoordinates();
        var overlay = new Element('div', {
            "class" : className || "busyOverlay",
            "styles": {
                "left":coords.left,
                "top":coords.top,
                "width": coords.width,
                "height": coords.height
            }
        });
        overlay.inject($E('body'));
        return overlay;
    },

    /**
     * Find options in a select that have specific innerText
     * @return array of extended OPTION elements
     * @param {Object} text
     */
    getOptionsByText : function(text){
        var validOptions = this.getElements('option').filter(function(o){
            return o.get('text') == text;
        });
        return $$(validOptions);
    },

    /**
     * Find options in a select that have a specific value
     * @return array of extended OPTION elements
     * @param {Object} value
     */
    getOptionsByValue : function(value){
        var validOptions = this.getElements('option').filter(function(o){
            return o.get('value') == value;
        });
        return $$(validOptions);
    },

    /**
     * Sets the selected index of a SELECT element to OPTION which has innerText = text
     * @param {Object} text
     */
    selectByText : function(text){
        var options = this.getElements('option');
        var validOptions = options.filter(function(o,i){
            return (o.get('text') == text);
        });
        this.selectedIndex = options.indexOf(validOptions[0]);
        return this;
    },

    /**
     * Sets the selected index of a SELECT element to OPTION which has value = value
     * @param {Object} value
     */
    selectByValue : function(value){
        var options = this.getElements('option');
        var validOptions = options.filter(function(o,i){
            return (o.get('value') == value);
        });
        this.selectedIndex = options.indexOf(validOptions[0]);
        return this;
    },

    /**
     * Allows an arbitray element to receive focus by attaching a tabindex to it
     * @param {Object} int
     */
    tabindex : function(index){
        var index = index || 0;
        this.set(((Browser.Engine.trident) ? 'tabIndex' : 'tabindex'), index);
        return this;
    },

    /**
     * Finds all things that can recieve focus in document order within a container
     */
    getFocusables : function(){
        var focusables = this.getElements('*').filter(function(el,i){
            return (el.get('tag').test(/\binput|select|textarea|button|a\b/,'i') && el.get('type') !== "hidden");
        });
        return focusables;
    },

    /**
     * A different way to do delegation?:
     * Acts like addEvent, but instead creates a list of events and formatted class names to listen for
     */
    registerEvent : function(e, classID, fn){

    },

    unregisterEvent : function(){

    },

    /**
     * .match() is buggy at the moment - this is a temporary fix until its fixed in core
     */
	match: function(selector){
		if (!selector) {
            return false;
        } else if (selector === this) {
            return true;
        } else if ($type(selector) === "string"){
    		var tagid = Selectors.Utils.parseTagAndID(selector);
    		var tag = tagid[0], id = tagid[1];
    		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
    		var parsed = Selectors.Utils.parseSelector(selector);
    		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
        } else {
            return false;
        }
	},

    // BUG: Mootools ignores <button> elements as possible submit elements
    toQueryString: function(){
		var queryString = [];
		this.getElements('input, select, textarea, button', true).each(function(el){
			if (!el.name || el.disabled) return;
			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
				return opt.value;
			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
			$splat(value).each(function(val){
				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	}

});

/**
 * String Extensions
 * **************************************************
 */
String.implement({

    /**
     * Form validation methods
     */
    // checks for any symbols that arent letters or numbers w/ optional array of exempted chars, returns boolean
    // returns false for invalid, true for valid
    isValid : function(exceptions){
        var exceptions = $splat(exceptions);
        var pattern = "[^" + exceptions.join() + "]";
        var test = new RegExp(pattern,"g");
        if (this.match(test)){
            return false;
        } else {
            return true;
        }
    },

    isValidTag: function(){
        return (this.clean() === "" || this.isValid(['\\w','\\s', ',', '\&']) === true);
    },

    isValidName: function(){
        return (this.clean() !== "" && this.clean().isValid(['\\w','\\s','\#', "'", '\&', '\.', '\-']) !== false);
    },

    // matches the system date pattern only (YYYY-MM-DD) ... accepts 1900-01-01 - 2099-12-31
    isDate : function(){
        return this.test("(19|20)[0-9]{2}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])");
    },

    // Validates system amount pattern only
    isAmount : function(){
        return (this.trim() !== "" && !this.match(/[^\d.]/));
    },

    // gets a variable from a query string
    getVar : function (v) {
        variable = v.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regexS = "[\\?&]" + variable + "=([^&#]*)";
        var regex = new RegExp(regexS);
        var results = regex.exec(this);
        if (results == null) {
            return "";
        } else {
            return results[1];
        }
    },
    
    // remove a extraneous trailing character from a string
    tidy : function (character) {
        var c = character || ',';
        var s = this.clean().replace(/\s+(,)\s+/ig,','); // make regex and replace "character" var        
        return (s.charAt(s.length-1) === c) ? s.substr(0, s.length-1) : s;
    }

});

/**
 * Number Extensions
 * **************************************************
 */

Number.implement({
    // formats a number to a pretty string
    moneyFormat : function(separator, currency){
        // every 3 digits before the decimal has a {separator}
        // currency has 2 decimal places
        // optional currency in front of string

        var separator = separator || ",";
        var currency = currency || '';

        var chunks = this.toFixed(2).split(".");
        var digits = chunks[0].split(''); // get digits before decimal into array
        var sign =  (digits[0] === "+" || digits[0] === "-") ? digits.shift() : ''; // watch for signed numbers

        // counting away from decimal place not including the first 3 digits, insert separator every 3 #s
        for (var i = (digits.length-3); i > 0; i -= 3){
            digits.splice(i,0,separator);
        }
        return (sign + currency + digits.join('') + '.' + chunks[1]);
    }
});


/**
 * Array Extensions
 * **************************************************
 */
Array.implement({
    removeEmpties : function(){
        var non_empty_array = this.filter(function(item){
            return (item && item.clean() !== "");
        });
        return non_empty_array;
    },

    trim: function(){
        var trimmed_string_array = this.map(function(item){
            return item.clean();
        });
        return trimmed_string_array;
    },

    max : function(){
        return Math.max.apply(Math, this);
    },

    min : function(){
        return Math.min.apply(Math, this);
    }

});
