/*
	Scholar
	Copyright (C) 2006   Center for History and New Media, George Mason University, Fairfax, VA
	http://chnm.gmu.edu/
	
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

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

/*
 *  Constructor the the CollectionTreeView object
 */
Scholar.CollectionTreeView = function()
{
	this._treebox = null;
	this.refresh();
	
	this._unregisterID = Scholar.Notifier.registerColumnTree(this);
}

/*
 *  Called by the tree itself
 */
Scholar.CollectionTreeView.prototype.setTree = function(treebox)
{
	if(this._treebox)
		return;
	this._treebox = treebox;
	//select Library
	this.selection.select(0);
}

/*
 *  Reload the rows from the data access methods
 *  (doesn't call the tree.invalidate methods, etc.)
 */
Scholar.CollectionTreeView.prototype.refresh = function()
{
	this._dataItems = new Array();
	this.rowCount = 0;
	this._showItem(new Scholar.ItemGroup('library',null),0,1);
	
	var newRows = Scholar.getCollections();
	for(var i = 0; i < newRows.length; i++)
		this._showItem(new Scholar.ItemGroup('collection',newRows[i]),  0, this._dataItems.length); //itemgroup ref, level, beforeRow
	
	var savedSearches = Scholar.Searches.getAll();
	for(var i = 0; i < savedSearches.length; i++)
		this._showItem(new Scholar.ItemGroup('search',savedSearches[i]),  0, this._dataItems.length); //itemgroup ref, level, beforeRow
	
	this._refreshHashMap();
}

/*
 *  Redisplay everything
 */
Scholar.CollectionTreeView.prototype.reload = function()
{
	var openCollections = new Array();
	
	for(var i = 0; i < this.rowCount; i++)
		if(this.isContainer(i) && this.isContainerOpen(i))
			openCollections.push(this._getItemAtRow(i).ref.getID());
	
	var oldCount = this.rowCount;
	this._treebox.beginUpdateBatch();
	this.refresh();
	this._treebox.rowCountChanged(0,this.rowCount - oldCount);
	
	for(var i = 0; i < openCollections.length; i++)
	{
		var row = this._collectionRowMap[openCollections[i]];
		if(row != null)
			this.toggleOpenState(row);
	}
	this._treebox.invalidate();
	this._treebox.endUpdateBatch();
}

/*
 *  Called by Scholar.Notifier on any changes to collections in the data layer
 */
Scholar.CollectionTreeView.prototype.notify = function(action, type, ids)
{
	var madeChanges = false;
	
	Scholar.debug(action+', '+type+', '+ids);
	
	if(action == 'remove')
	{
		ids = Scholar.flattenArguments(ids);
		//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(this._collectionRowMap[ids[i]] != null)
				rows.push(this._collectionRowMap[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];
				this._hideItem(row-i);
				this._treebox.rowCountChanged(row-i,-1);
			}
			
			madeChanges = true;
		}		
		
	}
	else if(action == 'move')
	{
		this.reload();
	}
	else if(action == 'modify')
	{
		var row = this._collectionRowMap[ids];
		if(row != null)
			this._treebox.invalidateRow(row);
	}
	else if(action == 'add')
	{
		var item = Scholar.Collections.get(ids);
		
		this._showItem(new Scholar.ItemGroup('collection',item), 0, this.rowCount);
		this._treebox.rowCountChanged(this.rowCount-1,1);
	
		madeChanges = true;
	}
	
	if(madeChanges)
		this._refreshHashMap();
}

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

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

Scholar.CollectionTreeView.prototype.getCellText = function(row, column)
{
	var obj = this._getItemAtRow(row);
	
	if(column.id == "name_column")
		return obj.getName();
	else
		return "";
}

Scholar.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
	var collectionType = this._getItemAtRow(row).type;
	return "chrome://scholar/skin/treesource-" + collectionType + ".png";
}

Scholar.CollectionTreeView.prototype.isContainer = function(row)
{
	return this._getItemAtRow(row).isCollection();
}

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

Scholar.CollectionTreeView.prototype.isContainerEmpty = function(row)
{
	//NOTE: this returns true if the collection has no child collections
	
	var itemGroup = this._getItemAtRow(row);
	if(itemGroup.isCollection())
		return !itemGroup.ref.hasChildCollections();
	else
		return true;
}

Scholar.CollectionTreeView.prototype.getLevel = function(row)
{
	return this._dataItems[row][2];
}

Scholar.CollectionTreeView.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.CollectionTreeView.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;
	}
}

/*
 *  Opens/closes the specified row
 */
