/**
 * Habit Stream Widget - http://www.habitstream.com
 * Author: Devin Hunt (devin@habitindustries.com)
 * Copyright (C) 2010 
 * V 1.0 of generic Stream Widget in HTML/JS
 *
 * Ify snippet from Mr. Dustin Diaz.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Widget Options
 * ============================================================================
 * title Title text for the widget
 * caption The subtitle for the widget
 * version Specifies version behavior the widget should work on. Default is 1.00
 * apiKey Your Habit Stream API Key. See http://www.habitstream.com/docs/api/key for deatils
 * streamId The stream you are accessing. Provided on your API Broadcast page
 * bcastId The Broadcast Id
 * layout Can be set for list or grid. Not in use at the moment.
 * width The widget of the widget. Can either be a number in pixels or 'auto'
 * height The height of the widget. Can either be a number in pixels or 'auto'
 * allowScroll When true, the content will have a scroll bar
 * theme The theme object. 
 * customCssUrl Url to a CSS style sheet which will be used in place of the default css sheet. 
 * skipCss Skips all CSS loading and themes all together. Useful if you want to define the widget CSS in your own sheet. 
 * liveRefresh When true, the widget will check at a constant interval for new approved items in your Stream
 */

HabitStream = window.HabitStream || {};

// Begin Widget Enclosure
(function() {

    if(HabitStream && HabitStream.Widget) {
        // avoids collisions if multiple widget are embedded
        return;
    }
    
    /**
     * Helper function for grabbing elements by id
     */
    function getEltById(id) {
        if((typeof id) == 'string') {
            return document.getElementById(id);
        }
        return id;
    }
    
    /**
     * Helper function for grabbing a series of elements by id. 
     * Borrowed from Dustin Diaz's getByClass. 
     * @param c The class name to search for
     * @param tag The tag to filer by
     * @param getAll If true, all results will be returned in an array. Default to false.
     * @param root Where to start the spidering at. Defaults to the document.
     */
    function getEltByClass(c, tag, getAll, root) {
        var tag = tag || '*';
        var getAll = getAll || false;
        var root = root || document;
        
        var nodes = [];
        var elements = root.getElementsByTagName(tag);
        var re = getClassRegEx(c);
        
        for(var i = 0, len = elements.length; i < len; ++ i) {
            if(re.test(elements[i].className)) {
                nodes[nodes.length] = elements[i];
            }
        }
        return getAll ? nodes : nodes[0];
    }
    
    /**
     * Creates a regex to find classes
     */
    function getClassRegEx(c) {
        return new RegExp('(?:^|\\s+)' + c + '(?:\\s+|$)');
    }
    
    /**
     * Are we using IE?
     */
    var browser = function() {
      var agent = navigator.userAgent;
      return {
        ie: agent.match(/MSIE\s([^;]*)/)
      };
    }();
    
    /**
     * Ify from Mr. Dustin Diaz.
     * http://www.dustindiaz.com/basement/ify.html
     */
    var ify = function() {
      return {
        "link": function(t) {
          return t.replace(/(^|\s+)(https*\:\/\/\S+[^\.\s+])/g, function(m, m1, link) {
            return m1 + '<a href=' + link + '>' + ((link.length > 30) ? link.substr(0, 29) + '...' : link) + '</a>';
          });
        },
        "at": function(t) {
          return t.replace(/(^|\s+)\@([a-zA-Z0-9_]{1,15})/g, function(m, m1, m2) {
            return m1 + '@<a href="http://twitter.com/' + m2 + '">' + m2 + '</a>';
          });
        },
        "hash": function(t) {
          return t.replace(/(^|\s+)\#([a-zA-Z0-9_]+)/g, function(m, m1, m2) {
            return m1 + '#<a href="http://search.twitter.com/search?q=%23' + m2 + '">' + m2 + '</a>';
          });
        },
        "clean": function(tweet) {
          return this.hash(this.at(this.link(tweet)));
        }
      };
    }();
    
    /**
     * Object that contains the basic html structure for an
     * item in the widget list
     * @param item The raw object data for the item
     */
    var WidgetItem = function(item) {
        function cleanBySource(text, source) {
            if(source == "TwitterSearch") {
                return ify.clean(text);
            }
            return ify.link(text);
        }
        
        function getItemContent(item) {
            var content = '<p class="item-text">' + cleanBySource(item.content, item.inputSource) + '</p> \
                           <p class="item-author"><small><a href="' + item.urlSource + '">' + item.author + '</a></small></p>';
            
            if(item.title) {
                return '<h4 class="item-title">' + item.title + '</h4>' + content;
            }
            return content;
        }
        
        var html = '<div class="strm-item-inner"> \
                        <div class="strm-item-thumb"> \
                            <img alt="' + item.author + '" src="' + item.thumbnail + '" width="100%"/>\
                        </div> \
                        <div class="strm-item-content">'
                            + getItemContent(item) +
                        '</div> \
                    </div>';
        
        var divElt = document.createElement('div');
        divElt.id = "strm-item-" + item.id;
        divElt.className = "strm-item strm-" + item.inputSource;
        divElt.innerHTML = html;
        this.element = divElt;
        this.elementId = item.id;
    }

    /**
     * The base class for the Habit Stream widget.
     * @param ops The options array. See above for basic usage guidelines. 
     */
    HabitStream.Widget = function(ops) {
        this.init(ops);
    };
    
    HabitStream.Widget.widgetCount = 0;
    HabitStream.Widget.isLoadingStyleSheet = false;
    HabitStream.Widget.hasLoadedStyleSheet = false;
    
    function loadStyleSheet(url, widgetElt) {
        if(! HabitStream.Widget.isLoadingStyleSheet) {
            HabitStream.Widget.isLoadingStyleSheet = true;
            
            // build our style link
            var linkElt = document.createElement('link');
            linkElt.href = url;
            linkElt.rel = 'stylesheet';
            linkElt.type = 'text/css';
            document.getElementsByTagName('head')[0].appendChild(linkElt);
            
            var timerId = setInterval(function() {
                var style = getStyle(widgetElt, 'position');
                if(style == 'relative') {
                    clearInterval(timerId);
                    HabitStream.Widget.hasLoadedStyleSheet = true;
                }
            }, 50);
        }
    }
    
    var isWindowLoaded = false;
    function setWidgetCss(cssText) {
        var styleElt = document.createElement('style');
        styleElt.type = 'text/css';
        
        if (browser.ie) {
            styleElt.styleSheet.cssText = cssText;
        } else {
            var styleFrag = document.createDocumentFragment();
            styleFrag.appendChild(document.createTextNode(cssText));
            styleElt.appendChild(styleFrag);
        }
        
        function appendStyle() {
            document.getElementsByTagName('head')[0].appendChild(styleElt);
        }

        if(! browser.ie || isWindowLoaded) {
            appendStyle();
        } else {
            window.attachEvent('onload', function() {
                appendStyle();
            });
        }
    }
    // check for window load, but just for IE
    if(browser.ie) {
        window.attachEvent('onload', function() {
            isWindowLoaded = true;
        });
    }
    
    var getStyle = function() {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        return function(elt, property) {
          var value = null;
          var computed = document.defaultView.getComputedStyle(elt, '');
          if (computed) {
            value = computed[property];
          }
          var ret = elt.style[property] || value;
          return ret;
        };
      }
      else if (document.documentElement.currentStyle) { // IE method
        return function(elt, property) {
          var value = elt.currentStyle ? elt.currentStyle[property] : null;
          return (elt.style[property] || value);
        };
      }
    }();
    
    HabitStream.Widget.prototype = function() {
        // private objects
        var _refreshInterval = 45000;
        var _defaultThumbnail = '';
        
        // our functions!
        return {
            
            /**
             * Initialize the widget. Sets all the settings as needed
             * @param ops The options object with all the non default settings required.
             */ 
            init : function(ops) {
                var that = this;
                this.ops = ops;
                
                // set our options
                this.setDimensions(ops.width, ops.height)
                this.theme = ops.theme ? ops.theme : this._getDefaultTheme();
                this.liveRefresh = ops.liveRefresh ? ops.liveRefresh : false;
                this.allowScroll = ops.allowScroll ? ops.allowScroll : false;
                this.widgetTitle = ops.title ? ops.title : false;
                this.widgetCaption = ops.caption ? ops.caption : false;
                this.widgetNumber = HabitStream.Widget.widgetCount ++;
                
                // our callback method from the api. 
                HabitStream.Widget['onContentLoaded_' + this.widgetNumber] = function(data) {
                    that._setBrandState(data);
                    that._injectNewElements(data);
                }
                this._callbackFunc = 'HabitStream.Widget.onContentLoaded_' + this.widgetNumber;
                this._isRendered = false;
                this._itemCount = 0;
                this.lastItemId = 0;
                this.itemResultIds = [];
                this.isRunning = false;
                
                if(ops.id) {
                    this.id = ops.id;
                } else {
                    this.id = "strm-widget-" + this.widgetNumber;
                    document.write('<div id="' + this.id + '" class="strm-widget"></div>');
                }
                
                this.widgetElt = getEltById(this.id);
                
                // Get our CSS loading...
                if(this.ops.skipCss) {
                    HabitStream.Widget.hasLoadedStyleSheet = true;
                } else if(! HabitStream.Widget.isLoadingStyleSheet) {
                    if(this.ops.customCssUrl) {
                        loadStyleSheet(this.ops.customCssUrl, this.widgetElt);
                    } else {
                        loadStyleSheet('http://cdn-live.habitindustries.com/stream/formats/widget_1_02.css', this.widgetElt);
                    }
                }
                return this;
            },
            
            /**
             * Sets the dimensions of the widget.
             * @param width The desired width. Minimum width is 160
             * @param height The desired height of the widget. Minumum height is 128
             */
            setDimensions : function(width, height) {
                this.wh = (width && height) ? [width, height] : [160, 128];
                if(width == 'auto' || width == '100%') {
                    this.wh[0] = '100%';
                } else {
                    this.wh[0] = this.wh[0] > 160 ? this.wh[0] : 160;
                }
                if(height == 'auto' || height == '100%') {
                    this.wh[1] = 'auto';
                } else {
                    this.wh[1] = this.wh[1] > 128 ? this.wh[1] : 128;
                }
                return this;
            },
            
            /**
             * Applies a theme objects preferences to the widget, overwritting as needed. 
             * @param theme The theme object. See the widget intro comments for all the possible flags
             */
            setTheme : function(themeOps) {
                var that = this;
                var defaultTheme = this._getDefaultTheme();
                
                this.theme = {
                    widget : {
                        background: themeOps.widget.background ? themeOps.widget.background : defaultTheme.widget.background,
                        color: themeOps.widget.color ? themeOps.widget.color : defaultTheme.widget.color, 
                        link : themeOps.widget.link ? themeOps.widget.link : defaultTheme.widget.link, 
                        linkhover : themeOps.widget.linkhover ? themeOps.widget.linkhover : defaultTheme.widget.linkhover, 
                        overflow: this.allowScroll ? 'scroll' : 'hidden'
                    }, 
                    item : {
                        background: themeOps.item.background ? themeOps.item.background : defaultTheme.item.background,
                        border: themeOps.item.border ? themeOps.item.border : defaultTheme.item.border,
                        color: themeOps.item.color ? themeOps.item.color : defaultTheme.item.color
                    }
                }
                
                var cssText =   '#' + this.id + ' a { color: ' + this.theme.widget.link + '; } \
                                 #' + this.id + ' a:hover { color: ' + this.theme.widget.linkhover + '; } \
                                 #' + this.id + ' .strm-header h2 { color: ' + this.theme.widget.color + '; } \
                                 #' + this.id + ' .strm-header h3 { color: ' + this.theme.widget.color + '; } \
                                 #' + this.id + ' .strm-footer { color: ' + this.theme.widget.color + '; } \
                                 #' + this.id + ' .strm-content { background: ' + this.theme.widget.background + '; } \
                                 #' + this.id + ' .strm-body { overflow-y: ' + this.theme.widget.overflow + '; background: ' + this.theme.item.background + '; color: ' + this.theme.item.color + '; } \
                                 #' + this.id + ' .strm-item { border-color: ' + this.theme.item.border + '; } \
                                 #' + this.id + ' .strm-item .strm-item-content p { color: ' + this.theme.item.color + '; } \
                                 #' + this.id + ' .strm-item .strm-item-content h4 { color: ' + this.theme.item.color + '; }';
                
                setWidgetCss(cssText);

                return this;
            },
            
            /**
             * @private
             * Generates the default theme for our widget. 
             */
            _getDefaultTheme : function() {
                return {
                    widget: {
                        background: "#cccccc",
                        color: "#000000",
                        link: "#0000ff",
                        linkhover: "#0000ff" },
                    item: {
                        background: "#ffffff",
                        border: "#cdcdcd",
                        color: "#000000" }
                };
            },
            
            /**
             * Renders the widget out onto the HTML pages. 
             */
            render : function() {
                var that = this;
                
                if(! HabitStream.Widget.hasLoadedStyleSheet) {
                    setTimeout(function() {
                        that.render();
                    }, 50);
                    return this;
                }
                
                if(! this.ops.customCssUrl && ! this.ops.skipCss) {
                    this.setTheme(this.theme);
                }
                this.widgetElt.innerHTML = this._getWidgetHtml();
                this._isRendered = true;

                return this; 
            },
            
            /**
             * Starts the widget running.
             */
            start : function() {
                if(this.isRunning) {
                    return this;
                }
                
                var that = this;
                if(! this._isRendered) {
                    setTimeout(function() {
                        that.start();
                    }, 50);
                    return this;
                }
                
                streamJs.init(this.ops.apiKey);
                
                this.isRunning = true;
                this._refreshWidget();

                return this;
            },
            
            /**
             * Stops the refresh cycle of the widget
             */
            stop : function() {
                this.isRunning = false;
                if(this._liveRefreshId) {
                    clearTimeout(this._liveRefreshId);
                }
                return this;
            },
            
            /**
             * @private
             * Starts the process up updating the widget with the latest conent
             * TEMP TODO :: Live refresh isn't actually grabbing the correct content, yet.
             */
            _refreshWidget : function() {
                var that = this;
                
                if(this.isRunning) {
                    // is this the first, or repeat call?
                    if(this.lastItemId == 0) {
                        streamJs.call('stream.stream.getContent', {'id' : this.ops.streamId
                            , 'broadcastId' : this.ops.bcastId
                            , 'count' : 10}
                            , that._callbackFunc + '(data)');
                    } else {
                        // TODO :: Use the after method when it's chrono listing get cleaned up.
                        streamJs.call('stream.stream.getContent', {'id' : this.ops.streamId
                            , 'broadcastId' : this.ops.bcastId
                            , 'count' : 2 }
                            , that._callbackFunc + '(data)');
                    }
                    
                    // cue up a refresh if we're doing that sort of thing
                    if(this.liveRefresh) {
                        this._liveRefreshId = setTimeout(function() {
                            that._refreshWidget();
                        }, _refreshInterval);
                    }
                }
            },
            
            /**
             * @private
             * Sets the the visual state of the habit branding based on server status
             * @param data The server state data object
             */
            _setBrandState : function(data) {
                if(data.branded == 1) {
                    var refItem = getEltByClass('strm-footer', 'div', false, getEltById(this.id));
                    refItem.style.display = "block";
                }
            },
            
            /**
             * @private
             * Parses the API response and does the work it needs to do to 
             * add all the elements into the widget
             * @param data The data object from the Stream API
             */
            _injectNewElements : function(data) {
                var that = this;
                
                // helper func to insure we are not duplicating data
                function isItemInjected(itemId) {
                    for(var i = 0, len = that.itemResultIds.length; i < len; i ++) {
                        if(that.itemResultIds[i] == itemId) {
                            return true;
                        }
                    }
                    return false;
                }
                
                var refItem = getEltByClass('strm-item-placeholder', 'div', false, getEltById(this.id));
                if(data.elements && data.count > 0) {
                    for(var i = data.count - 1; i >= 0; i --) {
                        
                        if(! isItemInjected(data.elements[i].id)) {
                            var item = new WidgetItem(data.elements[i]);
                            this._itemCount ++;
                            this._appendItem(item.element, refItem);
                            
                            this.itemResultIds[this.itemResultIds.length] = item.elementId;
                            if(item.elementId > this.lastItemId) {
                                this.lastItemId = item.elementId;
                            }
                        }
                    }
                }
            },
            
            /**
             * @private
             * Appends an item into the widget's DOM using the dictated behavior.
             * @param the DOMElement to add into the widget's item list
             */
            _appendItem : function(elt, appendToElt) {
                if(this._itemCount % 2 == 1) {
                    elt.className += " odd";
                }
                appendToElt.parentNode.insertBefore(elt, appendToElt.nextSibling);
            },
            
            /**
             * @private
             * Generates the basic html structure for our widget
             */
            _getWidgetHtml : function() {
                var that = this;
                function getHeader() { 
                    var head = '';
                    head += that.widgetTitle ? ('<h2>' + that.widgetTitle + '</h2>') : '';
                    head += that.widgetCaption ? ('<h3>' + that.widgetCaption + '</h3>') : '';
                    return head;
                }
                
                function getFooter() {
                    return '<span>Habit Stream powered</span><a href="http://www.habitstream.com" class="copy">Get your own!</a>'; //<a class="copy">Copy Widget</a>';
                }
                
                return  '<div class="strm-content" style="width: ' + this.wh[0] + 'px;"> \
                            <div class="strm-header"> \
                                ' + getHeader() + ' \
                            </div> \
                            <div class="strm-body" style="height: ' + this.wh[1] + 'px;"> \
                                <div class="strm-items"> \
                                    <div class="strm-item-placeholder"></div> \
                                </div> \
                            </div> \
                            <div class="strm-footer" style="display: none;"> \
                                ' + getFooter() + ' \
                            </div> \
                        </div>';
            }
        }
    }();
})();
// End Widget Enclosure