/* 
 This file was generated by Dashcode.  
 You may edit this file to customize your widget or web page 
 according to the license.txt file included in the project.
 */

// Properties set by attributes panel
var numItemsToShow;         // Max number of items to display; -1 = all
var maxAgeToShow;           // Max age in days; 0 = today, -1 = all
var feed = { url: null, title: "", baseURL: null };  // Object to hold information about the current feed

var httpFeedRequest = null;     // The current XMLHttpRequest
var filterString = "";          // String to filter results while searching
var feedResults = null;         // The results of the XMLHttpRequest

// Define some namespaces commonly used in feeds
var NS_DC = "http://purl.org/dc/elements/1.1/";
var NS_CONTENT = "http://purl.org/rss/1.0/modules/content/";
var NS_XHTML = "http://www.w3.org/1999/xhtml";

//
// Function: refreshFeed()
// Starts loading the feed source.
// processFeedDocument() will be called when it finishes loading.
//
function fetchFeed()
{
    if (!feed.url || feed.url.length < 1) {
        showError("No RSS feed specified\nProvide a Feed URL in Application Attributes");
        return false;
    }

    // Abort any pending request before starting a new one
    if (httpFeedRequest != null) {
        httpFeedRequest.abort();
        httpFeedRequest = null;
    }
    httpFeedRequest = new XMLHttpRequest();

    // Function callback when feed is loaded
    httpFeedRequest.onload = function (e)
    {
        var feedRootElement;
        if (httpFeedRequest.responseXML) feedRootElement = httpFeedRequest.responseXML.documentElement;

        // Request is no longer pending
        httpFeedRequest = null;

        // Process the loaded document
        processFeedDocument(feedRootElement);
    }

    try{
        httpFeedRequest.overrideMimeType("text/xml");
        httpFeedRequest.open("GET", feed.url);
        httpFeedRequest.setRequestHeader("Cache-Control", "no-cache");

        // Send the request asynchronously
        httpFeedRequest.send(null);
    } catch (e) {
        if( e == "Error: Permission denied" ){
            showError("Feed not available.  The feed URL must be from the same source as your web application.");
        }else{
            showError("Feed not available.");
        }
        return false;
    }
}

//
// Function: processFeedDocument(doc)
// When the feed finishes loading, this function is called to parse it and display the results.
//
// doc: XML document containing the feed
//
function processFeedDocument(doc)
{
     if (doc) {
 
        // Determine the feed type and call the appropriate parser
        var results;
        if (doc.tagName.toLowerCase() == "feed") {
            results = parseAtomFeed(doc);
        }
        else {
            // It's probably some version of RSS.
            // We don't care as long as it has <item>s
            results = parseRSSFeed(doc);
        }

        // Got no results?
        if (results == null || results.length < 1) {
            showError("No articles found in feed");
            return;
        }

        // Limit entries to top N, search terms, and date
        results = filterEntries(results);

        // Completely filtered out?
        if (results == null || results.length < 1) {
            showError("No articles left after filtering");
            return;
        }
        
        feedResults = results;
        document.getElementById("headlineList").object.reloadData();
        document.getElementById("secondHeadlines").object.reloadData();
    }
    else {
        showError(feed.url + " does not appear to be a valid RSS or Atom feed");
    }
}

//
// Function: parseAtomFeed(atom)
// Parse an Atom feed.
//
// atom: Atom feed as an XML document.
//
// Returns the parsed results array.
//
function parseAtomFeed(atom)
{
    // Check for a global base URL
    var base = atom.getAttribute("xml:base");
    if (base) {
        feed.baseURL = splitURL(base);
    }

    var results = new Array;

    // For each element, get title, link and publication date.
    // Note that all elements of an item are optional.
    for (var item = atom.firstChild; item != null; item = item.nextSibling) {
        if (item.nodeName == "entry") {
            var title = atomTextToHTML(findChild(item, "title"));

            // we have to have the title to include the item in the list
            if (title) {
                // Just get the first link for now - Atom is complicated
                var link;
                var linkElement = findChild(item, "link");
                if (linkElement) {
                    link = linkElement.getAttribute("href");
                }

                // Try a few different ways to find a date
                var dateEl = findChild(item, "updated")
                    || findChild(item, "issued") 
                    || findChild(item, "modified")
                    || findChild(item, "created");
                var itemDate = parseDate(allData(dateEl));

                var description;
                var descElt = findChild(item, "content") || findChild(item, "summary");
                if (descElt) {
                    description = atomTextToHTML(descElt);
                }

                results[results.length] = {
                    title: title,
                    link: link,
                    date: new Date(itemDate),
                    description: description
                }
            }
        }
    }

    return results;
}

