/**
 * jQuery Templates
 *
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Written by: Stan Lemon <stanlemon@mac.com>
 *
 * Based off of the Ext.Template library, available at:
 * http://www.extjs.com
 *
 * This library provides basic templating functionality, allowing for macro-based
 * templates within jQuery.
 *
 * Basic Usage:
 *
 * var t = $.template('<div id="foo">Hello ${name}, how are you ${question}?  I am ${me:substr(0,10)}</div>');
 *
 * $(selector).append( t , {
 *     name: 'Stan',
 *     question: 'feeling',
 *     me: 'doing quite well myself, thank you very much!'
 * });
 *
 * Requires: jQuery 1.2+
 *
 *
 * @todo    Add callbacks to the DOM manipulation methods, so that events can be bound
 *          to template nodes after creation.
 */
(function($){
    
    /**
     * Create a New Template
     */
    $.template = function(html, options) {
        return new $.template.instance(html, options);
    };

    /**
     * Template constructor - Creates a new template instance.
     *
     * @param   html    The string of HTML to be used for the template.
     * @param   options An object of configurable options.  Currently
     *          you can toggle compile as a boolean value and set a custom
     *          template regular expression on the property regx by
     *          specifying the key of the regx to use from the regx object.
     */
    $.template.instance = function(html, options) {
        // If a custom regular expression has been set, grab it from the regx object
        if ( options && options['regx'] ) options.regx = this.regx[ options.regx ];

        this.options = $.extend({
            compile:        false,
            regx:           this.regx.standard
        }, options || {});

        this.html = html;

        if (this.options.compile) {
            this.compile();   
        }
        this.isTemplate = true;
    };

    /**
     * Regular Expression for Finding Variables
     *
     * The default pattern looks for variables in JSP style, the form of: ${variable}
     * There are also regular expressions available for ext-style variables and
     * jTemplate style variables.
     *
     * You can add your own regular expressions for variable ussage by doing.
     * $.extend({ $.template.re , {
     *     myvartype: /...../g
     * }
     *
     * Then when creating a template do:
     * var t = $.template("<div>...</div>", { regx: 'myvartype' });
     */
    $.template.regx = $.template.instance.prototype.regx = {
        jsp:        /\$\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
        ext:        /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
        jtemplates: /\{\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}\}/g
    };
    
    /**
     * Set the standard regular expression to be used.
     */
    $.template.regx.standard = $.template.regx.jsp;
    
    /**
     * Variable Helper Methods
     *
     * This is a collection of methods which can be used within the variable syntax, ie:
     * ${variable:substr(0,30)} Which would only print a substring, 30 characters in length
     * begining at the first character for the variable named "variable".
     *
     * A basic substring helper is provided as an example of how you can define helpers.
     * To add more helpers simply do:
     * $.extend( $.template.helpers , {
     *   sampleHelper: function() { ... }   
     * });
     */
    $.template.helpers = $.template.instance.prototype.helpers = {
        substr : function(value, start, length){
            return String(value).substr(start, length);
        }
    };


    /**
     * Template Instance Methods
     */
    $.extend( $.template.instance.prototype, {
        
        /**
         * Apply Values to a Template
         *
         * This is the macro-work horse of the library, it receives an object
         * and the properties of that objects are assigned to the template, where
         * the variables in the template represent keys within the object itself.
         *
         * @param   values  An object of properties mapped to template variables
         */
        apply: function(values) {
            if (this.options.compile) {
                return this.compiled(values);
            } else {
                var tpl = this;
                var fm = this.helpers;

                var fn = function(m, name, format, args) {
                    if (format) {
                        if (format.substr(0, 5) == "this."){
                            return tpl.call(format.substr(5), values[name], values);
                        } else {
                            if (args) {
                                // quoted values are required for strings in compiled templates, 
                                // but for non compiled we need to strip them
                                // quoted reversed for jsmin
                                var re = /^\s*['"](.*)["']\s*$/;
                                args = args.split(',');

                                for(var i = 0, len = args.length; i < len; i++) {
                                    args[i] = args[i].replace(re, "$1");
                                }
                                args = [values[name]].concat(args);
                            } else {
                                args = [values[name]];
                            }

                            return fm[format].apply(fm, args);
                        }
                    } else {
                        return values[name] !== undefined ? values[name] : "";
                    }
                };

                return this.html.replace(this.options.regx, fn);
            }
        },

        /**
         * Compile a template for speedier usage
         */
        compile: function() {
            var sep = $.browser.mozilla ? "+" : ",";
            var fm = this.helpers;

            var fn = function(m, name, format, args){
                if (format) {
                    args = args ? ',' + args : "";

                    if (format.substr(0, 5) != "this.") {
                        format = "fm." + format + '(';
                    } else {
                        format = 'this.call("'+ format.substr(5) + '", ';
                        args = ", values";
                    }
                } else {
                    args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
                }
                return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
            };

            var body;

            if ($.browser.mozilla) {
                body = "this.compiled = function(values){ return '" +
                       this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn) +
                        "';};";
            } else {
                body = ["this.compiled = function(values){ return ['"];
                body.push(this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn));
                body.push("'].join('');};");
                body = body.join('');
            }
            eval(body);
            return this;
        }
    });


    /**
     * Save a reference in this local scope to the original methods which we're 
     * going to overload.
     **/
    var $_old = {
        domManip: $.fn.domManip,
        text: $.fn.text,
        html: $.fn.html
    };

    /**
     * Overwrite the domManip method so that we can use things like append() by passing a 
     * template object and macro parameters.
     */
    $.fn.domManip = function( args, table, reverse, callback ) {
        if (args[0].isTemplate) {
            // Apply the template and it's arguments...
            args[0] = args[0].apply( args[1] );
            // Get rid of the arguements, we don't want to pass them on
            delete args[1];
        }

        // Call the original method
        var r = $_old.domManip.apply(this, arguments);

        return r;
    };

    /**
     * Overwrite the html() method
     */
    $.fn.html = function( value , o ) {
        if (value && value.isTemplate) var value = value.apply( o );

        var r = $_old.html.apply(this, [value]);

        return r;
    };
    
    /**
     * Overwrite the text() method
     */
    $.fn.text = function( value , o ) {
        if (value && value.isTemplate) var value = value.apply( o );

        var r = $_old.text.apply(this, [value]);

        return r;
    };

})(jQuery);

