// The MIT License
// 
// Copyright (c) 2006 Jamie Pitts
// 
// 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.


// getCreator
// ============================================================================

function getCreator(creatorId) {
	var creator = global.creators[creatorId];
	
	// generate a new creator if it does not exist for this id
	if (creator == null) {
		creator = {"dateUpdated":"", "selectedCount":"0", "currentCount":"0", "avgCountPerDay":"0", "avgCountSamples":"0"};
		global.creators[creatorId] = creator;
	}
	
	return creator;
}

// getCreatorId
// ============================================================================
// returns a unique creator id given a channel and item

function getCreatorId (channel,item) {
	
	var creatorId;
	
	// set up the creator id
	if (item.creator != null && item.creator.length > 0) {
		creatorId = item.creator + '@' + channel.link;
	} else if (channel.webMaster != null && channel.webMaster.length > 0) {
		creatorId = channel.webMaster;
	} else {
		creatorId = 'webmaster@' + channel.link;
	}
	
	return creatorId;
	
} 

// getXHR
// ============================================================================
// returns an XMLHttpRequest object

function getXHR() {
	
    var req = false;

    // general XMLHttpRequest object
    if(window.XMLHttpRequest) {
    	try {
			req = new XMLHttpRequest();
        } catch(e) {
			req = false;
        }
        
    // IE/Windows ActiveX XMLHttpRequest object
    } else if(window.ActiveXObject) {
       	try {
        	req = new ActiveXObject("Msxml2.XMLHTTP");
      	} catch(e) {
        	try {
          		req = new ActiveXObject("Microsoft.XMLHTTP");
        	} catch(e) {
          		req = false;
        	}
		}
    }

    if (req.overrideMimeType) {
     	req.overrideMimeType('text/xml');
    }

	//if (req) {	
		return req;
	//} else {
	//	alert("Could not  an XHR");
	//}

}

// initialize
// ============================================================================

function initialize() {

	// niftycube loader
	Nifty("div#topPane,div#leftPane,div#centerPane");
	
	// set the globals hash with what is in the cookie
	// ----------------------------------------------------
	
	global = {};
	global.creators = {};
	global.username = getCookie('username');
	global.password = getCookie('password');
	
	if (getCookie('showAllSubscriptions') == 'true') {
		global.showAllSubscriptions = true;
	} else {
		global.showAllSubscriptions = false;
	}
	
	if (getCookie('markAllItemsRead') == 'true') {
		global.markAllItemsRead = true;
	} else {
		global.markAllItemsRead = false;
	}
	
	// if values are present, set the form values for convenient modification
	// ----------------------------------------------------
	
	if (global.username) {
		document.forms.credentialsForm.username.value = global.username;
	}
	if (global.password) {
		document.forms.credentialsForm.password.value = global.password;
	}
	if (global.showAllSubscriptions) {
		document.forms.credentialsForm.showAllSubscriptions.checked = true;
	}
	if (global.markAllItemsRead) {
		document.forms.credentialsForm.markAllItemsRead.checked = true;
	}
	
	// the user not yet dealt with the settings
	if (global.username == null || global.password == null) {
		toggleDisplay('settingsDiv');
	// list the entries	
	} else {
		listSubscriptions();
	}
	


}

// listItems
// ============================================================================
// retrieves items given a subscription id from the remote service, 
// converts the RSS to JSON, and renders the data to itemsDiv

function listItems(subscriptionId) {
	
	// write the waiting pane to the div 
	var divObj = document.getElementById('itemsDiv');
	divObj.innerHTML = renderWaitingPane();
	
	var req = getXHR();
	
    // the url is the perl proxy to the remote service
	var url;
	if (global.markAllItemsRead) {
	 	url = './ws.cgi?action=getitems&subscriptionId=' + subscriptionId + '&username='+ global.username + '&password=' + global.password + '&markAllItemsRead=true';
	} else {
	 	url = './ws.cgi?action=getitems&subscriptionId=' + subscriptionId + '&username='+ global.username + '&password=' + global.password;
	}
	
	var xml = '';
	var dat;
		
    req.open("GET",url,true);
	req.send("");
		
	// callback from within the XHR
    req.onreadystatechange=function() {
        if (req.readyState==4) {

			// perform further actions
        	if (req.status==200) { 
	
				// get the xml content from the response
				xml = req.responseText;
				
				if (xml.length > 0) {
					
					// convert the RSS into JSON
					dat = processRss(xml);

					// render the JSON 
					if (dat.error) {
						content = renderError(dat);
					} else {
						
						// winnow the content down to size
						winnowItems(dat);
						
						// render
						content = renderItems(dat);
						
					}
					
					// write to the div 
					divObj = document.getElementById('itemsDiv');
					divObj.innerHTML = content;

					//divObj = document.getElementById('itemsDiv');
					//divObj.innerHTML = '<form name="test"><textarea id="test" cols="50" rows="20"></textarea></form>';
					//document.forms.test.test.value = xml;

				}
				return true;
				
			} else if (req.status==404) {
				alert("No data returned from the GET request to " + url + ".");
		
      		} else {
          		alert("An error occurred during the GET request to " + url + ". Status: " + req.status + " Status Text: " + req.statusText);
      		}
  		}
	}
	
}

