// JSON to HTML recursive templating
// Philippe Rathé - http://philrathe.com/ - MIT Licensed
var rSupplant = (function() {

    var tmplProperty = "template";
    var tmplContentProperty = "outerContent";
    var tmplListProperty = "innerContent";
    var functionsProperty = "placeholders";
    var contentPlaceholder = "<outerContent>";
    var listPlaceholder = "<innerContent>";
    var recursiveListProperty = "recursiveProperty";
    var defaultRecursiveListProperty = "list";

    function supplant(str, o) { 
        return str.replace(/\{([^{}]*)\}/g, 
            function (a, b) {  
                var r = o[b];
                if (typeof r === "function") {
                    return r.call();
                } else if (typeof r === "string") {
                    return r;
                } else if (typeof r === "number") {
                    return r.toString();
                } else {
                    return a;
                }
            }
        ); 
    }

    var innerRSupplant = function (data, setup, extraParams) {

        var args = arguments;
        var list;
        var o = {};
        o[setup[recursiveListProperty] || defaultRecursiveListProperty] = data;

        return (function (o, extraParams, depth, m, parents) {
            parents = parents || [];
            m = m || "";
            depth = depth || 0;
            var _m = m; // A copy, not to mix within stacked results and nested results
            var o_extended;
            var args = arguments;

            var list = o[setup[recursiveListProperty] || defaultRecursiveListProperty];
            if (! (list instanceof Array)) {
                return ""; // Stop recursion
            } else {
                depth++;
                for (var i = 0; i < list.length; i++) {

                    // Extend the original object with those placeholder properties
                    o_extended = (function () {

                        // Add template's functions bindings placeholder properties
                        var _o_extended = (function () {

                            // Build the object of attribute/value for supplanting the template
                            var props = {};
                            for (var attr in setup[functionsProperty]) {
                                if (setup[functionsProperty].hasOwnProperty(attr)) {
                                    props[attr] = (function (fn, _o) {
                                        return fn.apply(setup[functionsProperty], [_o, {
                                            // State of current data and placeholder being processed
                                            placeholderName: attr,
                                            parents: parents,
                                            depth: depth,
                                            index: i
                                        }, extraParams]);
                                    }(setup[functionsProperty][attr], list[i]));
                                }
                            }
                            return props;
                        }());


                        // Add content placeholder property: deep recursion (nesting)
                        _o_extended[contentPlaceholder] = args.callee(list[i], extraParams, depth, _m, parents.concat(list[i]));

                        return _o_extended;
                    }());

                    for (var attr in o_extended) {
                        if (o_extended.hasOwnProperty(attr)) {
                            list[i][attr] = o_extended[attr];
                        }
                    }

                    // Concatenate results (stacking)
                    m = m + supplant(setup[tmplProperty][tmplListProperty], list[i]);
                }

                // Final results of nesting and stacking
                var internalList = {}; internalList[listPlaceholder] = m;
                return supplant(setup[tmplProperty][tmplContentProperty], internalList); 
            }
        }(o, extraParams));
    };

    return innerRSupplant;
}());