//
// Function: atomTextToHTML(element)
// Extracts the content of an atom text construct as HTML for display
//
// element: an Atom element containing an atomTextConstruct per RFC4287
//
// Returns an HTML div Element node containing the HTML
//
function atomTextToHTML(element)
{
    if (!element) {
        return;
    }

    var html;

    var type = element.getAttribute("type");
    if (type && (type.indexOf("xhtml") > -1)) {
        // The spec says there should be a DIV in the XHTML namespace
        var div = findChild(element, "div", NS_XHTML);
        if (div) {
            html = div.cloneNode(true);
        }
    }
    else if (type && (type.indexOf("html") > -1)) {
        // Encoded HTML
        html = document.createElement("div");
        html.innerHTML = allData(element);
    }
    else {
        // Plain text
        html = document.createElement("div");
        var elementText = allData(element);
        elementText = elementText.replace(/^\s+/, "");
        elementText = elementText.replace(/\s+$/, "");
        html.innerText = elementText;
    }

    return html;
}

//
// Function: parseRSSFeed(rss)
// Parse an RSS feed.
//
// rss: RSS feed as an XML document.
//
// Returns the parsed results array.
//
function parseRSSFeed(rss)
{
    var results = new Array;

    // Get global <link> element as base url
    var channel = findChild(rss, "channel");
    if (channel) {
        var mainLinkEl = findChild(channel, "link");
        if (mainLinkEl) {
            feed.baseURL = splitURL(allData(mainLinkEl));
        }
    }

    // Get all item elements.
    // For each element, get title, link and publication date.
    // Note that all elements of an item are optional.
    var items = rss.getElementsByTagName("item");
    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        if (item.nodeName == "item") {
            var title = findChild(item, "title");

            // we have to have the title to include the item in the list
            if (title != null) {
                // get the link
                var link = findChild(item, "link");
                // get the date
                var dateEl = findChild(item, "pubDate") || findChild(item, "date", NS_DC);
                var itemDate = parseDate(allData(dateEl));
                // get the description
                var description = findChild(item, "encoded", NS_CONTENT) || findChild(item, "description");
                var author = findChild(item, "encoded", NS_CONTENT) || findChild(item, "author");
                // save the result
                results[results.length] = {
                    title: allData(title),
                    link: allData(link),
                    date: new Date(itemDate),
                    description: allData(description),
                    author: allData(author)
                }
            }
        }
    }

    return results;
}

//
// Function: splitURL(url)
// Split components of the URL (protocol, domain, resource)
//
// url: URL to split
//
function splitURL(url)
{
    var baseURL = { protocol: "", domain: "", resource: "" };
    if (!url || url.length < 1) {
        return baseURL;
    }

    var components = url.split("://");
    baseURL.protocol = components[0];
    if (components.length > 1) {
        var slashIndex = components[1].indexOf("/");
        if (slashIndex >= 0) {
            baseURL.domain = components[1].substring(0, slashIndex);
            baseURL.resource = components[1].substring(slashIndex + 1, components[1].length);
        }
        else {
            baseURL.domain = components[1];
        }
    }
    
    return baseURL;
}

