/*
	Zotero
	Copyright (C) 2006   Center for History and New Media, George Mason University, Fairfax, VA
	http://chnm.gmu.edu/
*/

////////////////////////////////////////////////////////////////////////////////
///
///  ItemTreeView
///    -- handles the link between an individual tree and the data layer
///    -- displays only items (no collections, no hierarchy)
///
////////////////////////////////////////////////////////////////////////////////

/*
 *  Constructor the the ItemTreeView object
 */
Scholar.ItemTreeView = function(itemGroup, sourcesOnly)
{
	this._itemGroup = itemGroup;
	this._sourcesOnly = sourcesOnly;
	
	this._treebox = null;
	this.refresh();
	
	this._unregisterID = Scholar.Notifier.registerItemTree(this);
}

/*
 *  Called by the tree itself
 */
Scholar.ItemTreeView.prototype.setTree = function(treebox)
{
	if(this._treebox)
		return;
	this._treebox = treebox;
	
	if(!this.isSorted())
	{
		this.cycleHeader(this._treebox.columns.getNamedColumn('firstCreator'));
	}
	else
	{
		this.sort();
	}
}

/*
 *  Reload the rows from the data access methods
 *  (doesn't call the tree.invalidate methods, etc.)
 */
Scholar.ItemTreeView.prototype.refresh = function()
{
	this._dataItems = new Array();
	this.rowCount = 0;
	
	var newRows = this._itemGroup.getChildItems();
	if (newRows.length)
	{
		for(var i = 0, len = newRows.length; i < len; i++)
		{
			if(newRows[i] &&
			  (!this._sourcesOnly || (!newRows[i].isAttachment() && !newRows[i].isNote())))
			{
				this._showItem(new Scholar.ItemTreeView.TreeRow(newRows[i],0,false), i+1); //item ref, before row
			}
		}
	}
	
	this._refreshHashMap();
}

/*
 *  Called by Scholar.Notifier on any changes to items in the data layer
 */
