(function( $ ){
    $.randomString = function(stringLength)
    {
        chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
        randomString = '';
        for (i = 0; i < stringLength; i++)
        {
            var rnum = Math.floor(Math.random() * chars.length);
            randomString += chars.substring(rnum, rnum + 1);
        }
        return randomString;
    };

})( jQuery );

(function( $ ){

    $.feedView = $.feedView || {version: '1.0'};
    $.feedView.defaultOptions = {
            'feedURL' :         '/feed/',
            'feedParams' :      [],
            'mainElem' :        null,
            'mainClass' :       'scroller',
            'itemsInGroup' :    2,
            'id' :              'scroller-' + $.randomString(6),     
            'interval' :        5000,
            'updateInterval' :  90000,
            'runImmediately' :  true,
            'items' :           null,
            'prevLink' :        "a.prev",
            'nextLink' :        "a.next",
            'errorPrototype' :  $("<div class='scrollgroup'><div class='error'></div></div>"),
            'groupPrototype' :  $("<div class='scrollgroup'></div>"),
            'itemPrototype' :   $("<div class='scrollitem'><a class='scroll-link'><img class='icon' /><div class='labels'><h1 class='label1'></h1><h2 class='label2'></h2></div></a></div>"),
            'groupClasses' :    [],
            'itemClasses' :     [],
            'classFeedMap' :    {   'h1.label1' : 'title',
                                    'h2.label2' : 'description',
                                    'img.icon@src' : 'enclosure@url',
                                    'a.scroll-link@href' : 'link'
                                },
            'feedSuccess' :     function(data, textStatus, jqXHR) {},
            'feedError' :       function(jqXHR, textStatus, errorThrown) {},
            'feedComplete' :    function(jqXHR, textStatus)  {},
            'intervalID' :      null,
            'rssPredicates' :   {},
            'messages' :        {
                                    noPosts: 'Nothing to show right now...',
                                    error: 'Error while loading feed: ',
                                },
        
    }; 
    function FeedView(container, newOpts)
    {
        this.container = container;
        this.initialLoad = true;
        this.running = false;
        this.processing = false;

        this.options = $.extend({}, $.feedView.defaultOptions);
        this.options.prevLink = $(this.options.prevLink);
        this.options.nextLink = $(this.options.nextLink);
        this.options = $.extend(this.options, newOpts);

        return this;
    }
    FeedView.prototype =
    {
        init: function(newOpts)
        {
            if (!this.options.mainElem)
            {
                this.options.mainElem = $("<div class='" + this.options.mainClass + "' id='" + this.options.id + "'></div>");
                this.options.items = $("<div class='items'></div>");
                this.options.mainElem.append(this.options.items);
                this.container.append(this.options.mainElem);
                // start the update
                if (this.options.runImmediately)
                    this.getFeed();
                this.start();
            }
        },
        start: function()
        {
            if (!this.running)
            {
                if (this.scrollableAPI) this.scrollableAPI.play();
                var self = this;
                
                timerFunction = function() { self.getFeed.apply( self ) };
                this.intervalID = setInterval(timerFunction, this.options.updateInterval);
                this.running = true;
            }
        },
        stop: function()
        {
            if (this.running)
            {
                if (this.scrollableAPI) this.scrollableAPI.pause();
                clearInterval(this.intervalID);
                this.intervalID = null;
                this.running = false;
                if (this.processing)
                {
                    // stop the xhr
                    this.xhr.abort();
                    this.processing = false;
                }
            }
        },
        getFeed: function()
        {
            var self = this;
            this.xhr = $.get(this.options.feedURL, this.options.feedParams, function() {self.feedSuccess.apply(self, arguments)})
                .error(function() {self.feedError.apply(self, arguments)})
                .complete(function() {self.feedComplete.apply(self, arguments)});
            this.processing = true;
        },
        refresh: function()
        {
            // clear all existing groups
            if (this.scrollableAPI) this.scrollableAPI.clear(); 
            this.groupItemCount = 0;
            rssItemsCopy = this.rssItems.slice(0);
            while (rssItem = rssItemsCopy.shift())
            {
                /*
                // don't add this one if we already have it
                rssGuid = rssItem.find("guid").text();
                if (rssGuid && this.options.guidMap[rssGuid])
                    return true; // continue
                else if (rssGuid)
                    this.options.guidMap[rssGuid] = true;
                */

                // create new group if necessary
                if ((this.groupItemCount++ % this.options.itemsInGroup) == 0)
                {
                    this.groupItemCount = 1;
                    group = this.options.groupPrototype.clone();
                    // add the list of css classes
                    for (groupClass in this.options.groupClasses)
                        group.addClass(groupClass);
                }
                                
                // create new item view
                item = this.options.itemPrototype.clone();
                // apply the list of css classes
                for (itemClass in this.options.itemClasses)
                    item.addClass(itemClass);

                // now fill in the item.
                // using the select expressions from the attribute map,
                // map values from feed to HTML elements
                for (selector in this.options.classFeedMap)
                {
                    rssSelector = this.options.classFeedMap[selector];
                    // attribute?
                    attrIndex = selector.indexOf('@');
                    if (attrIndex > 0)
                    {
                        // we're looking for an attribute.
                        elemSelector = selector.substring(0, attrIndex);
                        attributeName = selector.substring(attrIndex + 1);
                    }
                    else
                    {
                        elemSelector = selector;
                        attributeName = null;
                    }

                    rssAttrIndex = rssSelector.indexOf('@');
                    if (rssAttrIndex > 0)
                    {
                        rssElemSelector = rssSelector.substring(0, rssAttrIndex);
                        rssAttributeName = rssSelector.substring(rssAttrIndex + 1);
                    }
                    else
                    {
                        rssElemSelector = rssSelector;
                        rssAttributeName = null;
                    }

                    rssElem = (rssElemSelector === '.' ? rssItem : rssItem.find(rssElemSelector));
                    htmlElem = (elemSelector === '.' ? item : item.find(elemSelector));
                    if ( rssElem && htmlElem )
                    {
                        rssValue = (rssAttributeName ? rssElem.attr(rssAttributeName) : rssElem.text());
                        if (attributeName)
                            htmlElem.attr(attributeName, rssValue);
                        else
                            htmlElem.text(rssValue);
                    }        
                }
            
                group.append(item);     

                if (this.groupItemCount == 1)
                {
                    if (this.initialLoad)
                        this.options.items.append(group);
                    else
                        this.scrollableAPI.addItem(group);
                }
            }

            if (this.initialLoad)
            {
                this.options.mainElem.scrollable({item: ".scrollgroup"}).navigator().autoscroll({interval:this.options.interval});
                this.scrollableAPI = this.options.mainElem.data("scrollable");

                this.initialLoad = false;
            }
        
            if (this.options.items.children().size() == 0)
            {
                errorMsg = this.options.errorPrototype.clone();
                errorMsg.find(".error").text(this.options.messages.noPosts);
                this.scrollableAPI.addItem(errorMsg);
            }
        },
        feedSuccess: function(data, textStatus, jqXHR)
        {
            this.rssItems = [];
            unfilteredRssItems = [];
            rssItemList = $(data).find("item");
            rssItemList.each(function(i, rssItem) { unfilteredRssItems.push($(rssItem)) });
            
            this.unfilteredRssItems = unfilteredRssItems;
            this.filterPredicates();
            this.refresh();
     
               // forward to custom handler
            this.options.feedSuccess(data, textStatus, jqXHR);
        },
        feedError: function(jqXHR, textStatus, errorThrown)
        {
            // if we haven't yet loaded anything, display an error
            if (self.initialLoad) 
            {
                errorMsg = this.options.errorPrototype.clone();
                errorMsg.find(".error").text(this.options.messages.error + textStatus);
                this.scrollableAPI.addItem(errorMsg);
            }
            // otherwise, keep showing whatever we had before
            // forward to custom handler
            this.options.feedError(jqXHR, textStatus, errorThrown);
        },
        feedComplete: function(jqXHR, textStatus)
        {

            // forward to custom handler
            this.options.feedComplete(jqXHR, textStatus);
        },
        filterPredicates: function()
        {
            this.rssItems = [];
            if ((typeof unfilteredRssItems === "undefined") || (unfilteredRssItems.length == 0))
                return;
            unfilteredRssItemsCopy = this.unfilteredRssItems.slice(0);
            while (rssItem = unfilteredRssItemsCopy.shift())
            {
                matchesPredicates = true;
                for (predicateKey in this.options.rssPredicates)
                {
                    predicate = this.options.rssPredicates[predicateKey];
                    if (rssItem.find(predicate).size() == 0)
                    {
                        matchesPredicates = false;
                        break;
                    }
                }
                if (matchesPredicates)
                    this.rssItems.push(rssItem);
            }
        },
        addPredicate: function(name, selector)
        {
            this.options.rssPredicates[name] = selector;
            this.filterPredicates();
            this.refresh();
        },
        removePredicate: function(name)
        {
            if (this.options.rssPredicates[name])
            {
                delete this.options.rssPredicates[name];
                this.filterPredicates();
                this.refresh();
            }
        },
        setPredicates: function(newPredicates)
        {
            this.options.rssPredicates = newPredicates;
            this.filterPredicates();
            this.refresh();
        },
        predicates: function()
        {
            return this.options.rssPredicates;
        },
    };

    $.fn.feedView = function(method)
    {
        api = this.data("FeedView");
        if (!api)
        {
            api = new FeedView(this, method);
            this.data("FeedView", api);
        }
        var self = api;
        if ((typeof method === 'object') || !method) { 
            api.init.apply( api, Array.prototype.slice.call(arguments, 1));
        } else if (api[method]) {
            return api[method].apply(api, Array.prototype.slice.call(arguments, 1));
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.feedView' );
        }
    };

})( jQuery ); 


