// This script contains the outliner object.

function LSOutliner(inKey, inOutlineId, inDiv, inClient)
{
var container_div;
var key = '';
var outline_id = '';
// var created_by = '';
// var created_on = '';
var title = '';
var body = new Object();
var max_id = 0;
var row_no = 0;
var selected_row;
var expanded_rows = new Array();		// this is used in SaveAsOPML
var row_counter = 0;					// used in SaveAsOPML / GenerateOPML

var file_ids = new Array();				// TEMPORARY: when we generate the outliner, we dump the files IDs in the outline in this array and return it to client.

var client;								// reference to the client to which this Outliner instance belongs. Used to call functions of client

var that = this;						// this is a workaround for a bug in the ECMAScript
										// Language Specification (cf: http://www.crockford.com/javascript/private.html)

	this.Init=function(key, outline_id, div, client)
	{
		if (typeof key === "undefined")
			key = '';
			
		this.max_id = 0;
		this.key = key;
		this.outline_id = outline_id;
		this.container_div = div;
//		this.created_by = "Arpan";
//		this.created_on = "12/12/2003";
		this.body = new Object();
		this.expanded_rows = new Array();
		this.selected_row = null;
		this.client = client;
	};
	
	this.SetOutlineKey=function(new_key)
	{
		this.key = new_key;
	};
	
	this.GetOutlineId=function()
	{
		return this.outline_id;
	};
	
	this.GetContainerDiv=function()
	{
		return this.container_div;
	};
	
	this.SelectRow=function(row)
	{
		this.selected_row = row;
	};
	
	this.DeselectRow=function()
	{
		if (this.selected_row != null)
			this.selected_row = null;
	};
	
	this.MouseOverRow=function(row_id)
	{
		sel_row = document.getElementById(row_id);
		AddClass(sel_row, "mouse-hover");
	};
	
	this.MouseOutRow=function(row_id)
	{
		sel_row = document.getElementById(row_id);
		RemoveClass(sel_row, "mouse-hover");
	};
	
	this.GetSelectedRow=function()
	{
		return this.selected_row;
	};
	
	// this function is called when we want to select previous row
	// if previous sibling has children, it returns the last child (recursively)
	// else returns previous sibling
	// if there is no previous sibling, it returns the parent row
	// if it is the top-most row(no prev sibling and parent is 'body'), it returns false
	this.FindPreviousRow=function(row)
	{
		if (!row)
			return false;
			
	var prev_sibling = that.GetPreviousSibling(row);
		
		if (prev_sibling)						// it has a previous sibling
		{
			if (!prev_sibling.hasChildren() || !prev_sibling.isExpanded())
				return prev_sibling;
			else
				return that.FindLastVisibleChild(prev_sibling);
		}
		else									// no previous sibling
		{
			var parent_row = row.getParent();
			if (parent_row == this.body)		// top-most row, return false
				return false;
			else return parent_row;				// return parent
		}
	};
	
	this.FindLastVisibleChild=function(row)
	{
		if (!row)
			return false;

		if (!row.hasChildren() || !row.isExpanded())
			return row;
		else
		{
		var last_child = that.GetLastChild(row);
			if (last_child)
				return that.FindLastVisibleChild(last_child);
		}
	};
	
	// this function is called when we want to select next row
	// if row has children and is expanded, return first child
	// else if next sibling exists, return it
	// else returns parent's next sibling (recursively)
	// if it is the bottom-most row(no next sibling and ancestor's dont have next siblings), it returns false
	this.FindNextRow=function(row)
	{
		if (!row)
			return false;
			
		if (row.hasChildren() && row.isExpanded())
		{
		
		var children = row.GetChildren();
			return children[0];
		}
			
	var next_sibling = that.GetNextSibling(row);
		
		if (next_sibling)						// it has a next sibling
			return next_sibling;
		else									// no previous sibling, look for ancestors' next sibling
		{
			var parent_row = row.getParent();
			while (parent_row != this.body)
			{
				if (that.GetNextSibling(parent_row))
					return that.GetNextSibling(parent_row);
					
				parent_row = parent_row.getParent();
			}
			return false;						// bottom-most row, return false
		}
	};
	
	// after loading the OPML, this function calls the server to get the files.
	// client.StoreFiles() is called on call back, and that function renders the Outline
	this.LoadOPML=function(response)
	{
	var opml_body = response.getElementsByTagName('body')[0];
	var expansion = response.getElementsByTagName('expansionState')[0].firstChild.data;

		title = response.getElementsByTagName('title')[0].firstChild.data;
		expansionState = (expansion != "") ? expansion.split(',') : new Array();

	var children = new Array();
	var n = opml_body.firstChild;
		
		while (n)
		{
			if (n.nodeType == 1)
				children[children.length] = n;
			n = n.nextSibling;
		}

		b = that.AddRow('body');
	
	var	parent_stack = new Array();
		parent_stack.push(b);

		that.file_ids = new Array();

		that.GenerateOutliner(parent_stack, children);
		
		that.client.ComputeAmountForAllRows();				// now that the outline data has been loaded,
															// we can compute the amount for all parent rows
		
		that.client.sync.CreateRequest('getFiles');			// creates asynchronous API call
		that.client.sync.CreateRequest('getInvites');		// creates asynchronous API call
	};
	
	this.GenerateOutliner=function(parent_stack, children)
	{
		for (var i = 0; i < children.length; i++)
		{
		var outline = that.AddRow('child', parent_stack[parent_stack.length-1]);
		var attrs = children[i].attributes;

			// dump attributes
			for(var a=0; a < attrs.length; a++)
	        	outline.setAttribute(attrs[a].name, NormaliseText(attrs[a].value, 'opml2attr'));

			// THIS IS A TEMPORARY FIX. ONCE WE STORE THE ROWS IN THE DB, WE CAN REMOVE THIS BLOCK
			var file_id = outline.getAttribute('_file_id');
			var exists = false;
			if (file_id)
			{
	        	for (var x = 0; x < that.file_ids.length; x++)
				{
					if (that.file_ids[x] == file_id)
					{
						exists = true;
						break;
					}
				}
				if (!exists)
					that.file_ids[that.file_ids.length] = file_id;
			}
			// TEMPORARY BLOCK ENDS HERE

	        if (expansionState.indexOf(outline.id.toString()) != -1)
	        	outline.expanded = true;

			if (children[i].hasChildNodes() && CountChildren(children[i]) != 0)					// row has children
			{
				parent_stack.push(outline);

				// create children array
				c = new Array();
				n = children[i].firstChild;
				while (n)
				{
					if (n.nodeType == 1)
						c[c.length] = n;
					n = n.nextSibling;
				}

				that.GenerateOutliner(parent_stack, c);
				p = parent_stack.pop();
			}
		}
	};
	
	// TEMPORARY FUNCTION. IT RETURNS this.files. ONCE WE STORE ROWS IN DB, WE WONT NEED THIS FUNCTION.
	this.GetFileIds=function()
	{
		return that.file_ids;
	};
	
	this.ConvertToOPML = function()
	{
	var children = new Array();
	this.expanded_rows = new Array();
	this.row_counter = 0;
	var opml = "";
	
		if (this.body.children)
		{
			children = this.body.children;
			opml = this.GenerateOPML(children);
		}
	
	var expansionState = (this.expanded_rows.length) ? this.expanded_rows.join() : 0;
		
	var code =  '<opml version="1.0">\r\n';
		code += '<head>\r\n';
		code += '<title>Untitled Document</title>\r\n';
		code += '<expansionState>' + expansionState + '</expansionState>\r\n';
		code += '</head>\r\n';
		code += '<body>\r\n';
		code += opml;
		code += '</body>\r\n';
		code += '</opml>\r\n';

		return code;
	};
	
	this.GetOPMLCallBack=function(t)
	{
	var response = t.responseXML.documentElement;
	var status = response.getElementsByTagName('status')[0].firstChild.data;
	var new_sids = response.getElementsByTagName('new_sids')[0].firstChild.data;

		new_rows = (new_sids != "") ? new_sids.split(',') : new Array();
		for (i = 0; i < new_rows.length; i++)
		{
			parts = new_rows[i].split(':');		// client_ID : server_ID
			
			curr_row = that.GetRowById(parts[0]);
			if (curr_row)
				curr_row.setAttribute('_sid', parts[1]);
		}
	};

	this.GenerateOPML=function(children)
	{
	var opml = "";
		for (var i = 0; i < children.length; i++)
		{
			this.row_counter++;
			row_attributes = children[i].getAttributesAsString();
			
			if (children[i].expanded)
				this.expanded_rows.push(this.row_counter);
				
			if (children[i].hasChildren())
			{
			//	opml += "<outline " + row_attributes + ">\r\n";
				opml += "<outline _cid=\"" + children[i].getId() + "\" " + row_attributes + ">\r\n";			// we need to send the client ID also
				opml += this.GenerateOPML(children[i].children);
				opml += "</outline>\r\n";
			}
			else
			{
				opml += "<outline _cid=\"" + children[i].getId() + "\" " + row_attributes + "/>\r\n";
			}
		}
		return opml;
	};
	
	this.CreateRow = function()
	{
	var	outline = new Outline(max_id);
		max_id++;
	
		return outline;
	};
	
	// position:	string ('before', 'after', child', 'first_child')
	//				determines where the new row should
	//				be created in reference to ref_row
	// ref_row:		id of the row near which we want to put the new rows
	this.AddRow = function(position, ref_row)
	{
	var parent;
	var	outline;
	
		if (position == 'body')
		{
			outline = new Outline('body');
			outline.setParent(null);		// parent of body is NULL
			this.body = outline;
			return outline;
		}
		
		outline = this.CreateRow();
	
		switch(position)
		{
		case 'before':	if (ref_row && ref_row.getParent())
							parent = ref_row.getParent();
						else break;
		
						for (var i = 0; i < parent.children.length; i++)
						{
							if (parent.children[i] == ref_row)
								break;
						}

						parent.children.splice(i, 0, outline);
						outline.setParent(parent);
						break;
						
		case 'after':	if (ref_row && ref_row.getParent())
							parent = ref_row.getParent();
						else break;
		
						for (var i = 0; i < parent.children.length; i++)
						{
							if (parent.children[i] == ref_row)
								break;
						}
						
						parent.children.splice(i+1, 0, outline);
						outline.setParent(parent);
						break;
						
		case 'child':	if (!ref_row)
							break;
							
						ref_row.children.push(outline);
						outline.setParent(ref_row);
						break;
						
	case 'first_child':	if (!ref_row)
							break;
							
						ref_row.children.splice(0, 0, outline);
						outline.setParent(ref_row);
						break;
		}
		return outline;
	};
	
	this.DeleteRow=function(row)
	{
	var parent_row = row.getParent();

		if (parent_row == null)			// deleting body node
		{
			delete this.body;
			this.body = new Object();
		}
		else
		{
		var children = parent_row.children;
			for (var i = 0; i < children.length; i++)
			{
				if (children[i].id == row.id)
				{
					children.splice(i, 1);
					break;
				}
			}
		}
		this.selected_row = null;
		return true;
	};
	
	this.DuplicateRow=function(row)
	{
		
	};
	
	this.IndentRow=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == null)
			return false;
			
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		
		if (i != 0)
		{
			new_parent = children[i-1];
			new_parent.children.push(row);
			row.setParent(new_parent);
			children.splice(i, 1);
			return true;
		}
		else return false;				// if this is the first child of a row
										// it can't be indented further
	};
	
	this.OutdentRow=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == this.body)
			return false;
			
		gparent = parent_row.getParent();
		gchildren = gparent.children;
		
		for (var i = 0; i < gchildren.length; i++)
		{
			if (gchildren[i] == parent_row)
				break;
		}
		
		gparent.children.splice(i+1, 0, row);
		row.setParent(gparent);
		
		// now delete the row from its old position
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		children.splice(i, 1);
	};
	
	this.MoveUp=function(row)
	{
	var parent_row = row.getParent();
		
		if (!row || parent_row == null)
			return false;

	var children = parent_row.children;
					
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		
		if (i != 0)			// if its the first child of a row, it can't be moved up
		{
			parent_row.children.splice(i-1, 0, children[i]);		// insert sel row @ i-1 pos
			parent_row.children.splice(i+1, 1);						// remove it from its old pos
		}
	};
	
	this.MoveDown=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == null)
			return false;
					
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}

		if (i != children.length-1)		// if its the last child of a row, it can't be moved down
		{
			parent_row.children.splice(i+2, 0, children[i]);		// insert sel row @ i+2 pos
			parent_row.children.splice(i, 1);						// remove it from its old pos
		}	
	};
	
	this.ExpandAll=function(row)
	{
		if (typeof row === "undefined")
			row = this.body;

		if (row.hasChildren())
		{
			row.expanded = true;
			
			for (var i = 0; i < row.children.length; i++)
				that.ExpandAll(row.children[i]);
		}		
	};

	this.CollapseAll=function(row)
	{
		if (typeof row === "undefined")
			row = this.body;

		if (row.hasChildren())
		{
			row.expanded = false;
			
			for (var i = 0; i < row.children.length; i++)
				that.CollapseAll(row.children[i]);
		}
	};

	this.ExpandRow=function(row)
	{
		if (row)
			row.expanded = true;
			
		return false;
	};
	
	this.CollapseRow=function(row)
	{
		if (row)
			row.expanded = false;
			
		return false;
	};
	
	this.RemoveAllColors=function()
	{
		if (this.body.hasChildren())
		{
			var children = this.body.children;
			for (var i = 0; i < children.length; i++)
			{
				children[i].setAttribute('_color', '');
				that.SetAttributeForChildren(children[i].GetChildren(), '_color', '');
			}
		}
	};
	
	this.GetRowById=function(id)
	{
		if (this.body.getId() == id)
			return this.body;
			
	var children = this.body.children;

		return this.FindIdInChildren(children, id);
	};
	
	// this function is called by GetRowById().
	// It recursively finds the row with the given id
	this.FindIdInChildren=function(children, id)
	{
	var row = "";
		for (var i = 0; i < children.length; i++)
		{
			if (children[i].getId() == id)
				row = children[i];
			else row = this.FindIdInChildren(children[i].children, id);
			
			if (row != "")		 // we have found the row, break out of the loop and return
				break;
		}
		return row;
	};
	
	this.GetNextSibling=function(row)
	{
		if (row == null)
			return false;
	
		var parent_row = row.getParent();
		var children = parent_row.children;
			
			if (!row || parent_row == null)
				return false;
						
			for (var i = 0; i < children.length; i++)
			{
				if (children[i] == row)
					break;
			}
			
			if (i == children.length-1)					// this is the last child	
				return false;
			else return children[i+1];
	};
	
	this.GetPreviousSibling=function(row)
	{
		if (row == null)
			return false;
			
		var parent_row = row.getParent();
		var children = parent_row.children;
			
			if (!row || parent_row == null)
				return false;
						
			for (var i = 0; i < children.length; i++)
			{
				if (children[i] == row)
					break;
			}
			
			if (i == 0)					// this is the first child	
				return false;
			else return children[i-1];
	};
	
	this.GetLastChild=function(row)
	{
		if (row == null)
			return false;
			
		if (row.hasChildren())
		{
		var children = row.GetChildren();
			return children[children.length-1];
		}
		else return false;
	}
		
	this.SetAttributeForChildren=function(rows, attr, value)
	{
		var children;
		for (var i = 0; i < rows.length; i++)
		{
			rows[i].setAttribute(attr, value);
			if (rows[i].hasChildren())
			{
				children = rows[i].GetChildren();
				that.SetAttributeForChildren(children, attr, value);
			}
		}
	};
	
	// new_row is only used while adding a row
	this.Render=function(row, action, new_row)
	{
		if (typeof row === 'undefined')
			this.RenderAll();
		else
		{
			var row_div = document.getElementById(row.id);
			if (row_div)
				var next_div = row_div.nextSibling;
			if (row_div)
				var prev_div = row_div.previousSibling;

//document.getElementById('scratch').innerHTML += '<br />before display row - ' + action;
			
			switch(action)
			{
			case 'expand':		if (!row.hasChildren())
									return false;
									
								var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
					
								if (next_div && next_div.className == 'children')		// if children were 'cached',
									next_div.style.display = 'block';					// show children
								else
								{
									children_div = document.createElement('div');
									this.DisplayChildren(row, parent_count-1, children_div);
									row_div.parentNode.insertBefore(children_div, row_div.nextSibling);
								}
								break;
							
			case 'collapse':	if (!row.hasChildren())
									return false;
								
								var parent_count = this.GetParentCount(row);	
								this.DisplayRow(row, parent_count-1, row_div);
				
								if (next_div && next_div.className == 'children')
									next_div.style.display = 'none';
								break;
								
		case 'delete_before':	var parent = row.getParent();					// this renders the deleted row
								row_div.parentNode.removeChild(row_div);
								if (next_div && next_div.className == 'children')
									next_div.parentNode.removeChild(next_div);
									
								// if parent row doesn't have children, knock off the 'children div'
								if (parent != null && parent.GetChildrenCount() == 1)
								{
									if (parent.id != 'body')			// if parent is body, we dont do anything
									{
										var parent_div = document.getElementById(parent.id);
										var children_div = parent_div.nextSibling;
										if (children_div.className == 'children')
											children_div.parentNode.removeChild(children_div);
									}
								}
								break;
						
		case 'delete_after':	if (row.id != 'body')							// the row has been deleted, check to see if
								{												// the parent row needs to be rendered again
									var parent_count = this.GetParentCount(row);
									this.DisplayRow(row, parent_count-1, row_div);
								}
								break;
								
		case 'add_first_child':	if (!new_row || new_row == null)
								{
									alert("something went wrong...");
									break;
								}
								
								var parent_count = this.GetParentCount(row);
								d = document.createElement('div');
								this.DisplayRow(new_row, parent_count, d);
								
								if (next_div && next_div.className == 'children')
									next_div.insertBefore(d,next_div.firstChild);
								else		// there is no chidren's div
								{
									var children = document.createElement('div');		// add childrens div first
									children.className = "children";
									
									children.appendChild(d);							// and then add new row
									row_div.parentNode.insertBefore(children, row_div.nextSibling);
								}
								break;
								
			case 'add_after':	if (next_div && next_div.className == 'children')		// selected row has children, insert after that
								{
									row_div = next_div;
									next_div = next_div.nextSibling;
								}
								var parent_count = this.GetParentCount(row);
								d = document.createElement('div');
								
								this.DisplayRow(new_row, parent_count-1, d);
								row_div.parentNode.insertBefore(d,row_div.nextSibling);
								break;
								
		case 'add_last_child':	// *** this is a special case, it appends new_row at the end of the document.
								var parent_count = this.GetParentCount(row);
								d = document.createElement('div');
								this.DisplayRow(new_row, parent_count-1, d);
								this.container_div.appendChild(d);
								break;
								
			case 'indent':		var parent_count = this.GetParentCount(row);
								var parent_row = row.getParent();
								var parent_div = document.getElementById(parent_row.id);
			
								if (prev_div && prev_div.className == 'children')		// previous row has children
								{
									// first delete the div that contained the row earlier
									parent_div.parentNode.removeChild(parent_div.nextSibling.nextSibling);
									
									if (parent_div.nextSibling.nextSibling && parent_div.nextSibling.nextSibling.className == 'children')
										parent_div.parentNode.removeChild(parent_div.nextSibling.nextSibling);
									
									// render row
									d = document.createElement('div');
									this.DisplayRow(row, parent_count-1, d);
									prev_div.appendChild(d);
									
									// render children
									if (row.hasChildren())
									{
										c = document.createElement('div');
										this.DisplayChildren(row, parent_count-1, c);
										prev_div.appendChild(c);
									}
								}
								else			// parent row doesn't have children, or its children
								{				// have not been rendered. We just need to expand it
									// first delete the div that contained the row earlier
									parent_div.parentNode.removeChild(parent_div.nextSibling);
									
									if (parent_div.nextSibling && parent_div.nextSibling.className == 'children')
										parent_div.parentNode.removeChild(parent_div.nextSibling);
								}
								
								// now expand the parent if it isn't expanded already
						//		that.client.ExpandRow(parent_row);
								break;
								
		case 'outdent_before':	var parent_count = this.GetParentCount(row);
								var parent_row = row.getParent();
								var parent_div = document.getElementById(parent_row.id);

								if (parent_row.id != 'body')
								{
									// render row
									d = document.createElement('div');
									this.DisplayRow(row, parent_count-2, d);
									parent_div.parentNode.insertBefore(d,parent_div.nextSibling.nextSibling);
									
									row_div.parentNode.removeChild(row_div);
									
									// render children
									if (next_div && next_div.className == 'children')
									{
										c = document.createElement('div');
										this.DisplayChildren(row, parent_count-2, c);
										parent_div.parentNode.insertBefore(c,parent_div.nextSibling.nextSibling.nextSibling);
										
										next_div.parentNode.removeChild(next_div);
									}
									
									// previous parent doesn't have any children any more
									if (!parent_div.nextSibling.firstChild)
										parent_div.parentNode.removeChild(parent_div.nextSibling);
								}
								break;
								
		case 'outdent_after':	var parent_count = this.GetParentCount(row);		// re-render previous parent since
								this.DisplayRow(row, parent_count-1, row_div);		// it may not have any children any more
								break;
								
			case 'move_up':		if (row_div.previousSibling)
								{
									if (row_div.previousSibling.className == 'children')
									{
										row_div.parentNode.insertBefore(row_div, row_div.previousSibling.previousSibling);
										if (next_div && next_div.className == 'children')
											row_div.parentNode.insertBefore(next_div, row_div.nextSibling);
									}
									else
									{
										row_div.parentNode.insertBefore(row_div, row_div.previousSibling);
										if (next_div && next_div.className == 'children')
											row_div.parentNode.insertBefore(next_div, row_div.nextSibling);
									}
								}
								// else we can't move up
								
								break;

			case 'move_down':	if (next_div && next_div.className != 'children')	// row doesn't contain children
								{
									// check if next row has children
									if (next_div.nextSibling && next_div.nextSibling.className == 'children')
										row_div.parentNode.insertBefore(row_div, next_div.nextSibling.nextSibling);
									else row_div.parentNode.insertBefore(row_div, next_div.nextSibling);
								}
								else if (next_div && next_div.nextSibling)		// row contains children and there is a row after that
								{
									// check if next row has children
									if (next_div.nextSibling.nextSibling && next_div.nextSibling.nextSibling.className == 'children')
										row_div.parentNode.insertBefore(row_div, next_div.nextSibling.nextSibling.nextSibling);
									else row_div.parentNode.insertBefore(row_div, next_div.nextSibling.nextSibling);
									
									// move children
									row_div.parentNode.insertBefore(next_div, row_div.nextSibling);
								
								}
								// else we can't move down
								
								break;

			case 'save_notes':
			case 'save_text':	var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								break;
								
	case 'save_text_multirow':	var parent_count = this.GetParentCount(row);
								var parent_row = row.getParent();
								var children_div = row_div.parentNode;

								this.DisplayChildren(parent_row, parent_count-2, children_div);
								break;
							
		case 'save_amount':	
		case 'toggle_status':	// re-render selected row
								var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div); 
								
								// re-render children
								if (next_div && next_div.className == 'children')
									this.DisplayChildren(row, parent_count-1, next_div);
								
								// re-render ancestors
								var ancestors = this.GetAncestors(row);
								var depth = parent_count-2;
								for (i = 0; i < ancestors.length; i++)
								{
									var a = document.getElementById(ancestors[i].id);
									this.DisplayRow(ancestors[i], depth, a);
									depth--;
								}
								
								break;
								
			case 'attachment':	var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								break;
								
			case 'assign_to':	var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								break;
								
			default:			alert("invalid action");
								break;
			}
		}
		
		this.PaintBackground();
	};
	
	// this function clears the container div
	// and loads the entire document
	this.RenderAll=function()
	{
		// clear the container div
		while(this.container_div.firstChild)
			this.container_div.removeChild(this.container_div.firstChild);

		this.row_no = 0;
		if (this.body.hasChildren())
		{
			for (var i = 0; i < this.body.children.length; i++)
			{
				that.RenderRow(this.body.children[i], this.container_div, 0);
			}
		}
	};
	
	this.RenderRow=function(row, container, depth)
	{
		var row_div = document.createElement('div');;

		this.DisplayRow(row, depth, row_div);

		container.appendChild(row_div);

		// render children
		if (row.hasChildren() && row.expanded == true)
		{
		var children = document.createElement('div');
			children.className = "children";
			
			for (var i = 0; i < row.children.length; i++)
				that.RenderRow(row.children[i], children, depth+1);	
			
			container.appendChild(children);
		}
	};
	
	// this function actually creates the div
	// that contains the elements that are displayed in a row
	// we should call this function RenderRow
	this.DisplayRow=function(row, depth, row_div)
	{
		// ROW-HANDLE
		var handle = document.createElement('img');
		handle.className = "row-handle";
		if (row.hasChildren())
		{
			if (row.expanded == true)
			{
				handle.src = "/images/expanded.gif";
				handle.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.CollapseRow(row); };
			}
			else
			{
				handle.src = "/images/collapsed.gif";
				handle.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ExpandRow(row); };
			}
		}
		else handle.src = "/images/leafrowhandle.gif";

		// TEXT
		var txt;
		if (that.client.GetUserAgent() == 'ipad')
		{
			txt = document.createElement('textarea');
			if (!that.client.IsInReadOnlyMode())
			{
				txt.contentEditable = "true";
				txt.onfocus = function() { that.client.EnterEditMode(); };
				txt.onblur = function() { that.client.SaveText(txt.value); that.client.LeaveEditMode(); };
			}
		}
		else
		{
			txt = document.createElement('div');
			if (!that.client.IsInReadOnlyMode())
			{
				txt.contentEditable = "true";
				txt.onfocus = function() { that.client.EnterEditMode(); that.client.AddUpDownKeyboardShortcuts('propagate'); };
				txt.onblur = function() { that.client.SaveText(txt.innerHTML); that.client.LeaveEditMode(); that.client.AddUpDownKeyboardShortcuts(''); };
			}
		}

		txt.className = 'row-text';
		txt.id = 'text'+row.id;
		if (row.getAttribute('text'))
			txt.innerHTML = NormaliseText(row.getAttribute('text'), 'html');			// Shashwat: changed to include NormaliseText
		else txt.innerHTML = "";
		
		if (row.getAttribute('_color'))
			txt.style.color = row.getAttribute('_color');
		else txt.style.color = "";
		
		if (row.getAttribute('_status') == 'checked')
			AddClass(txt, "strike");

		// NOTES
		var notes = document.createElement('div');
		notes.className = 'row-notes';
		if (row.getAttribute('_notes'))
		{
			notes.style.display = 'block';
			notes.innerHTML = NormaliseText(row.getAttribute('_notes'), 'html');			// Shashwat: changed to include NormaliseText
		}
		else notes.style.display = 'none';
		
		// DUE-DATE
		var duedate, row_duedate = row.getAttribute('_duedate');
		duedate = document.createElement('div');
		duedate.className = 'due-date';
		if (row_duedate != '')
		{	
			duedate.innerHTML = GetFriendlyDate(row_duedate);

			if (IsDatePassed(row_duedate))
				AddClass(duedate, 'due-date-overdue');
			
			ShowColumn('duedate');
		}
		else duedate.innerHTML = '';
		
		// AMOUNT
		var amount, row_amount = row.getAttribute('_amount');
		amount = document.createElement('div');
		amount.className = 'amount';
		if (row_amount != '' && row_amount != 0.00)
		{
			amount.innerHTML = FormatNumberWithCommas(row_amount);
			
			if (row.getAttribute('_color'))
				amount.style.color = row.getAttribute('_color');
			else amount.style.color = "";

			ShowColumn('amount');
		}
		else amount.innerHTML = '';
		
		// ATTACHMENT
		var attachment, attachment_img;
		attachment = document.createElement('div');
		attachment.className = 'attachment';
		if (row.getAttribute('_file_id')  != "" && that.client.GetFileById(row.getAttribute('_file_id')) != {})
		{
			attachment_img = document.createElement('img');
			attachment_img.className = 'attachment-clip';
			attachment_img.alt = "";
			attachment_img.height = "14";
			attachment_img.width = "14";
			attachment_img.border = "0";
			attachment_img.src = '/images/attachment.gif';
			attachment_img.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ShowFileBubbleAtRow(row.getAttribute('_file_id'), attachment); };
	
			attachment.appendChild(attachment_img);
			
			ShowColumn('attachment');
		}
		else attachment.innerHTML = '';
		
		// ASSIGN TO
		var assign_to, assign_to_img;
		assign_to = document.createElement('div');
		assign_to.className = 'assigned-to';
		if (row.getAttribute('_assigned_to')  != "")
		{
			assign_to_img = document.createElement('img');
			assign_to_img.className = 'assign-to-icn';
			assign_to_img.alt = "";
			assign_to_img.height = "14";
			assign_to_img.width = "14";
			assign_to_img.border = "0";
			assign_to_img.src = '/images/icn_assign_to.png';
			assign_to_img.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ShowAssignedToBubbleAtRow(row.getAttribute('_assigned_to'), assign_to); };
			
			assign_to.appendChild(assign_to_img);
			
			ShowColumn('assigned-to');
		}
		else assign_to.innerHTML = '';
		
		// build the wrappers
		var txt_wrapper = document.createElement('div');
			txt_wrapper.className = 'text-wrapper';
			txt_wrapper.style.paddingLeft = depth*15 + "px";			
			txt_wrapper.appendChild(handle);
			txt_wrapper.appendChild(txt);
			txt_wrapper.appendChild(notes);
			
		var amount_wrapper = document.createElement('div');
			amount_wrapper.className = 'amount-wrapper';
			amount_wrapper.appendChild(amount);
			amount_wrapper.appendChild(txt_wrapper);

		var duedate_wrapper = document.createElement('div');
			duedate_wrapper.className = 'duedate-wrapper';
			duedate_wrapper.appendChild(duedate);
			duedate_wrapper.appendChild(amount_wrapper);
			
		var assigned_to_wrapper = document.createElement('div');
			assigned_to_wrapper.className = 'assigned-to-wrapper';
			assigned_to_wrapper.appendChild(assign_to);
			assigned_to_wrapper.appendChild(duedate_wrapper);
			
		// first clear the div
		while(row_div && row_div.firstChild)
			row_div.removeChild(row_div.firstChild);
		
		// now rebuild the row
		row_div.id = row.id;
		row_div.className = "row";

		row_div.appendChild(attachment);
		row_div.appendChild(assigned_to_wrapper);
		row_div.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.SelectRow(row); };
		row_div.onmouseover = function() { that.MouseOverRow(row.id); };
		row_div.onmouseout = function() { that.MouseOutRow(row.id); };

		// check if this is the selected row
		if (this.selected_row && row.id == this.selected_row.id)
			AddClass(row_div, "selected");

		return row_div;
	};
	
	// this function renders the children of row into children_div
	this.DisplayChildren=function(row, depth, children_div)
	{
		// first clear the div
		while(children_div && children_div.firstChild)
			children_div.removeChild(children_div.firstChild);
	
		if ((row.hasChildren() && row.expanded == true) || row.getId() == 'body')	// row.expanded for BODY is false, thats why we need
		{																			// to check for the case where row is body separately
			children_div.className = "children";

			var d,c;
			for (var i = 0; i < row.children.length; i++)
			{
				d = document.createElement('div');
				this.DisplayRow(row.children[i], depth+1, d);
				children_div.appendChild(d);
				
				if (row.children[i].hasChildren() && row.children[i].expanded == true)
				{
					c = document.createElement('div');
					this.DisplayChildren(row.children[i], depth+1, c);
					children_div.appendChild(c);
				}
			}
		}
	}
	
	this.RenderSelectRow=function()
	{
		sel_row = document.getElementById(this.selected_row.id);
		RemoveClass(sel_row, "mouse-hover");
		AddClass(sel_row, "selected");
	};
	
	this.RenderDeselectRow=function()
	{
		if (this.selected_row != null)
		{
			sel_row = document.getElementById(this.selected_row.id);
			RemoveClass(sel_row, "selected");
			
			that.RemoveFocusFromTextInSelectedRow();
		}
	};
	
	this.RemoveFocusFromTextInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_text = document.getElementById('text'+this.selected_row.id);
			
			sel_text_status = sel_text.contentEditable;
			if (sel_text_status == 'true')
			{
				sel_text.contentEditable = 'false';
				sel_text.contentEditable = 'true';
			}
		}
	};
	
	this.DoBlurOnTextInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_text = document.getElementById('text'+this.selected_row.id);
			sel_text.blur();
		}
	}
	
	this.PaintBackground=function()
	{
		var n = this.container_div.firstChild;

		// check if children are exist. if not, don't call paintSiblings
		if (n)
			this.PaintSiblings(n, 0);
	};
	
	this.PaintSiblings=function(firstNode, counter)
	{
		var parent;

		n = firstNode;
		while(n)
		{
			if (n.style.display != 'none')
			{
				if (Element.hasClassName(n, 'children'))
				{
					n = n.firstChild;
					counter = this.PaintSiblings(n, counter);
				}
				else
				{
					if (counter % 2)
					{
						RemoveClass(n,'oddrow');
						AddClass(n,'evenrow');
					}
					else
					{
						RemoveClass(n,'evenrow');
						AddClass(n,'oddrow');
					}
					counter++;
				}
			}
			parent = n.parentNode;
			n = n.nextSibling;
		}
	
		n = parent;
	
		return counter;

	};
	
	// THIS FUNCTION SHOULD RETURN FALSE IF NOT FOUND, BUT IT ISN'T RETURNING ANYTHING RIGHT NOW
	// this returns true if child is a decendent of parent, false otherwise
	this.isDecendentOf=function(parent, child)
	{
		if (parent.hasChildren())
		{
			for (i = 0; i < parent.children.length; i++)
				return this.isDecendentOf(parent.children[i], child);
		}
		else
		{
			if (child == parent)
				return true;
		}
	};
	
	// this function returns the number of parents that this row has
	this.GetParentCount=function(row)
	{
		var parent = row.getParent();
		var count = 0;
		while (parent != null)
		{
			count++;
			parent = parent.getParent();
		}
		return count;
	};
	
	this.GetAncestors=function(row)
	{
		var ancestors = new Array();
		var parent = row.getParent();
		var count = 0;
		while (parent.id != 'body')
		{
			ancestors[ancestors.length] = parent;
			parent = parent.getParent();
		}
		return ancestors;
		
	};
	
	this.Init(inKey, inOutlineId, inDiv, inClient);
};