Scholar.ItemTreeView.prototype.notify = function(action, type, ids)
{
	var madeChanges = false;
	this.selection.selectEventsSuppressed = true;
	var savedSelection = this.saveSelection();
	
	ids = Scholar.flattenArguments(ids);
	
	// See if we're in the active window
	var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
		.getService(Components.interfaces.nsIWindowMediator);
	if (wm.getMostRecentWindow("navigator:browser") ==
			this._treebox.treeBody.ownerDocument.defaultView){
		var activeWindow = true;
	}
	
	var quicksearch = this._treebox.treeBody.ownerDocument.getElementById('tb-search');
	
	if((action == 'remove' && !this._itemGroup.isLibrary()) || (action == 'delete' && this._itemGroup.isLibrary()))
	{
		//Since a remove involves shifting of rows, we have to do it in order
		
		//sort the ids by row
		var rows = new Array();
		for(var i=0, len=ids.length; i<len; i++)
			if(action == 'delete' || !this._itemGroup.ref.hasItem(ids[i]))
				rows.push(this._itemRowMap[ids[i]]);
		
		if(rows.length > 0)
		{
			rows.sort(function(a,b) { return a-b });
			
			for(var i=0, len=rows.length; i<len; i++)
			{
				var row = rows[i];
				if(row != null)
				{
					this._hideItem(row-i);
					this._treebox.rowCountChanged(row-i,-1);
				}
			}
			
			madeChanges = true;
		}		
		
	}
	else if(action == 'modify') 	//must check for null because it could legitimately be 0
	{
		// If no quicksearch, process modifications manually
		if (quicksearch.value == '')
		{
			for(var i=0, len=ids.length; i<len; i++)
			{
				var row = this._itemRowMap[ids[i]];
				if( row != null)
				{
					if(this.isContainer(row) && this.isContainerOpen(row))
					{
						this.toggleOpenState(row);
						this.toggleOpenState(row);
					}
					else if(this.getParentIndex(row))
					{
						
					}
					else
					{
						this._treebox.invalidateRow(row);
					}
					madeChanges = true;
				}
				else if(this._itemGroup.isLibrary() || this._itemGroup.ref.hasItem(ids[i]))
				{
					var item = Scholar.Items.get(ids[i]);
					
					if(!item.getSource())
					{
						//most likely, the note or attachment's parent was removed.
						this._showItem(new Scholar.ItemTreeView.TreeRow(item,0,false),this.rowCount);
						this._treebox.rowCountChanged(this.rowCount-1,1);
						madeChanges = true;
					}
				}
			}
		}
		
		// If quicksearch, re-run it, since the results may have changed
		else
		{
			quicksearch.doCommand();
			madeChanges = true;
		}
	}
	else if(action == 'add')
	{
		// If no quicksearch, process new items manually
		if (quicksearch.value == '')
		{
			var items = Scholar.Items.get(ids);
			
			for (var i in items)
			{
				if((this._itemGroup.isLibrary() || items[i].inCollection(this._itemGroup.ref.getID()))	// if the item belongs in this collection
					&& this._itemRowMap[items[i].getID()] == null											// if we haven't already added it to our hash map
					&& (items[i].isRegularItem() || !items[i].getSource()))								// if it's stand-alone
				{
					this._showItem(new Scholar.ItemTreeView.TreeRow(items[i],0,false),this.rowCount);
					this._treebox.rowCountChanged(this.rowCount-1,1);
			
					madeChanges = true;
				}
			}
		}
		// Otherwise rerun the search, which refreshes the item list
		else
		{
			// If active window, clear search first
			if (activeWindow)
			{
				quicksearch.value = '';
			}
			quicksearch.doCommand();
			madeChanges = true;
		}
	}
	
	if(madeChanges)
	{
		if(this.isSorted())
		{
			this.sort();				//this also refreshes the hash map
			this._treebox.invalidate();
		}
		else if(action != 'modify') //no need to update this if we modified
		{
			this._refreshHashMap();
		}
		
		// If adding and this is the active window, select the item
		if(action == 'add' && ids.length===1 && activeWindow)
		{
			// Reset to Info tab
			this._treebox.treeBody.ownerDocument.
			getElementById('scholar-view-tabs').selectedIndex = 0;
			
			this.selectItem(ids[0]);
		}
		else
		{
			this.rememberSelection(savedSelection);
		}
	}
	this.selection.selectEventsSuppressed = false;
}

/*
 *  Unregisters view from Scholar.Notifier (called on window close)
 */
Scholar.ItemTreeView.prototype.unregister = function()
{
	Scholar.Notifier.unregisterItemTree(this._unregisterID);
}

////////////////////////////////////////////////////////////////////////////////
///
///  nsITreeView functions
///  http://www.xulplanet.com/references/xpcomref/ifaces/nsITreeView.html
///
////////////////////////////////////////////////////////////////////////////////

Scholar.ItemTreeView.prototype.getCellText = function(row, column)
{
	var obj = this._getItemAtRow(row);
	var val;
	
	if(column.id == "numChildren")
	{
		var c = obj.numChildren();
		if(c)	//don't display '0'
			val = c;
	}
	else if(column.id == "typeIcon")
	{
		val = Scholar.getString('itemTypes.'+Scholar.ItemTypes.getName(obj.getType()));
	}
	else
	{
		val = obj.getField(column.id);
	}
	
	if(column.id == 'dateAdded' || column.id == 'dateModified')		//this is not so much that we will use this format for date, but a simple template for later revisions.
	{
		val = new Date(Date.parse(val.replace(/-/g,"/"))).toLocaleString();
	}
	
	return val;
}

