/* 
jquery string templating engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This template engine takes a webstring like approach to templating.
Instead of putting control directives directly into your markup,
you create a fragment of fairly normal markup with $names as replacement
placeholders. Filling the template and expanding structures is then done
in javascript.

Usage
-----

javascript::

var template = '<ul><li><a href="$link">$title</a></li></ul>';
    var data = [
        {link:'http://www.google.com', title:'google'},
        {link:'http://www.digg.com', title:'digg'},
        {link:'http://www.reddit.com', title:'reddit'}
    ];
    $(template).repeat('li', data, function(i, item, node){
        node.replace(item);
    }).appendTo('body');

The output of this is::

    <ul>
        <li><a href="http://www.google.com">google</a></li>
        <li><a href="http://www.digg.com">digg</a></li>
        <li><a href="http://www.reddit.com">reddit</a></li>
    </ul>

:copyright: (C) 2009 Florian Boesch <pyalot@gmail.com>
:license: GNU AGPLv3, see http://www.fsf.org/licensing/licenses/agpl-3.0.html for more details.
    
*/

(function () {
    var copy_array = function(arr){
        var new_arr = [];
        for(var j=0; j<arr.length; j++){
            new_arr[j] = arr[j];
        }
        return new_arr;
    };
    var compute_access = function(fragment){
        var search_re = /\$[a-zA-Z_]+/g;
        var access = {};
        var visit = function(node){
            if(node.nodeName != '#comment'){
                $.each(node.attributes, function(i, attribute){
                    matches = attribute.nodeValue.match(search_re);
                    if(matches){
                        $.each(matches, function(i, match){
                            access[match] = function(value){
                                attribute.nodeValue = attribute.nodeValue.replace(match, convert_text(value));
                            }
                        })
                    }
                });
                $.each(node.childNodes, function(i, child){
                    if(child.nodeName == '#text'){
                        matches = child.textContent.match(search_re);
                        if(matches){
                            access[matches[0]] = function(value){
                                $.each(convert(value), function(i, item){
                                    node.insertBefore(item, child);
                                })
                                node.removeChild(child);
                            };
                        }
                    }
                    else{
                        visit(child);
                    }
                });
            }
        };
        visit(fragment);
        return access;
    };
    var compute_fast_access = function(fragment){
        var search_re = /\$[a-zA-Z_]+/g;
        var fast_access = {};
        var visit = function(path, node){
            $.each(node.attributes, function(i, attribute){
                matches = attribute.nodeValue.match(search_re);
                if(matches){
                    var new_path = copy_array(path);
                    new_path.push(attribute.nodeName);
                    $.each(matches, function(i, match){
                        fast_access[match] = new_path;
                    })
                }
            });
            $.each(node.childNodes, function(i, child){
                var new_path = copy_array(path);
                new_path.push(i);
                if(child.nodeName == '#text'){
                    matches = child.textContent.match(search_re);
                    if(matches){
                        $.each(matches, function(i, match){
                            fast_access[match] = new_path;
                        });
                    }
                }
                else{
                    visit(new_path, child);
                }
            });
        };
        visit([], fragment[0]);
        return fast_access;
    };
    var convert_text = function(value){
        if(value){
            return new String(value);
        }
        else{
            return '';
        }
    };
    var convert = function(value){
        if(value){
            if(typeof(value) == 'string' || typeof(value) == 'number'){
                return [document.createTextNode(new String(value))];
            }
            else{
                return value;
            }
        }
        else{
            return [document.createTextNode('')];
        }
    }
    var obj_length = function(obj){
        var counter = 0;
        for(name in obj){
            counter += 1;
        }
        return counter;
    };


    jQuery.fn.templateRepeat = function(data, selection, fun){
        var elem = this;
        var fragment = $(selection, elem);
        var parent = fragment.parent();
        fragment.remove();
        var fast_access = compute_fast_access(fragment);
        $.each(data, function(i, item){
            var to_insert = fragment.clone();
            to_insert.fast_access = fast_access;
            if(fun){
                fun(i, item, to_insert);
            }
            parent.append(to_insert);
        });
        return elem;
    };

    jQuery.fn.repeat = function(data, fun){
        var fragment = $(this);
        var parent = fragment.parent();
        fragment.remove();
        var fast_access = compute_fast_access(fragment);
        $.each(data, function(i, item){
            var to_insert = fragment.clone();
            to_insert.fast_access = fast_access;
            parent.append(to_insert);
            if(fun){
                fun(item, to_insert);
            }
        });
        return this;
    };

    jQuery.fn.repeatFor = function(data){
        var fragment = this;
        if(fragment.length == 0){
            throw 'no selection';
        }
        var parent = fragment.parent();
        fragment.remove();
        var fast_access = compute_fast_access(fragment)
        $.each(data, function(i, item){
            var to_insert = fragment.clone();
            to_insert.fast_access = fast_access;
            to_insert.templateReplace(item);
            parent.append(to_insert);
        });
        return this;
    };
    
    jQuery.fn.templateReplace = function(arg1){
        var elem = this;
        if(elem.fast_access){
            var access = {};
            $.each(elem.fast_access, function(name, path){
                var node = elem[0];
                var attr = null;
                $.each(path, function(i, index){
                    if(typeof(index) == 'number'){
                        node = node.childNodes[index];
                    }
                    else{
                        attr = node.attributes.getNamedItem(index);
                    }
                });
                if(attr){
                    access[name] = function(value){
                        attr.nodeValue = attr.nodeValue.replace(name, convert_text(value));
                    }
                }
                else{
                    access[name] = function(value){
                        var parent = node.parentNode;
                        $.each(convert(value), function(i, item){
                            parent.insertBefore(item, node);
                        })
                        parent.removeChild(node);
                    };
                }
            })
        }
        else{
            var access = compute_access(elem[0]);
        }
        
        $.each(arg1, function(search, value){
            var action = access['$' + search];
            if(action){
                action(value);
            }
        });

        return elem;
    };

    jQuery.fn.fill = jQuery.fn.templateReplace;

    jQuery.loadResources = function(paths, params){
        var loaded = {};
        var total = 0;
        var counter = 0;
        if(params.progress){
            params.progress(0, 'start');
        }
        var count = function(paths){
            $.each(paths, function(name, entry){
                if(typeof(entry) == 'string'){
                    if(name != 'path'){
                        total += 1;
                    }
                }
                else{
                    count(entry);
                }
            });
        };
        var load = function(root, paths, result){
            var root = root ? root : '';
            $.each(paths, function(name, entry){
                if(typeof(entry) == 'string'){
                    if(name != 'path'){
                        var path = root ? root + '/' + entry : entry;
                        if(path.search(/html$/g) >= 0){
                            $.get(path, function(data){
                                result[name] = $(data);
                                counter += 1;
                                if(params.progress){
                                    params.progress(counter/total, path);
                                }
                                if(counter == total){
                                    if(params.load){
                                        params.load(loaded);
                                    }
                                }
                            });
                        }
                        else if(path.search(/json$/g) >= 0){
                            $.getJSON(path, function(data){
                                result[name] = data;
                                counter += 1;
                                if(params.progress){
                                    params.progress(counter/total, path);
                                }
                                if(counter == total){
                                    if(params.load){
                                        params.load(loaded);
                                    }
                                }
                            });
                        }
                        else if(path.search(/(png|jpeg|jpg|gif)$/g) >= 0){
                            result[name] = $('<img></img>')
                                .load(function(){
                                    counter += 1;
                                    if(params.progress){
                                        params.progress(counter/total, path);
                                    }
                                    if(counter == total){
                                        if(params.load){
                                            params.load(loaded);
                                        }
                                    }
                                })
                                .error(function(){
                                    counter += 1;
                                    if(params.error){
                                        params.error(counter/total, path);
                                    }
                                    if(counter == total){
                                        if(params.load){
                                            params.load(loaded);
                                        }
                                    }
                                })
                                .attr('src', path);
                        }
                    }
                }
                else{
                    if(typeof(entry.length) == 'number'){
                        var sub_result = result[name] = [];
                    }
                    else{
                        var sub_result = result[name] = {};
                    }
                    var entry_path = entry.path ? entry.path : '';
                    if(root){
                        if(entry_path){
                            var path = root + '/' + entry_path;
                        }
                        else{
                            var path = root;
                        }
                    }
                    else if(entry_path){
                        var path = entry_path;
                    }
                    else{
                        var path = '';
                    }
                    load(path, entry, sub_result);
                }
            });
        }
        count(paths);
        load('', paths, loaded);
    };
})();
