/**********************************************************************
* rssAjax.js
*
* Defines xmlTemplate.
* Defines rssTemplate (which uses xmlTemplate).
*
* Requires prototype.js (as modified by Brainstorm, Inc)
* Requires template.js (by TrimPath)
*
* Hiroki Chalfant
* (c) Copyright Brainstorm, Inc 2006
*/

/***********************************************************************************************************************
* XMLTemplate
*
* Requires prototype.js (as modified by Brainstorm, Inc)
* Requires template.js (by TrimPath)
*
* Always asynchronous.
*
* Hiroki Chalfant
* (c) Copyright Brainstorm, Inc 2006
*/

/***********************************************************************************************************************
* Usage:
*
* var xt = new XMLTemplate({
*       xml {
*           url        : 'http://engadget.com/rss.xml',
*           parameters : 'name=value&name=value',
*           remote     : true
*       }.
*
*       template {
*           url        : '/js/tmpls/rss.tmpl',
*           parameters : 'name=value&name=value',
*           remote     : false (default)
*       }.
*
*       onTotalLoadComplete: function (xmlRequest, templateObj) {}
*   });
*
* Always asynchronous.
*
*/
function XMLTemplate(o) {
    this.options = new Object();
    
    with (this) {
        if (o.xml) {
            options.xmlUrl = o.xml.url;
            options.xmlParameters = o.xml.parameters;
            options.xmlRemote = o.xml.remote;
        }
        
        if (o.template) {
            options.templateUrl = o.template.url;
            options.templateParameters = o.template.parameters;
            options.templateRemote = o.template.remote;
        }
    }
    
    if (o.onTotalLoadComplete)
        this.onTotalLoadComplete = o.onTotalLoadComplete;
    
    // get started straight away
    this.loadXML();
}

/***********************************************************************************************************************
* Properites
*/
XMLTemplate.prototype.remoteContentLoaderURL = '../get_url.pl';

/***********************************************************************************************************************
* loadStuff(params)
*
* Uses Ajax.Request object from prototype.js to load given parameters
*/
XMLTemplate.prototype.loadStuff = function (gUrl, gParameters, gRemote, gOnCompleteCallBack) {

    var url         = gUrl;
    var parameters  = gParameters ? gParameters : '';
    
    if (gRemote) {
        url = this.remoteContentLoaderURL;
        parameters = 'url=' + escape(gUrl + (parameters ? ('?' + parameters) : '' ));
    }
    
    // use prototype
    var ar = new Ajax.Request(url, {
        parameters : parameters,
        method : 'get',
        onComplete : gOnCompleteCallBack.bind(this)
    });
}

/***********************************************************************************************************************
* loadXML()
*
* XML Loader
*/
XMLTemplate.prototype.loadXML = function () {
    this.loadStuff(this.options.xmlUrl, this.options.xmlParameters, this.options.xmlRemote, this.onXMLComplete)
}

/***********************************************************************************************************************
* loadTemplate()
*
* Template Loader
*/
XMLTemplate.prototype.loadTemplate = function () {
    this.loadStuff(this.options.templateUrl, this.options.templateParameters, this.options.templateRemote, this.onTemplateComplete)
}

/***********************************************************************************************************************
* onXMLComplete()
*
* fired when xml is loaded (or not)
*/
XMLTemplate.prototype.onXMLComplete = function (gXMLRequestObj) {

    // save
    this.xmlRequestObject = gXMLRequestObj;
    
    // get template
    this.loadTemplate();
}

/***********************************************************************************************************************
* onTemplateComplete()
*
* fired when template is loaded (or not)
*/
XMLTemplate.prototype.onTemplateComplete = function (gXMLRequestObj) {

    if ((gXMLRequestObj.status == 0) || (gXMLRequestObj.status == 200) || (gXMLRequestObj.status == 304)) {
    
        // save
        this.templateRequestObject = gXMLRequestObj;
        
        var templateText = gXMLRequestObj.responseText;
        
        this.templateObj = TrimPath.parseTemplate(templateText);
    }
    
    // call last one
    this.onTotalLoadComplete(this.xmlRequestObject, this.templateObj);
}