//
// Function: filterEntries(entries)
// Narrow down the RSS entries by configured date or maximum limits.
//
// entries: Array of entries to filter.
//
// Returns array of entries matching the filter(s).
//
function filterEntries(entries)
{
    var result = new Array();

    // Set initial cutoff to "today" (midnight)
    var cutoffDate = new Date();
    cutoffDate.setHours(0, 0, 0, 0);

    // Max age is in days; 0 = today; -1 = "any date"
    // Subtract 24-hour periods to generate a cutoff date
    if (maxAgeToShow > 0) {
        cutoffDate.setTime(cutoffDate.getTime() - maxAgeToShow * 24 * 60 * 60 * 1000);
    }

    var regExp = new RegExp(filterString, "i");
    for (var i = 0; i < entries.length; i++) {
        // Have we reached the limit of items to show (-1 = all)
        if (numItemsToShow > 1 && i >= numItemsToShow)
            break;

        var entry = entries[i];
        var entryDate = entry.date;
        if (entryDate == null) {
            // No date, pretend it's today
            entryDate = new Date();
        }
        // Ignore cutoff date if "any date" (-1) was chosen
        if (maxAgeToShow == -1 || entryDate >= cutoffDate) {
            // If searching, filter by search string
            if (filterString==""
                || (entry.title && entry.title.match(regExp))
                || (entry.description && entry.description.match(regExp))){
                result.push(entry);
            }
        }
    }

    return result;
}

//
// Function: parseDate(dateToParse)
// Parse a date string in several formats into a Date object
//
// dateToParse: String containing a date.
//
// Returns a Date object containing the parsed date.
//
function parseDate(dateToParse)
{
    var returnDate = null;
    if (!dateToParse || dateToParse.length < 1) {
        return null;
    }
    
    // try to parse as date string
    returnDate = new Date(dateToParse);

    // if no success, try other formats
    if ((!returnDate || isNaN(returnDate.valueOf()))) {
        var dateTimeSeparator = null;
        var monthIndex = null;
        var dayIndex = null;
        var yearIndex = null;
        // try ISO 8601 format (YYYY-MM-DDTHH:MM:SS+OO:OO)
        if (dateToParse.match(/^\d\d\d\d-\d\d-\d\d/)) {
            dateTimeSeparator = "T";
            monthIndex = 1;
            dayIndex = 2;
            yearIndex = 0;
        }
        // try other format (MM-DD-YYYY HH:MM:SS)
        else if (dateToParse.match(/^\d\d-\d\d-\d\d\d\d/)) {
            dateTimeSeparator = " ";
            monthIndex = 0;
            dayIndex = 1;
            yearIndex = 2;
        }
        
        // if the date format was recognized, parse it
        if (dateTimeSeparator) {
            returnDate = new Date();
            // separate date and time
            var dateTime=dateToParse.split(dateTimeSeparator);

            // set the date
            var dateArray = dateTime[0].split("-");
            if (dateArray[monthIndex]) returnDate.setMonth(dateArray[monthIndex]-1);
            if (dateArray[dayIndex]) returnDate.setDate(dateArray[dayIndex]);
            if (dateArray[yearIndex]) returnDate.setYear(dateArray[yearIndex]);

            // split time and offset
            var timeArray = null;
            if (dateTime[1]) timeArray = dateTime[1].match(/(\d\d):(\d\d):(\d\d)(?:\.\d+)?(?:([+-])(\d\d):(\d\d))?/);
            if (timeArray) {
                // set the time
                if (timeArray[1]) returnDate.setHours(timeArray[1]);
                if (timeArray[2]) returnDate.setMinutes(timeArray[2]);
                if (timeArray[3]) returnDate.setSeconds(timeArray[3]);

                // add the offset
                if (timeArray[4] && timeArray[5]) {
                    var time = returnDate.getTime() - returnDate.getTimezoneOffset() * 60000;
                    if (timeArray[4] == "+")
                        time -= timeArray[5] * 3600000;
                    else
                        time += timeArray[5] * 3600000;
                    returnDate.setTime(time);
                }
            }
        }
    }

    // if no success, return null
    if (returnDate && isNaN(returnDate.valueOf())) {
        returnDate = null;
    }

    return returnDate;
}

//
// Function: findChild(element, nodeName, namespace)
// Scans the children of a given DOM element for a node matching nodeName, optionally in a given namespace.
//
// element: The DOM element to search.
// nodeName: The node name to search for.
// namespace: Optional namespace the node name must be in.
//
// Returns the child node if found, otherwise null.
//
function findChild(element, nodeName, namespace)
{
    var child;

    for (child = element.firstChild; child != null; child = child.nextSibling) {
        if (child.localName == nodeName) {
            if (namespace == null || child.namespaceURI == namespace)
                return child;
        }
    }

    return null;
}