// listSubscriptions
// ============================================================================
// retrieves subscriptions from the remote service, converts the OPML to JSON,
// and renders the data to subscriptionsDiv

function listSubscriptions() {
	
	// write the waiting pane to the div 
	var divObj = document.getElementById('subscriptionsDiv');
	divObj.innerHTML = renderWaitingPane();
	
	var req = getXHR();

    // the url is the perl proxy to the remote service
	var url = './ws.cgi?action=listsubs&username='+ global.username + '&password=' + global.password;
	var xml = '';
	var dat;
		
    req.open("GET",url,true);
	req.send("");
		
	// callback from within the XHR
    req.onreadystatechange=function() {
        if (req.readyState==4) {

			// perform further actions
        	if (req.status==200) { 
	
				// get the xml content from the response
				xml = req.responseText;
				
				if (xml.length > 0) {
					
					// convert the OPML into JSON
					dat = processOpml(xml);
					
					// render the JSON 
					if (dat.error) {
						content = renderError(dat);
					} else {
						content = renderSubscriptions(dat);
					}
					
					// write to the div 
					divObj = document.getElementById('subscriptionsDiv');
					divObj.innerHTML = content;
					
				}
				return true;
				
			} else if (req.status==404) {
				alert("No data returned from the GET request to " + url + ".");
		
      		} else {
          		alert("An error occurred during the GET request to " + url + ". Status: " + req.status + " Status Text: " + req.statusText);
      		}
  		}
	}
	
}

// processError
// ============================================================================
// looks for the error element and stores it in the dat

function processError(node,dat) {
	if (node.nodeName == 'error') {
		if (node.firstChild != null && node.firstChild.nodeValue != '') {
			dat.error = node.firstChild.nodeValue;
		} else {
			dat.error = "The web proxy returned an undefined error.";
		}
	}
}


// processOpml
// ============================================================================
// processes opml xml into JSON

function processOpml(xml) {
	
	var dat = {};
	dat.items = [];
	
	// parse into a DOM document
	var doc = parseXML(xml);
	
	var node = doc.documentElement;
	
	// test for errors
	processError(node,dat);
	if (dat.error != null) {
		return dat;
	}
	
	for (var i=0;i<node.childNodes.length;i++) {
		
		//alert(node.childNodes[i].nodeName);
		
		if (node.childNodes[i].nodeName == 'head' && node.childNodes[i].nodeType == 1) {
			processOpmlHead(node.childNodes[i],dat);
			
		} else if (node.childNodes[i].nodeName == 'body' && node.childNodes[i].nodeType == 1) {
			processOpmlOutline(node.childNodes[i],dat,0);
			
		}
	
	}
	
	return dat;
	
}

// processOpmlHead
// ============================================================================

function processOpmlHead(node,dat) {
	
	// loop through the head child nodes
	for (var i=0;i<node.childNodes.length;i++) {
		
		if (node.childNodes[i].nodeName == 'title' && node.childNodes[i].nodeType == 1) {
			dat.title = node.childNodes[i].childNodes[0].nodeValue;
			
		} else if (node.childNodes[i].nodeName == 'dateCreated' && node.childNodes[i].nodeType == 1) {
			dat.dateCreated = node.childNodes[i].childNodes[0].nodeValue;
			
		} else if (node.childNodes[i].nodeName == 'ownerName' && node.childNodes[i].nodeType == 1) {
			dat.ownerName = node.childNodes[i].childNodes[0].nodeValue;
			
		}
		
	}
	
	return true;
}