Scholar.ItemTreeView.prototype.getImageSrc = function(row, col)
{
	if(col.id == 'title')
	{
		var item = this._getItemAtRow(row);
		var itemType = Scholar.ItemTypes.getName(item.getType());
		if(itemType == 'attachment')
		{
			var linkMode = item.ref.getAttachmentLinkMode();
			if(linkMode == Scholar.Attachments.LINK_MODE_IMPORTED_FILE)
			{
				itemType = itemType + "-file";
			}
			else if(linkMode == Scholar.Attachments.LINK_MODE_LINKED_FILE)
			{
				itemType = itemType + "-link";
			}
			else if(linkMode == Scholar.Attachments.LINK_MODE_IMPORTED_URL)
			{
				itemType = itemType + "-snapshot";
			}
			else if(linkMode == Scholar.Attachments.LINK_MODE_LINKED_URL)
			{
				itemType = itemType + "-web-link";
			}
		}
		
		return "chrome://scholar/skin/treeitem-"+itemType+".png";
	}
}

Scholar.ItemTreeView.prototype.isContainer = function(row)
{
	return this._getItemAtRow(row).isRegularItem();
}

Scholar.ItemTreeView.prototype.isContainerOpen = function(row)
{
	return this._dataItems[row].isOpen;
}

Scholar.ItemTreeView.prototype.isContainerEmpty = function(row)
{
	if(this._sourcesOnly) {
		return true;
	} else {
		return (this._getItemAtRow(row).numNotes() == 0 && this._getItemAtRow(row).numAttachments() == 0);
	}
}

Scholar.ItemTreeView.prototype.getLevel = function(row)
{
	return this._getItemAtRow(row).level;
}

Scholar.ItemTreeView.prototype.getParentIndex = function(row)
{
	var thisLevel = this.getLevel(row);
	if(thisLevel == 0) return -1;
	for(var i = row - 1; i >= 0; i--)
		if(this.getLevel(i) < thisLevel)
			return i;
	return -1;
}

Scholar.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex)
{
	var thisLevel = this.getLevel(row);
	for(var i = afterIndex + 1; i < this.rowCount; i++)
	{	
		var nextLevel = this.getLevel(i);
		if(nextLevel == thisLevel) return true;
		else if(nextLevel < thisLevel) return false;
	}
}

Scholar.ItemTreeView.prototype.toggleOpenState = function(row)
{
	var count = 0;		//used to tell the tree how many rows were added/removed
	var thisLevel = this.getLevel(row);
	
	if(this.isContainerOpen(row))
	{
		while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel))
		{
			this._hideItem(row+1);
			count--;	//count is negative when closing a container because we are removing rows
		}
	}
	else
	{
		var item = this._getItemAtRow(row).ref;
		//Get children
		var attachments = item.getAttachments();
		var notes = item.getNotes();
		
		var newRows;
		if(attachments && notes)
			newRows = attachments.concat(notes);
		else if(attachments)
			newRows = attachments;
		else if(notes)
			newRows = notes;
		
		newRows = Scholar.Items.get(newRows); 
		
		for(var i = 0; i < newRows.length; i++)
		{
			count++;
			this._showItem(new Scholar.ItemTreeView.TreeRow(newRows[i],thisLevel+1,false), row+i+1); //item ref, before row
		}
	}
	
	this._treebox.beginUpdateBatch();
	
	this._dataItems[row].isOpen = !this._dataItems[row].isOpen;
	this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
	this._treebox.invalidateRow(row);
	this._treebox.endUpdateBatch();
	this._refreshHashMap();
}

Scholar.ItemTreeView.prototype.isSorted = function()
{
	for(var i=0, len=this._treebox.columns.count; i<len; i++)
		if(this._treebox.columns.getColumnAt(i).element.getAttribute('sortActive'))
			return true;
	return false;
}