/***********************************************************************************************************************
* onTotalLoadComplete()
*
* fired when everything is done;  overrwriten by constructor
*/
XMLTemplate.prototype.onTotalLoadComplete = function (gXMLRequestObj, templateObj) {
}

/***********************************************************************************************************************
* RSSTemplate
*
* Requires prototype.js (as modified by Brainstorm, Inc)
* Requires template.js (by TrimPath)
*
* Always asynchronous.
*
* Hiroki Chalfant
* (c) Copyright Brainstorm, Inc 2006
*/

/***********************************************************************************************************************
* Usage:
*
* var xt = new RSSTemplate({
*       xml {
*           url        : 'http://engadget.com/rss.xml',
*           parameters : 'name=value&name=value',
*           remote     : true
*       }.
*
*       template {
*           url        : '/js/tmpls/rss.tmpl',
*           parameters : 'name=value&name=value',
*           remote     : false (default)
*       }.
*
*       displayObjectId : 'rssContainerDiv',
*
*       templateVars : { varToPassToTemplate : varValue, anotherVarName : anotherValue },       // optional
*
*       onFailure: function (xmlRequest, templateObj) {}
*       onTotalLoadComplete: function (xmlRequest, templateObj) {}          // probably don't need
*   });
*
* Always asynchronous.
*
*/

function RSSTemplate(o) {
    this.options = new Object();
    
    this.options.xml                 = o.xml;
    this.options.template            = o.template;
    this.options.displayObjectId     = o.displayObjectId;
    this.options.onTotalLoadComplete = o.onTotalLoadComplete;
    
    // optional
    if (o.templateVars)
        this.options.templateVars = o.templateVars;
    else
        this.options.templateVars = new Object();
    
    // Events
    if (o.onTotalLoadComplete)
        this.onTotalLoadComplete = o.onTotalLoadComplete;
    
    if (o.onFailure)
        this.onFailure = o.onFailure;
    
    // need unique id for multi feeds using same template
    this.uniqueID = RSSTemplate.prototype.uniqueID = RSSTemplate.prototype.uniqueID + 1;
    
    // load!
    this.xmlTemplateLoader = new XMLTemplate({
        xml : o.xml,
        template : o.template,
        onTotalLoadComplete : this.templateLoaderFinished.bind(this)
    });
}

/***********************************************************************************************************************
* Properties
*/
RSSTemplate.prototype.uniqueID = 0;

/***********************************************************************************************************************
* templateLoaderFinished()
*
* fired when template/xml loader object is finished
*/
RSSTemplate.prototype.templateLoaderFinished = function (xmlRequestObject, template) {

    if (
        (xmlRequestObject.status && (xmlRequestObject.status != 200) && (xmlRequestObject.status != 304)) ||
        (!xmlRequestObject.responseXML) ||
        (!xmlRequestObject.responseXML.documentElement) ||
        (xmlRequestObject.responseXML.documentElement.nodeName == 'parsererror')
    ) {
        // signal failure
        this.onFailure(xmlRequestObject, template);
        return;
    }
    
    var xmlDoc = xmlRequestObject.responseXML.documentElement;
    var items = xmlDoc.getElementsByTagName('item');
    
    //
    // Channel
    //
    
    // convert to object
    var channelXml = xmlDoc.getElementsByTagName('channel')[0];
    var channelObj = new Object();
    
    if (channelXml.childNodes.length) {
        for (var ci = 0; ci < channelXml.childNodes.length; ci++) {
            if (channelXml.childNodes[ci].firstChild)
                channelObj[channelXml.childNodes[ci].nodeName] = channelXml.childNodes[ci].firstChild.nodeValue;
        }
    }
    
    //
    // Channel Image
    //
    var channelImageXML = channelXml.getElementsByTagName('image')[0];
    var channelImageObj = new Object();
    
    if (channelImageXML && channelImageXML.childNodes.length) {
        for (var ci = 0; ci < channelImageXML.childNodes.length; ci++) {
            if (channelImageXML.childNodes[ci].firstChild)
                channelImageObj[channelImageXML.childNodes[ci].nodeName] = channelImageXML.childNodes[ci].firstChild.nodeValue;
        }
        
        // it's part of the channel, make it so
        channelObj.image = channelImageObj;
    }
    
    //
    // Get items
    //
    var itemObjects = new Array();

    for (var i = 0; i < items.length; i++) {
        var itemNode = items[i];
        var item     = new Object();
        
        for (var ci = 0; ci < itemNode.childNodes.length; ci++) {
            var node = itemNode.childNodes[ci];
            
            if (node.firstChild) {
                if (!item[node.nodeName]) {
                    item[node.nodeName] = node.firstChild.nodeValue;
                }
                else {
                    if (!(item[node.nodeName] instanceof Array))
                        item[node.nodeName] = [ item[node.nodeName ]];
                    
                    item[node.nodeName].push(node.firstChild.nodeValue);
                }
            }
            else if (node.attributes) {
                var o = {};
                
                for (var j = 0; j < node.attributes.length; j++)
                    o[node.attributes[j].nodeName] = node.attributes[j].nodeValue;
                
                item[node.nodeName] = o;
            }
        }
        
        itemObjects.push(item);
    }
    
    // set to div's innerhtml
    if (this.options.displayObjectId) {
    
        var rssContainerObj = $(this.options.displayObjectId);
        
        // copy given template vars
        var template_vars = new Object();
        
        for (var v in this.options.templateVars)
            template_vars[v] = this.options.templateVars[v];
        
        // merge required template vars with given template vars (required vars override given vars)
        template_vars.uniqueID = this.uniqueID;
        template_vars.channel = channelObj;
        template_vars.items = itemObjects;
        
        if (rssContainerObj)
            rssContainerObj.innerHTML = template.process(template_vars);
    }
    
    // signal done
    this.onTotalLoadComplete(xmlRequestObject, template);
}