// processOpmlOutline
// ============================================================================
// recursive function that appends dat.items as it walks down the OPML hier

function processOpmlOutline(node,dat,level) {
	var item;
	
	// loop through the child nodes
	for (var i=0;i<node.childNodes.length;i++) {
		//if (node.childNodes[i] == null) { continue; }
		
		// this is an outline item
		if (node.childNodes[i] != null && node.childNodes[i].nodeName == 'outline') {
			
			item = {};
			item.level = level;
			item.nodeType = node.childNodes[i].nodeType;
			
			item.title = node.childNodes[i].getAttribute("title");
			item.BloglinesSubId = node.childNodes[i].getAttribute("BloglinesSubId");
			item.BloglinesIgnore = node.childNodes[i].getAttribute("BloglinesIgnore");
			item.htmlUrl = node.childNodes[i].getAttribute("htmlUrl");
			item.xmlUrl = node.childNodes[i].getAttribute("xmlUrl");
			item.type = node.childNodes[i].getAttribute("type");
			item.BloglinesUnread = node.childNodes[i].getAttribute("BloglinesUnread");
			
			// add this item to the dat items
			dat.items.push(item);
			
			// delve a bit deeper to see if further outline items are to be found
			childNodes = node.childNodes[i].childNodes;
			for (var childi=0;childi<childNodes.length;childi++) {
				if (childNodes[childi] != null && childNodes[childi].nodeName == 'outline') {
					processOpmlOutline(node.childNodes[i],dat,level + 1);
					break;
				}
			}

		}
		
	}
	
	return true;
}

// processRss
// ============================================================================
// processes rss xml into JSON

function processRss(xml) {
	
	var dat = {};
	dat.items = [];
	
	// parse into a DOM document
	var doc = parseXML(xml);
	
	// test for errors
	processError(doc.documentElement,dat);
	if (dat.error != null) {
		return dat;
	}
	
	// get the main nodes
	var rootNode = doc.getElementsByTagName("rss")[0];
	dat.channels = [];
	var channelNodes = rootNode.getElementsByTagName("channel");
	var channel;
	var item;
	var itemNodes;
	
	// loop through the channels and add channel objects to the channels array
	for (var i=0;i<channelNodes.length;i++) {
		
		// channel
		channel = {};
	
		channel.title = channelNodes[i].getElementsByTagName("title")[0].firstChild.nodeValue;
		
		if (channelNodes[i].getElementsByTagName("link")[0] != null && channelNodes[i].getElementsByTagName("link")[0].firstChild != null) {
			channel.link = channelNodes[i].getElementsByTagName("link")[0].firstChild.nodeValue;
		}
		if (channelNodes[i].getElementsByTagName("description")[0] != null && channelNodes[i].getElementsByTagName("description")[0].firstChild != null) {
			channel.description = channelNodes[i].getElementsByTagName("description")[0].firstChild.nodeValue;
		}
		channel.language = channelNodes[i].getElementsByTagName("language")[0].firstChild.nodeValue;
		if (channelNodes[i].getElementsByTagName("webMaster")[0] != null && channelNodes[i].getElementsByTagName("webMaster")[0].firstChild != null) {
			channel.webMaster = channelNodes[i].getElementsByTagName("webMaster")[0].firstChild.nodeValue;
		}
		if (channelNodes[i].getElementsByTagName("bloglines:siteid")[0] != null && channelNodes[i].getElementsByTagName("bloglines:siteid")[0].firstChild != null) {
			channel.bloglines_siteid = channelNodes[i].getElementsByTagName("bloglines:siteid")[0].firstChild.nodeValue;
		}
	
		// items
		channel.items = [];
		itemNodes = channelNodes[i].getElementsByTagName("item");
		
		// loop through the item nodes and add item objects to the items array
		for (var its=0;its<itemNodes.length;its++) {
			
			item = {};

			// add in the item data
			item.title = itemNodes[its].getElementsByTagName("title")[0].firstChild.nodeValue;

			if (itemNodes[its].getElementsByTagName("creator")[0] != null && itemNodes[its].getElementsByTagName("creator")[0].firstChild != null) {
				item.creator = itemNodes[its].getElementsByTagName("creator")[0].firstChild.nodeValue;
			}
			if (itemNodes[its].getElementsByTagName("link")[0] != null && itemNodes[its].getElementsByTagName("link")[0].firstChild != null) {
				item.link = itemNodes[its].getElementsByTagName("link")[0].firstChild.nodeValue;
			}
			item.guid = itemNodes[its].getElementsByTagName("guid")[0].firstChild.nodeValue;
			item.guid_isPermaLink = itemNodes[its].getElementsByTagName("guid")[0].getAttribute("isPermaLink");
			item.description = itemNodes[its].getElementsByTagName("description")[0].firstChild.nodeValue;
			item.pubDate = itemNodes[its].getElementsByTagName("pubDate")[0].firstChild.nodeValue;

			if (itemNodes[its].getElementsByTagName("bloglines\:itemid")[0] != null && itemNodes[its].getElementsByTagName("bloglines\:itemid")[0].firstChild != null) {
				item.bloglines_itemid = itemNodes[its].getElementsByTagName("bloglines\:itemid")[0].firstChild.nodeValue;
			}

			// add this item to the channel items
			channel.items.push(item);

		}
		
		// add this channel to the dat
		dat.channels.push(channel);
		
	}
	
	return dat;
	
}