Scholar.ItemTreeView.prototype.cycleHeader = function(column)
{
	for(var i=0, len=this._treebox.columns.count; i<len; i++)
	{
		col = this._treebox.columns.getColumnAt(i);
		if(column != col)
		{
			col.element.removeAttribute('sortActive');
			col.element.removeAttribute('sortDirection');
		}
		else
		{
			col.element.setAttribute('sortActive',true);
			col.element.setAttribute('sortDirection',col.element.getAttribute('sortDirection') == 'descending' ? 'ascending' : 'descending');
		}
	}
	
	this.selection.selectEventsSuppressed = true;
	var savedSelection = this.saveSelection();
	this.sort();
	this.rememberSelection(savedSelection);
	this.selection.selectEventsSuppressed = false;
	this._treebox.invalidate();
}

/*
 *  Sort the items by the currently sorted column.
 *  Simply uses Array.sort() function, and refreshes the hash map.
 */
Scholar.ItemTreeView.prototype.sort = function()
{
	var column = this._treebox.columns.getSortedColumn()
	var order = column.element.getAttribute('sortDirection') == 'descending';
	
	if(column.id == 'typeIcon')
	{
		function columnSort(a,b)
		{
			var typeA = Scholar.getString('itemTypes.'+Scholar.ItemTypes.getName(a.getType()));
			var typeB = Scholar.getString('itemTypes.'+Scholar.ItemTypes.getName(b.getType()));
			
			return (typeA > typeB) ? -1 : (typeA < typeB) ? 1 : 0;
		}
	}
	else if(column.id == 'numNotes')
	{
		function columnSort(a,b)
		{
			return b.numNotes() - a.numNotes();
		}
	}
	else
	{
		function columnSort(a,b)
		{
			var fieldA = a.getField(column.id);
			var fieldB = b.getField(column.id);
			
			if(typeof fieldA == 'string')
			{
				fieldA = fieldA.toLowerCase();
				fieldB = fieldB.toLowerCase();
			}
			
			return (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
		}
	}
	
	function doSort(a,b)
	{
		var s = columnSort(a,b);
		if(s)
			return s;
		else
			return secondarySort(a,b);
	}
	
	function oppositeSort(a,b)
	{
		return(doSort(a,b) * -1);
	}
	
	function secondarySort(a,b)
	{
		return (a.getField('dateModified') > b.getField('dateModified')) ? -1 : (a.getField('dateModified') < b.getField('dateModified')) ? 1 : 0;
	}
	
	var openRows = new Array();
	for(var i = 0; i < this._dataItems.length; i++)
	{
		if(this.isContainer(i) && this.isContainerOpen(i))
		{
			openRows.push(this._getItemAtRow(i).ref.getID());
			this.toggleOpenState(i);
		}
	}
	
	if(order)
		this._dataItems.sort(oppositeSort);
	else
		this._dataItems.sort(doSort);
		
	this._refreshHashMap();
	
	for(var i = 0; i < openRows.length; i++)
		this.toggleOpenState(this._itemRowMap[openRows[i]]);
	
}

////////////////////////////////////////////////////////////////////////////////
///
///  Additional functions for managing data in the tree
///
////////////////////////////////////////////////////////////////////////////////

/*
 *  Select an item
 */
Scholar.ItemTreeView.prototype.selectItem = function(id)
{
	var row = this._itemRowMap[id];
	if(row == null)
	{
		var item = Scholar.Items.get(id);
		this.toggleOpenState(this._itemRowMap[item.getSource()]); //opens the parent of the item
		row = this._itemRowMap[id];
	}
		
	this.selection.select(row);
	this._treebox.ensureRowIsVisible(row);
}

/*
 *  Delete the selection
 */
Scholar.ItemTreeView.prototype.deleteSelection = function(eraseChildren)
{
	if(this.selection.count == 0)
		return;
		
	//collapse open items
	for(var i=0; i<this.rowCount; i++)
		if(this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i))
			this.toggleOpenState(i);
	this._refreshHashMap();
	
	//create an array of selected items
	var items = new Array();
	var start = new Object();
	var end = new Object();
	for (var i=0, len=this.selection.getRangeCount(); i<len; i++)
	{
		this.selection.getRangeAt(i,start,end);
		for (var j=start.value; j<=end.value; j++)
			items.push(this._getItemAtRow(j));
	}
	
	//iterate and erase...
	this._treebox.beginUpdateBatch();
	for (var i=0; i<items.length; i++)
	{
		if(this._itemGroup.isLibrary() || !items[i].isRegularItem()) //erase item from DB
			items[i].ref.erase(eraseChildren);
		else if(this._itemGroup.isCollection())
			this._itemGroup.ref.removeItem(items[i].ref.getID());
	}
	this._treebox.endUpdateBatch();
}