/***********************************************************************************************************************
* onFailure()
*
* fired when there is a failure
*/
RSSTemplate.prototype.onFailure = function (gXMLRequestObj, templateObj) {
}

/***********************************************************************************************************************
* onTotalLoadComplete()
*
* fired when everything is done;  overrwriten by constructor
*/
RSSTemplate.prototype.onTotalLoadComplete = function (gXMLRequestObj, templateObj) {
}


/***********************************************************************************************************************
* RSSAJAX
*
* Requires prototype.js (as modified by Brainstorm, Inc)
* Requires template.js (by TrimPath)
*
* Always asynchronous.
*
* loads a config file with rss feed and template pair configurations, then uses RSSTemplate to pair them together
*
* Hiroki Chalfant
* (c) Copyright Brainstorm, Inc 2006
*/

/***********************************************************************************************************************
* Usage:
*
* var xt = new RSSAJAX(url, {
*   onFailure  : function (xmlRequestObj), {} // optional
*   feed_parameters : {              // optional per feed parameters
*      containerID  : 'name=value;name=value',
*      containerID2 : 'name=value;name=value',
* });
*
* Always asynchronous.
*
* URL should point to an xml document (valid) as such:
*
*   <rssFeedOptions>
*       <feedOption containerID="rssFeed01">
*           <feed url="http://url.com/to/feed/rss.xml" parameters="name=value" remote="true" />
*           <template url="template.txt" remote="0" parameters="" />
*       </feedOption>
*       <feedOption containerID="anotherRSSContainer">
*           <feed url="anotherRSS.xml" />
*           <template url="anotherTemplate.txt" />
*       </feedOption>
*   </rssFeedOptions>
*
*
*/

function RSSAJAX(u, o) {
    this.url = u;
    
    o = o ? o : new Object();
    
    // Events
    if (o.onFailure)
        this.onFailure = o.onFailure;
    
    // extra parameters
    this.feed_parameters = {};
    
    if (o.feed_parameters) {
        for (var i in o.feed_parameters)
            this.feed_parameters[i] = o.feed_parameters[i];
    }
    
    // load
    this.load();
}

/***********************************************************************************************************************
* load()
*
* Load rss feed options file
*/
RSSAJAX.prototype.load = function() {
    // use prototype
    var ar = new Ajax.Request(this.url, {
        method : 'get',
        onComplete : this.loadComplete.bind(this)
    });
}