// parseXML
// ============================================================================

function parseXML(xmlText) {

	// code for IE
	if (window.ActiveXObject) {
		
	  	var doc=new ActiveXObject("Microsoft.XMLDOM");
	  	doc.async=false;
	  	doc.loadXML(xmlText);
	
	// code for Mozilla, Firefox, Opera, etc.
	} else {
		
	  	var parser=new DOMParser();
	  	var doc=parser.parseFromString(xmlText,"text/xml");

	}
	
	return doc;

}

// renderError
// ============================================================================

function renderError(dat) {
	
	// write to the div
	var content = '<span class="error">' + dat.error + '</span>';
	
	return content;
	
}


// renderItems
// ============================================================================

function renderItems(dat) {
	
	// write to the div
	var content = "";
	var channel;
	var item;
	
	if (dat.channels) {
		for (var i=0;i<dat.channels.length;i++) {
			channel = dat.channels[i];
			
			// channel header
			content += '<h2><a href="' + channel.link + '">' + channel.title + '</a></h2>';
			if (channel.webMaster != null) {
				content += channel.webMaster;
			}
			content += '<hr id="channelHeader" />'
			
			// channel items
			if (channel.items) {
				for (var its=0;its<channel.items.length;its++) {
					item = channel.items[its];
					
					if (! item.selected) {
						continue;
					}
					
					if (item.link != null) {
						content += '<h3><a href="' + item.link + '">' + item.title + '</a></h3>';
					}

					if (item.creator != null) {
						content += 'By ' + item.creator + '<br/>';
					}

					if (item.pubDate != null) {
						content += '<i>' + item.pubDate + '</i><br/>';
					}

					if (item.description != null && item.description.length > 0) {
						content += '<p>' + item.description + '</p>';
					}
					
					content += '<hr/>'

				}
			}

		}
	}
	
	return content;
	
}

// renderSubscriptions
// ============================================================================

function renderSubscriptions(dat) {
	
	// write to the div
	var content = "";
	var item;
	//content += '<h3>' + dat.title + '</h3>';
	//content += dat.title + '<br/>';;
	//content += dat.ownerName + '<br/>';
	
	var subscriptionId;
	if (dat.items) {
		for (var i=0;i<dat.items.length;i++) {
			item = dat.items[i];
			
			if (item.BloglinesSubId == null) {
				subscriptionId = 0;
			} else {
				subscriptionId = item.BloglinesSubId;
			} 
			
			// skip if this is an RSS feed and the user is not showing all subscriptions
			if (item.type != null && item.type == 'rss') {
				if (! global.showAllSubscriptions) {
					continue;
				}
			}
			
			// add spacing
			for (var spacei=0;spacei<item.level;spacei++) {content += '&nbsp;&nbsp;';}
			
			// subscription link
			content += '<a href="javascript:listItems(' + subscriptionId + ')">' + item.title + '</a>';
			if (item.BloglinesUnread != null && item.BloglinesUnread > 0) {
				 content += ' (' + item.BloglinesUnread + ')';
			}
			content += '<br/>';
		}
	}
	
	return content;
	
}

// renderWaitingPane
// ============================================================================

function renderWaitingPane() {
	
	// write to the div
	var content = '<div><img src="../images/waiting.gif"/></div>';

	return content;
	
}

// resetSettings
// ============================================================================