/*
 *  Set the search filter on the view
 */
Scholar.ItemTreeView.prototype.searchText = function(search)
{
	this.selection.selectEventsSuppressed = true;
	var savedSelection = this.saveSelection();
	
	this._itemGroup.setSearch(search);
	var oldCount = this.rowCount;
	this.refresh();
	this._treebox.rowCountChanged(0,this.rowCount-oldCount);
	
	this.sort();

	this.rememberSelection(savedSelection);
	this.selection.selectEventsSuppressed = false;
	this._treebox.invalidate();
}

/*
 *  Called by various view functions to show a row
 * 
 *  	item:	reference to the Item
 *      beforeRow:	row index to insert new row before
 */
Scholar.ItemTreeView.prototype._showItem = function(item, beforeRow)
{
	this._dataItems.splice(beforeRow, 0, item);
	this.rowCount++;
}

/*
 *  Called by view to hide specified row
 */
Scholar.ItemTreeView.prototype._hideItem = function(row)
{
	this._dataItems.splice(row,1);
	this.rowCount--;
}

/*
 *  Returns a reference to the item at row (see Scholar.Item in data_access.js)
 */
Scholar.ItemTreeView.prototype._getItemAtRow = function(row)
{
	return this._dataItems[row];
}

/*
 *  Create hash map of item ids to row indexes
 */
Scholar.ItemTreeView.prototype._refreshHashMap = function()
{
	this._itemRowMap = new Array();
	for(var i=0; i < this.rowCount; i++)
	{
		var row = this._getItemAtRow(i);
		this._itemRowMap[row.ref.getID()] = i;
	}
}

/*
 *  Saves the ids of currently selected items for later
 */
Scholar.ItemTreeView.prototype.saveSelection = function()
{
	savedSelection = new Array();
	
	var start = new Object();
	var end = new Object();
	for (var i=0, len=this.selection.getRangeCount(); i<len; i++)
	{
		this.selection.getRangeAt(i,start,end);
		for (var j=start.value; j<=end.value; j++)
		{
			savedSelection.push(this._getItemAtRow(j).ref.getID());
		}
	}
	return savedSelection;
}

/*
 *  Sets the selection based on saved selection ids (see above)
 */
Scholar.ItemTreeView.prototype.rememberSelection = function(selection)
{
	this.selection.clearSelection();
	for(var i=0; i < selection.length; i++)
	{
		if(this._itemRowMap[selection[i]] != null)
			this.selection.toggleSelect(this._itemRowMap[selection[i]]);
	}
}

////////////////////////////////////////////////////////////////////////////////
///
///  Command Controller:
///		for Select All, etc.
///
////////////////////////////////////////////////////////////////////////////////

Scholar.ItemTreeCommandController = function(tree)
{
	this.tree = tree;
}

Scholar.ItemTreeCommandController.prototype.supportsCommand = function(cmd)
{
	return (cmd == 'cmd_selectAll');
}

Scholar.ItemTreeCommandController.prototype.isCommandEnabled = function(cmd)
{
	return (cmd == 'cmd_selectAll');
}

Scholar.ItemTreeCommandController.prototype.doCommand = function(cmd)
{
	if(cmd == 'cmd_selectAll')
		this.tree.view.selection.selectAll();
}