Scholar.CollectionTreeView.prototype.toggleOpenState = function(row)
{
	var count = 0;		//used to tell the tree how many rows were added/removed
	var thisLevel = this.getLevel(row);

	this._treebox.beginUpdateBatch();
	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 newRows = Scholar.getCollections(this._getItemAtRow(row).ref.getID()); //Get children
		
		for(var i = 0; i < newRows.length; i++)
		{
			count++;
			this._showItem(new Scholar.ItemGroup('collection',newRows[i]), thisLevel+1, row+i+1); //insert new row
		}
	}
	this._dataItems[row][1] = !this._dataItems[row][1];  //toggle container open value

	this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
	this._treebox.invalidateRow(row);
	this._treebox.endUpdateBatch();	
	this._refreshHashMap();
}

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

/*
 *  Delete the selection
 */
Scholar.CollectionTreeView.prototype.deleteSelection = function()
{
	if(this.selection.count == 0)
		return;

	//collapse open collections
	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 collections
	var rows = 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++)
			if(!this._getItemAtRow(j).isLibrary())
				rows.push(j);
	}
	
	//iterate and erase...
	this._treebox.beginUpdateBatch();
	for (var i=0; i<rows.length; i++)
	{
		//erase collection from DB:
		var group = this._getItemAtRow(rows[i]-i);
		if(group.isCollection())
			group.ref.erase();
		else if(group.isSearch())
		{
			Scholar.Searches.erase(group.ref['id']);
			this._hideItem(rows[i]-i); //we don't have the notification system set up with searches.
		}
	}
	this._treebox.endUpdateBatch();
	
	if(end.value < this.rowCount)
		this.selection.select(end.value);
	else
		this.selection.select(this.rowCount-1);
}

/*
 *  Called by various view functions to show a row
 * 
 *  	itemGroup:	reference to the ItemGroup
 *  	level:	the indent level of the row
 *      beforeRow:	row index to insert new row before
 */
Scholar.CollectionTreeView.prototype._showItem = function(itemGroup, level, beforeRow)
{
	this._dataItems.splice(beforeRow, 0, [itemGroup, false, level]); this.rowCount++;
}

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

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

/*
 * Creates hash map of collection ids to row indexes
 * e.g., var rowForID = this._collectionRowMap[]
 */
Scholar.CollectionTreeView.prototype._refreshHashMap = function()
{	
	this._collectionRowMap = new Array();
	for(var i=0; i < this.rowCount; i++)
		if (this.isContainer(i))
			this._collectionRowMap[this._getItemAtRow(i).ref.getID()] = i;
		
	
}

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

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

Scholar.CollectionTreeCommandController.prototype.supportsCommand = function(cmd)
{
	return (cmd == 'cmd_delete');
}

Scholar.CollectionTreeCommandController.prototype.isCommandEnabled = function(cmd)
{
	return (cmd == 'cmd_delete' && this.tree.view.selection.count > 0);
}

Scholar.CollectionTreeCommandController.prototype.doCommand = function(cmd)
{
	if(cmd == 'cmd_delete')
		ScholarPane.deleteSelectedCollection();
}

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

////////////////////////////////////////////////////////////////////////////////
///
///  Drag-and-drop functions:
///		canDrop() and drop() are for nsITreeView
///		onDragStart(), getSupportedFlavours(), and onDrop() for nsDragAndDrop.js + nsTransferable.js
///
////////////////////////////////////////////////////////////////////////////////

/*
 *  Called while a drag is over the tree.
 */
Scholar.CollectionTreeView.prototype.canDrop = function(row, orient)
{
	if(typeof row == 'object')	//workaround... two different services call canDrop (nsDragAndDrop, and the tree)
		return false;
		
	try
	{
		var dataSet = nsTransferable.get(this.getSupportedFlavours(),nsDragAndDrop.getDragData, true);
	}
	catch (e)
	{
		//a work around a limitation in nsDragAndDrop.js -- the mDragSession is not set until the drag moves over another control.
		//(this will only happen if the first drag is from the collection list)
		nsDragAndDrop.mDragSession = nsDragAndDrop.mDragService.getCurrentSession();
		return false;
	}
	var data = dataSet.first.first;
	var dataType = data.flavour.contentType;
	
	
	//Highlight the rows correctly on drag:
	if(orient == 1 && row == 0 && dataType == 'scholar/collection') //for dropping collections into root level
	{
		return true;
	}
	else if(orient == 0)	//directly on a row...
	{
		var rowCollection = this._getItemAtRow(row).ref; //the collection we are dragging over
		
		if(dataType == 'scholar/item' || dataType == "text/x-moz-url")
			return true;	//items can be dropped on anything
		else if(dataType='scholar/collection' && data.data != rowCollection.getID() && !Scholar.Collections.get(data.data).hasDescendent('collection',rowCollection.getID()) )
			return true;	//collections cannot be dropped on themselves, nor in their children
	}
	return false;
}

/*
 *  Called when something's been dropped on or next to a row
 */
