﻿// river.js
// A jQuery plugin that creates a river!

// SETTINGS:
// Besides all the normal Echo Stream client settings, you can also specify:
// defaultPluginCallback: function(pluginArray): This function will be called once with the array
//                                               of plugins (defaults merged with whatever caller specified in settings object)
//                                               Modify the array as needed and return it.
//
// Example callback to remove the Reply plugin:
//
// defaultPluginCallback: function(plugins) {
//     var retVal = [];
//     for (var n=0; n < plugins.length; n++) {
//         if (plugins[n].name !== "Reply") {
//             retVal.push(plugins[n]);
//         }
//     }
//     return retVal;
// }
//

(function($) {
    if (typeof $.fn.river === 'undefined') {
        var riverDefined = false; // false if we haven't defined any rivers yet; true if we have
        
        $.fn.river = function(settings) {
            var defaults = {
                maxBodyCharacters: 2000,
                viaLabel: {icon: true, text: false},
                reTag: false,
                streamStateLabel: {icon: false, text: false},
                aggressiveSanitization: false,
                contentTransformations: {
                    text: ["smileys", "urls", "newlines"],
                    html: ["smileys", "urls", "newlines"],
                    xhtml: ["smileys", "urls"]
                },
                children: {
                    additionalItemsPerPage: 5,
                    moreButtonSlideTimeout: 700,
                    itemsSlideTimeout: 700
                }
                
            };

            if (settings) {
                $.extend(true, defaults, settings);
            }

            var authPlugin;
            if (typeof getUMGAuthPlugin === 'function') {
                authPlugin = getUMGAuthPlugin();
            }
            else {
                authPlugin = {name: "FormAuth"};
            }

            var defaultPlugins = [
                {
                    name: "Whirlpools",
                    after: 2,
                    clickable: true
                }, { 
                    name: "Like" 
                },  {
                    name: "Reply",
                    actionString: "Reply...",
                    markers: ["site:" + stripHash(window.location.href)],
                    nestedPlugins: [authPlugin, 
                        { 
                            name: "SocialSharing" 
                        }, { 
                            name: "MetadataTweaks", 
                            markers: ["site:" + stripHash(window.location.href)]
                        }
                    ]
                }, {
                    name: "Curation"
                }, {
                    name: "Edit"
                }, {
                    name: "AvatarLink"
                }, {
                    name: "SocialSharing"
                }, {
                    name: "ClearAfterBody"
                }, {
                    name: "EnhancedMetadata"
                }, {
                    name: "BlankLinks"
                }, {
                    name: "UserBan"
                }, {
                    name: "UserPrivileges"
                }, {
                    name: "CommunityFlag"
                }, {
                    name: "StripHTML"
                }, {
                    name: "Permalink"
                }, {
                    name: "YouTubeWmode"
                }
            ];
            if ($.isArray(defaults.plugins)) {
                $.merge(defaultPlugins, defaults.plugins);
            }
            defaults.plugins = defaultPlugins;

            if (typeof defaults.defaultPluginCallback === 'function') {
                defaults.plugins = defaults.defaultPluginCallback(defaults.plugins);
            }

            return this.each(function() {
                defaults.target = this;
                $(this).data("RiverClient", new RiverClient(defaults, !riverDefined));
                riverDefined = true;
            });
        };
    }

    function stripHash(url) {
        if (url.indexOf('#') != -1) {
            return url.substring(0, url.indexOf('#'));
        }
        else {
            return url;
        }
    }
})(jQuery);

/**
 * Constructor for a river client, which is a stream client with a query selector
 * 
 * HTML structure should be:
 * <div>
 *   <div class="selector" style="display:none">
 *     <a href="#!first">First Selector</a>
 *     <span class="query">childrenof:http://www.example.com/* ...</span>
 *
 *     <a href="#!second">Second Selector</a>
 *     <span class="query">childrenof:http://www.example.com/* ...</span>
 *   </div>
 * </div>
 *
 * Parameters:
 *
 * settings: object - will be passed to the Echo.Stream constructor. The "target" property should
 *                    be set to the outermost DIV in the structure above
 *
 * useFragment: boolean - if true, this river will use the URL fragment for switching queries, and
 *                        the A tags in the selector should contain hash-bang fragments in the href
 *
 *                        if false, this river will attach click handlers to the A tags and 
 *                        the href doesn't matter (but for SEO purposes should NOT contain hash-bangs)
 */