/***********************************************************************************************************************
* loadComplete()
*
* called when load is complete
*/
RSSAJAX.prototype.loadComplete = function(xmlRequestObject) {

    // failed?
    if (
        (xmlRequestObject.status && (xmlRequestObject.status != 200) && (xmlRequestObject.status != 304)) ||
        (!xmlRequestObject.responseXML) ||
        (!xmlRequestObject.responseXML.documentElement) ||
        (xmlRequestObject.responseXML.documentElement.nodeName == 'parsererror')
    ) {
        // signal failure
        throw(new Error('Some Error occured'));
        this.onFailure(xmlRequestObject);
        return;
    }
    
    // convert xml feed options to an array of objects
    var feedOptions = xmlRequestObject.responseXML.documentElement.getElementsByTagName('feedOption');
    
    var feedOptionObjects = new Array();
    
    // loop through feed options
    for (var i = 0; i < feedOptions.length; i++) {
        var fOption = feedOptions[i];
        
        // params
        var foParams = fOption.childNodes ? fOption.childNodes : [];
        
        // convert to object
        var options = {
            containerID : fOption.getAttribute('containerID'),
            attributes : new Object(),
            feed     : { url : '', parameters : '', remote : false },
            template : { url : '', parameters : '', remote : false }
        };
        
        for (var foai = 0; foai < fOption.attributes.length; foai++) {
            var attrObj = fOption.attributes[foai];
            options.attributes[attrObj.name] = attrObj.value;
        }
        
        // because safari (and the w3c say to) sets attributes' names to lowercase, 
        // set well known attributes in mixed case, for ease of use
        options.attributes.containerID = fOption.getAttribute('containerID');
        
        // loop through feed option parameters (feed and template, so far)
        for(var ci = 0; ci < foParams.length; ci++) {
            if ((foParams[ci].nodeName != '#text')) {
                var node_name = foParams[ci].nodeName;
                
                var hasRemoteAttribute = false;
                
                // go through attributes
                for (var ai = 0; ai < foParams[ci].attributes.length; ai++) {
                    var attrNode      = foParams[ci].attributes[ai];
                    var attributeName = attrNode.name;
                    
                    // flag if this is the result attribute
                    if (attributeName == 'remote')
                        hasRemoteAttribute = true;
                    
                    options[node_name][attributeName] = attrNode.value;
                }
                
                // convert remote value from other type to a bool, only if set explicitly
                if (hasRemoteAttribute) {
                    // empty to bool
                    if (!options[node_name].remote)
                        options[node_name].remote = false;
                    // yes|no to bool
                    else if (String(options[node_name].remote).match(/yes|no/i))
                        options[node_name].remote = String(options[node_name].remote).match(/no/i) ? false : true;
                    // "true|false" string to bool (really anything but "false" is true)
                    else if (String(options[node_name].remote).match(/false|true/i))
                        options[node_name].remote = String(options[node_name].remote).match(/false/i) ? false : true;
                    // number to bool
                    else if (parseInt(options[node_name].remote) != parseInt('this aint a number'))
                        options[node_name].remote = options[node_name].remote == 0 ? false : true;
                }
            }
        }
        
        // save for outside this loop
        feedOptionObjects.push(options);
    }
    
    // load rss feeds into RSSTemplate objects. and this object is done
    this.rssTemplateObjects = new Array();
    
    for (var i = 0; i < feedOptionObjects.length; i++) {
        // less typing
        var o = feedOptionObjects[i];
        
        // extra parameters?
        if (this.feed_parameters[o.containerID]) {
        
            // merge
            if (o.feed.parameters)
                o.feed.parameters += ';' + this.feed_parameters[o.containerID];
            else
                o.feed.parameters = this.feed_parameters[o.containerID];
        }
        
        // don't know why i'm saving it
        this.rssTemplateObjects.push(new RSSTemplate({
            xml : o.feed,
            template : o.template,
            displayObjectId : o.containerID,
            templateVars : {
                feedOption : o.attributes
            }
        }));
    }
}

/***********************************************************************************************************************
* onFailure()
*
* called if there's a failure
*/
RSSAJAX.prototype.onFailure = function(xmlRequestObj) {
}