Scholar.ItemTreeCommandController.prototype.onEvent = function(evt)
{
	
}

////////////////////////////////////////////////////////////////////////////////
///
///  Drag-and-drop functions:
///		for nsDragAndDrop.js + nsTransferable.js
///
////////////////////////////////////////////////////////////////////////////////

/*
 *  Begin a drag
 */
Scholar.ItemTreeView.prototype.onDragStart = function (evt,transferData,action)
{ 
	transferData.data=new TransferData();
	transferData.data.addDataForFlavour("scholar/item",this.saveSelection());
}

/*
 *  Called by nsDragAndDrop.js for any sort of drop on the tree
 */
Scholar.ItemTreeView.prototype.getSupportedFlavours = function () 
{ 
	var flavors = new FlavourSet();
	flavors.appendFlavour("scholar/item");
	flavors.appendFlavour("text/x-moz-url");
	return flavors; 
}

/*
 *  Called by nsDragAndDrop.js for any sort of drop on the tree
 */
Scholar.ItemTreeView.prototype.onDrop = function (evt,data,session)
{
	var dataType = data.flavour.contentType;
	
	if(dataType == 'scholar/item')
	{
		var ids = data.data.split(',');
		for(var i = 0; i<ids.length; i++)
			this._itemGroup.ref.addItem(ids[i]);
	}
	else if(dataType == 'text/x-moz-url')
	{
		var url = data.data.split("\n")[0];
		
		/* WAITING FOR INGESTER SUPPORT
		var newItem = Scholar.Ingester.scrapeURL(url);
		
		if(newItem)
			this._itemGroup.ref.addItem(newItem.getID());
		*/
	}
}

Scholar.ItemTreeView.prototype.onDragOver = function (evt,dropdata,session) { }

////////////////////////////////////////////////////////////////////////////////
///
///  Functions for nsITreeView that we have to stub out.
///
////////////////////////////////////////////////////////////////////////////////

Scholar.ItemTreeView.prototype.isSeparator = function(row) 						{ return false; }
Scholar.ItemTreeView.prototype.getRowProperties = function(row, prop) 			{ }
Scholar.ItemTreeView.prototype.getColumnProperties = function(col, prop) 		{ }
Scholar.ItemTreeView.prototype.getCellProperties = function(row, col, prop) 	{ }

Scholar.ItemTreeView.TreeRow = function(ref, level, isOpen)
{
	this.ref = ref;			//the item associated with this
	this.level = level;
	this.isOpen = isOpen;
}

Scholar.ItemTreeView.TreeRow.prototype.isNote = function()
{
	return this.ref.isNote();
}

Scholar.ItemTreeView.TreeRow.prototype.isAttachment = function()
{
	return this.ref.isAttachment();
}

Scholar.ItemTreeView.TreeRow.prototype.isRegularItem = function()
{
	return this.ref.isRegularItem();
}

Scholar.ItemTreeView.TreeRow.prototype.getField = function(field)
{
	if(this.isNote() && field == 'title')
	{
		var t = this.ref.getNote();
		if(t)
		{
			var n = t.indexOf("\n");
			if(n > -1)
				t = t.substring(0,n);
			return t;
		}
	}
	else
	{
		return this.ref.getField(field);
	}
	
}

Scholar.ItemTreeView.TreeRow.prototype.getType = function()
{
	return this.ref.getType();
}

Scholar.ItemTreeView.TreeRow.prototype.numChildren = function()
{
	if(this.isRegularItem())
		return this.ref.numChildren();
	else
		return 0;
}

Scholar.ItemTreeView.TreeRow.prototype.numNotes = function()
{
	if(this.isRegularItem())
		return this.ref.numNotes();
	else
		return 0;
}

Scholar.ItemTreeView.TreeRow.prototype.numAttachments = function()
{
	if(this.isRegularItem())
		return this.ref.numAttachments();
	else
		return 0;
}