// Outline Class
function Outline(inId)
{
this.id = '';
this.attributes = new Object();
this.parent_row;
this.children = new Array();
this.expanded = false;

this.extras = new Object();			// temporary attributes that don't get stored in the OPML (ex: filename, file extension)

	this.getId = function()
	{
		return this.id;
	};

	this.init = function(id)
	{
		this.id = id;
	};
	
	this.isExpanded=function()
	{
		return this.expanded;
	}
	
	this.setParent = function(par_row)
	{
		this.parent_row = par_row;
	};
	
	this.getParent=function()
	{
		return this.parent_row;
	};

	this.setAttribute = function(_name, _value)
	{
		this.attributes[_name] = _value;
	};
	
	this.unsetAttribute = function(_name)
	{
		delete this.attributes[_name];
	};

	this.getAttribute = function(_name)
	{
		if (typeof this.attributes[_name] === "undefined")
			return '';
		else return this.attributes[_name];
	};

	this.getAttributesAsString = function()
	{
	var attrs = new Array();
	var attr = "";

		for (key in this.attributes)
		{
			if (this.attributes[key] || key == 'text')
				attrs[attrs.length] = key + "=\"" + this.attributes[key] + "\"";
		}

		return attrs.join(' ');
	};
	
	this.setExtra = function(_name, _value)
	{
		this.extras[_name] = _value;
	};

	this.getExtra = function(_name)
	{
		if (typeof this.extras[_name] === "undefined")
			return '';
		else return this.extras[_name];
	};
	
	this.hasChildren = function()
	{
		if (this.children.length > 0)
			return true;
		else return false;
	};
	
	
	this.GetChildren=function()
	{
		if (this.children.length > 0)
			return this.children;
		else return false;
	};
	
	this.GetChildrenCount=function()
	{
		return this.children.length;
	};
		
	this.init(inId);
};