//
// Function: allData(node)
// Concatenate all the text data of a node's children.
//
// node: DOM element to search for text.
//
// Returns the concatenated text.
//
function allData(node)
{
    var data = "";
    if (node && node.firstChild) {
        node = node.firstChild;
        if (node.data) data += node.data;
        while (node = node.nextSibling) {
            if (node.data) data += node.data;
        }
    }

    return data;
}

//
// Function: absoluteURL(url)
// Convert a relative URL into an absolute one using the feed's base URL
//
// url: Relative URL to convert.
//
// Returns the absolute URL.
//
function absoluteURL(url)
{
    if (!feed.baseURL) {
        return url;
    }

    var baseURL = feed.baseURL.protocol + "://" + feed.baseURL.domain;
    // if it is absolute within the domain
    if (url.indexOf("/") == 0) url = baseURL + url;
    // if it is relative to the current resorce
    else url = baseURL + "/" + feed.baseURL.resource + url;
    return url;
}

//
// Function: createDateStr(date)
// Generate a date label from a JavaScript date.
//
// date: JavaScript date object
//
// Returns a string containing the short date.
//
function createDateStr(date)
{
    var month;
    switch (date.getMonth()) {
        case 0: month = "January"; break;
        case 1: month = "February"; break;
        case 2: month = "March"; break;
        case 3: month = "April"; break;
        case 4: month = "May"; break;
        case 5: month = "June"; break;
        case 6: month = "July"; break;
        case 7: month = "August"; break;
        case 8: month = "September"; break;
        case 9: month = "October"; break;
        case 10: month = "November"; break;
        case 11: month = "December"; break;
    }
    return month + " " + date.getDate() + ", " + date.getFullYear();
}

//
// Function: setFeedSource()
// Set the URL from where to get the feed.
//
// url: URL of the feed source
//
function setFeedSource(url) {
    if (url) {
        // make sure the url has an appropriate protocol
        url = url.replace(/^(feed:\/\/)/, "http://");
        if (url.indexOf("://") < 0) {
            url = "http://" + url;
        }
    }
    //feed.url = url;
    feed.url = url;
    feed.baseURL = splitURL(url);
}

//
// Function: load()
// Called by HTML body element's onload event when the web application is ready to start
//
function rssLoad()
{
    numItemsToShow = +attributes.numItemsToShow;
    maxAgeToShow   = +attributes.maxAgeToShow;
    
    setFeedSource(attributes.feedURL);
    fetchFeed();
}

//
// Function: extractText(content)
// Pulls text out of div elements
//
// content: string or element containing an entry
//
function extractText(content)
{
    var extractedString;

    if (typeof content == "string") {
        extractedString = content;
    }
    else {
        extractedString = content.innerText;
    }
    extractedString = extractedString.replace(/\n/g," ");
    extractedString = extractedString.replace(/<[^>]*>/g, "");
    
    return extractedString;
}

//
// Function: extractHTML(content)
// Pulls HTML out of div elements
//
// content: string or element containing an entry
//
function extractHTML(content)
{
    var extractedString;

    if (typeof content == "string") {
        extractedString = content;
    }
    else {
        extractedString = content.innerHTML;
    }

    return extractedString;
}

//
// Function: showError(errorString)
// Show an error in a red box
//
// errorString: string to be displayed
//
function showError(errorString)
{
    var errorDiv;
    errorDiv = document.createElement("div");
    errorDiv.innerText = errorString;
    errorDiv.setAttribute("style", "position: absolute; border-style: solid; border-width: 1px; right: 10px; left: 10px; top: 120px; background-color: rgb(32, 32, 32); border-color: rgb(0, 0, 0); -webkit-border-radius: 10px 10px; text-align: center; padding: 15px; font-family: Helvetica; font-weight: bold; color: rgb(255, 255, 255); font-size: 15px; text-shadow: rgb(0, 0, 0) 0px -1px 0px;");
    document.getElementById("content").appendChild(errorDiv);
}