Scholar.CollectionTreeView.prototype.drop = function(row, orient)
{
	var dataSet = nsTransferable.get(this.getSupportedFlavours(),nsDragAndDrop.getDragData, true);
	var data = dataSet.first.first;
	var dataType = data.flavour.contentType;
	
	if(dataType == 'scholar/collection')
	{
		var oldCount = this.rowCount;
		
		var targetCollectionID;
		if(this._getItemAtRow(row).isCollection())
			targetCollectionID = this._getItemAtRow(row).ref.getID();
		var droppedCollection = Scholar.Collections.get(data.data);
		droppedCollection.changeParent(targetCollectionID);
		
		var selectRow = this._collectionRowMap[data.data];
		if(selectRow == null)
			selectRow = this._collectionRowMap[targetCollectionID];
		
		this.selection.selectEventsSuppressed = true;
		this.selection.clearSelection();
		this.selection.select(selectRow);
		this.selection.selectEventsSuppressed = false;
			
	}
	else if(dataType == 'scholar/item' && this.canDrop(row, orient))
	{
		var ids = data.data.split(',');
		var targetCollection = this._getItemAtRow(row).ref;
		for(var i = 0; i<ids.length; i++)
			targetCollection.addItem(ids[i]);
	}
	else if(dataType == 'text/x-moz-url' && this.canDrop(row, orient))
	{
		var url = data.data.split("\n")[0];
		
		/* WAITING FOR INGESTER SUPPORT
		var newItem = Scholar.Ingester.scrapeURL(url);
		
		if(newItem)
			this._getItemAtRow(row).ref.addItem(newItem.getID());
		*/
	}
}

/*
 *  Begin a drag
 */
Scholar.CollectionTreeView.prototype.onDragStart = function(evt,transferData,action)
{
	transferData.data=new TransferData();
	
	//attach ID
	transferData.data.addDataForFlavour("scholar/collection",this._getItemAtRow(this.selection.currentIndex).ref.getID());
}

/*
 *  Returns the supported drag flavors
 */
Scholar.CollectionTreeView.prototype.getSupportedFlavours = function () 
{ 
	var flavors = new FlavourSet();
	flavors.appendFlavour("text/x-moz-url");
	flavors.appendFlavour("scholar/item");
	flavors.appendFlavour("scholar/collection");
	return flavors; 
}

/*
 *  Called by nsDragAndDrop.js for any sort of drop on the tree
 */
Scholar.CollectionTreeView.prototype.onDrop = function (evt,dropdata,session) { }

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

Scholar.CollectionTreeView.prototype.isSorted = function() 							{ return false; }
Scholar.CollectionTreeView.prototype.isSeparator = function(row) 					{ return false; }
Scholar.CollectionTreeView.prototype.isEditable = function(row, idx) 				{ return false; }
Scholar.CollectionTreeView.prototype.getRowProperties = function(row, prop) 		{ }
Scholar.CollectionTreeView.prototype.getColumnProperties = function(col, prop) 		{ }
Scholar.CollectionTreeView.prototype.getCellProperties = function(row, col, prop) 	{ }
Scholar.CollectionTreeView.prototype.performAction = function(action) 				{ }
Scholar.CollectionTreeView.prototype.performActionOnCell = function(action, row, col)	{ }
Scholar.CollectionTreeView.prototype.getProgressMode = function(row, col) 			{ }
Scholar.CollectionTreeView.prototype.cycleHeader = function(column)					{ }

////////////////////////////////////////////////////////////////////////////////
///
///  Scholar ItemGroup -- a sort of "super class" for Collection, library, 
///  	and eventually smartSearch
///
////////////////////////////////////////////////////////////////////////////////

Scholar.ItemGroup = function(type, ref)
{
	this.type = type;
	this.ref = ref;
}

Scholar.ItemGroup.prototype.isLibrary = function()
{
	return this.type == 'library';
}

Scholar.ItemGroup.prototype.isCollection = function()
{
	return this.type == 'collection';
}

Scholar.ItemGroup.prototype.isSearch = function()
{
	return this.type == 'search';
}

Scholar.ItemGroup.prototype.getName = function()
{
	if(this.isCollection())
		return this.ref.getName();
	else if(this.isLibrary())
		return Scholar.getString('pane.collections.library');
	else if(this.isSearch())
		return this.ref['name'];
	else
		return "";
}

Scholar.ItemGroup.prototype.getChildItems = function()
{
	if(this.searchText)
	{
		return Scholar.Items.get(Scholar.Items.search(this.searchText,(this.isCollection() ? this.ref.getID() : null)));
	}
	else
	{
		if(this.isCollection())
			return Scholar.getItems(this.ref.getID());
		else if(this.isLibrary())
			return Scholar.getItems();
		else if(this.isSearch())
		{
			var s = new Scholar.Search();
			s.load(this.ref['id']);
			return Scholar.Items.get(s.search());
		}
		else
			return null;
	}
}

Scholar.ItemGroup.prototype.setSearch = function(searchText)
{
	this.searchText = searchText;
}