var RiverClient = (function($) {
    return function(settings, useFragment) {
        var self = this;

        // An instance of Echo.Stream
        var streamClient;
            
        // The div that contains the Echo stream client, placed in the DOM adjacent to the selector
        var riverDiv;
            
        // all of the queries in the selector: array of objects with these properties: label, query, el
        var queries;

        // A handy reference to the default query
        var defaultQuery = null;
            
        // The outermost DIV that holds everything
        var riverContainer = settings.target;
        
        /**
         * Performs initial setup
         */
        function init() {
            if (!riverContainer) {
                if (typeof console !== 'undefined') {
                    console.error("No target defined for river.");
                }
                return;
            }

            self.processSelector();

            // check the URL hash for a query label
            var initialQuery = null;
            if (useFragment) {
                initialQuery = queryFromHash();
            }
            if (!initialQuery) {
                initialQuery = defaultQuery;
            }

            riverDiv = $('<div class="river"></div>').appendTo(riverContainer);
            if (typeof initialQuery === 'string') {
                settings.query = initialQuery;
            }
            else if (typeof initialQuery === 'object' && initialQuery.query) {
                settings.query = initialQuery.query;
                setCssClassesForQuery(initialQuery);
            }
            settings.target = riverDiv;
            streamClient = new Echo.Stream(settings);
            
            if (useFragment) {
                var hash = window.location.href.split("#")[1] || "";
                if (hash === "" && typeof initialQuery === 'object') {
                    window.location.hash = "#!" + initialQuery.label;
                }

                setTimeout(function() {
                    $(window).hashchange(function() {
                        var newQuery = queryFromHash();
                        if (newQuery) {
                            self.setQuery(newQuery);
                        }
                    });
                }, 0);
            }
            else {
                $.each(queries, function(i, o) {
                    $(o.el).click(function(e) {
                        e.preventDefault();
                        self.setQuery(o);
                    });
                });
            }
        };

        /**
         * Reads the selector and queries from the DOM and populates the queries object.
         * This can be called repeatedly if the DOM structure of the selector changes.
         */
        this.processSelector = function() {
            queries = [];
            $("div.selector a", riverContainer).each(function() {
                var a = $(this)
                var className;
                var query;
                var queryLabel;
                var queryObject;

                var queryElement = a.next("span.query");
                if (queryElement.length > 0) {
                    query = queryElement.text();
                    queryElement.hide();
                    if (a.attr('href') && a.attr('href').substr(0, 2) == '#!') {
                        queryLabel = a.attr('href').substr(2);
                    }
                }
                else { // no span.query found, must be using the old syntax.
                    className = a.parent().attr("class");
                    if (className && a[0]) {
                        if (className.indexOf(' ') == -1) {
                            queryLabel = className;
                        }
                        else {
                            queryLabel = className.substring(0, className.indexOf(' '));
                        }
                        query = unescape(a[0].href);

                        if (useFragment) {
                            a.attr("href", "#!" + queryLabel);
                        }
                        else {
                            a.attr("href", "#");
                        }
                    }
                }

                if (query) {
                    if (query.substr(0, 6) == "river:") {
                        query = query.substr(6);
                    }
                    if (!queryLabel) {
                        queryLabel = getUniqueId("query");
                    }
                    queryObject = {label: queryLabel, query: processQuery(query), el: a};
                    queries.push(queryObject);
                    if (defaultQuery === null && a.hasClass("default")) {
                        defaultQuery = queryObject
                    }
                }
            });

            if (defaultQuery === null) {
                defaultQuery = queries[0];
            }

            $("div.selector", riverContainer).show();
        }

        /**
         * Sets the query in the Stream client
         */
        this.setQuery = function(query) {
            if (typeof query === 'object' && query.query) {
                streamClient.config.set("query", query.query);
                setCssClassesForQuery(query);
            }
            else if (typeof query === 'string') {
                clearQueryRelatedCssClasses();
                streamClient.config.set("query", query);
            }
            else {
                return;
            }
            streamClient.refresh();
        }

        /**
         * Returns a query based on the URL hash, or null if the hash does not contain a good value
         */
        function queryFromHash() {
            var hash = window.location.href.split("#")[1] || "";
            
            if (hash.length > 3 && hash.substr(1, 2) === "p=") {
                return permalinkQuery(hash.substring(3));
            }
            else if (hash.length > 1) {
                return queryNamed(hash.substring(1));
            }

            return null;
        }

        /**
         * returns the query with the given label, or null if none have that label
         */
        function queryNamed(label) {
            for (var n=0; n < queries.length; n++) {
                if (queries[n].label === label) {
                    return queries[n];
                    break;
                }
            }
            return null;
        }

        /**
         * Returns a query that displays a single item (for permalinks)
         */
        function permalinkQuery(url) {
            var childQuery = "children:1 -state:ModeratorDeleted,ModeratorFlagged,SystemFlagged,CommunityFlagged -user.state:ModeratorBanned";

            if (defaultQuery && defaultQuery.query) {
                var queryParts = splitQuery(defaultQuery.query);
                childQuery = queryParts.children;
            }

            return "url:" + url + " -state:ModeratorDeleted safeHTML:off " + childQuery;
        }

        /**
         * Splits a query in two and returns the parent and children parts
         */
        function splitQuery(query) {
            var parents;
            var children;

            var splitPoint = query.search("children(\s|:|$)");
            if (splitPoint == -1) {
                parents = query;
                children = "";
            }
            else {
                parents = query.substring(0, splitPoint);
                children = query.substring(splitPoint);
            }

            return {parents: parents, children: children};
        };

        /**
         * Clears all the query-specific CSS classes
         */
        function clearQueryRelatedCssClasses() {
            riverDiv.removeClass(function(index, className) {
                var classes  = className.split(" ");
                var retVal = "";
                for (var n=0; n < classes.length; n++) {
                    if (classes[n].substr(0, 9) === "selected-") {
                        retVal += " " + classes[n];
                    }
                }
                return retVal;
            });

            $("div.selector a.selected", riverContainer).removeClass("selected");
        }

        /**
         * Sets all the query-specific CSS classes, such as the "selected" class on the selector <A> tag
         */
        function setCssClassesForQuery(query) {
            // Clear any previously-set classes
            clearQueryRelatedCssClasses();

            // Sets a "selected-????" class on the river div that cooresponds to the currently selected query
            riverDiv.addClass("selected-" + query.label);

            // Sets a "selected" class on the selector <A> tag
            $(query.el).addClass("selected");
        }

        /**
         * Generates a unique ID (not random)
         */
        var getUniqueId = (function() {
            var uniqueid = 0;
            return function(prefix) {
                var retVal = prefix + uniqueid;
                uniqueid++;
                return retVal;
            }
        })();

        /**
         * Makes the following modifications to the query:
         *  1. sets safeHTML:off
         */
        function processQuery(query) {
            query = query.replace(/safehtml:\S+/ig, "");
            var queryParts = splitQuery(query);

            return queryParts.parents + " safeHTML:off " + queryParts.children;
        }

        init();
    }
})(jQuery);