function resetSettings() {
	
	// reset the globals
	global = {};
	
	var date = new Date(); 
	
	// reset the form fields and the cookies
	
	setCookie('username','', date);
	document.forms.credentialsForm.username.value = '';
	
	setCookie('password','', date);
	document.forms.credentialsForm.password.value = '';
	
	setCookie('markAllItemsRead','', date);
	document.forms.credentialsForm.markAllItemsRead.checked = false;
	
	setCookie('showAllSubscriptions','', date);
	document.forms.credentialsForm.showAllSubscriptions.checked = false;
	
}

// setCredentials
// ============================================================================
// sets crediantials from the form into the session cookie

function setCredentials() {
	
	var date = new Date(); 
	date.setTime(date.getTime() + 7 *24*60*60*1000);

	setCookie('username', document.forms.credentialsForm.username.value, date);
	global.username = document.forms.credentialsForm.username.value;
	
	setCookie('password', document.forms.credentialsForm.password.value, date);
	global.password = document.forms.credentialsForm.password.value;
	
	// re-list the entries
	listSubscriptions();
	
}

// setMarkAllItemsRead
// ============================================================================

function setMarkAllItemsRead () {
	
	var date = new Date(); 
	date.setTime(date.getTime() + 7 *24*60*60*1000);
	
	// checked
	if (document.forms.credentialsForm.markAllItemsRead.checked) {
		global.markAllItemsRead = true;
		setCookie('markAllItemsRead','true', date);
	
	// not checked
	} else {
		global.markAllItemsRead = false;
		setCookie('markAllItemsRead','false', date);
	}
	
}

// setShowAllSubscriptions
// ============================================================================

function setShowAllSubscriptions () {
	
	var date = new Date(); 
	date.setTime(date.getTime() + 7 *24*60*60*1000);
	
	// checked
	if (document.forms.credentialsForm.showAllSubscriptions.checked) {
		global.showAllSubscriptions = true;
		setCookie('showAllSubscriptions','true', date);
	
	// not checked
	} else {
		global.showAllSubscriptions = false;
		setCookie('showAllSubscriptions','false', date);
	}
	
	// re-list the entries
	listSubscriptions();
	
}

// toggleDisplay
// ============================================================================

function toggleDisplay(id) {
	var element = document.getElementById(id);
	
	element.style.display = element.style.display? "":"block";
}

// unToggleDisplay
// ============================================================================

function unToggleDisplay(id) {
	var element = document.getElementById(id);
	element.style.display = "";
}

// winnowItems
// ============================================================================
// winnows down the dat.items into dat.winnowedItems

function winnowItems(dat) {
	
	var allow;
	var creatorId;
	var creator;
	var item;
	var channel;
	
	// analyze the channels and items
	// ----------------------------------------------------
	
	// loop through the channels
	if (dat.channels) {
		for (var i=0;i<dat.channels.length;i++) {
			channel = dat.channels[i];
			
			// loop through the items
			if (channel.items) {
				for (var its=0;its<channel.items.length;its++) {
					item = channel.items[its];
					
					// unique creator id
					creatorId = getCreatorId(channel,item);
					
					// fetch the creator
					creator = getCreator(creatorId);
					
					// increment the current item count for this creator
					creator.currentCount++;
				
				}
			}
		}
	}
	
	// randomly choose which items make the cut per creator
	// ----------------------------------------------------
	
	var randNo;
	var randInt;
	
	// loop through the channels
	if (dat.channels) {
		for (var i=0;i<dat.channels.length;i++) {
			channel = dat.channels[i];
			
			// loop through the items
			if (channel.items) {
				for (var its=0;its<channel.items.length;its++) {
					item = channel.items[its];
					
					// unique creator id
					creatorId = getCreatorId(channel,item);
					
					// determine if a creator record exists
					creator = getCreator(creatorId);
					
					// an item by this creator has already been selected
					if (creator.selectedCount > 0) {
						continue;
					}
					
					// randomly choose if this item gets selected
					randNo = Math.random() * (creator.currentCount + 1);
					randInt = Math.ceil(randNo);
					
					// choose the last if none from this channel were chosen
					if (creator.selectedCount == 0 && its == (channel.items.length - 1)) {
						item.selected = true;
						creator.selectedCount++;
					// two chances to see a story
					} else if (randInt == 1 || randInt == creator.currentCount) {
						item.selected = true;
						creator.selectedCount++;
					} else {
						item.selected = false;
					}
					
				}
			}
		}
	}
	
	
	return dat;
	
}


