/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */



var NC_NS  = "http://home.netscape.com/NC-rdf#";
var RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";



function TopLevelDrag ( event )
{
  dump("TOP LEVEL bookmarks window got a drag");
  return(true);
}


function BeginDragTree ( event )
{
  //XXX we rely on a capturer to already have determined which item the mouse was over
  //XXX and have set an attribute.
    
  // if the click is on the tree proper, ignore it. We only care about clicks on items.

  var tree = document.getElementById("bookmarksTree");
  if ( event.target == tree || event.target.localName == "treechildren" )
    return(true);         // continue propagating the event
    
  var childWithDatabase = tree;
  if ( ! childWithDatabase )
    return(false);
    
  var dragStarted = false;

  var trans = 
    Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
  if ( !trans ) return(false);

  var genData = 
    Components.classes["@mozilla.org/supports-wstring;1"].createInstance(Components.interfaces.nsISupportsWString);
  if (!genData) return(false);

  var genDataURL = 
    Components.classes["@mozilla.org/supports-wstring;1"].createInstance(Components.interfaces.nsISupportsWString);
  if (!genDataURL) return(false);

  trans.addDataFlavor("text/unicode");
  trans.addDataFlavor("moz/rdfitem");
        
  // ref/id (url) is on the <treeitem> which is two levels above the <treecell> which is
  // the target of the event.
  var id = event.target.parentNode.parentNode.getAttribute("ref");
  if (!id || id=="")
  {
	id = event.target.parentNode.parentNode.getAttribute("id");
  }
  dump("\n    ID: " + id);

  var parentID = event.target.parentNode.parentNode.parentNode.parentNode.getAttribute("ref");
  if (!parentID || parentID == "")
  {
	parentID = event.target.parentNode.parentNode.parentNode.parentNode.getAttribute("id");
  }
  dump("    Parent ID: " + parentID);

  var trueID = id;
  if (parentID != null)
  {
  	trueID += "\n" + parentID;
  }
  genData.data = trueID;
  genDataURL.data = id;

  var database = childWithDatabase.database;
  var rdf = 
    Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
  if ((!rdf) || (!database))  { dump("CAN'T GET DATABASE\n"); return(false); }

  // make sure its a bookmark, bookmark separator, or bookmark folder
  var src = rdf.GetResource(id, true);
  var prop = rdf.GetResource(RDF_NS + "type", true);
  var target = database.GetTarget(src, prop, true);

  if (target) target = target.QueryInterface(Components.interfaces.nsIRDFResource);
  if (target) target = target.Value;
  if ((!target) || (target == "")) {dump("BAD\n"); return(false);}
  dump("    Type: '" + target + "'");

  if ((target != NC_NS + "BookmarkSeparator") &&
    (target != NC_NS + "Bookmark") &&
    (target != NC_NS + "Folder")) return(false);

dump("genData is " + genData.data + " len is " + genData.data.length + "\n");
  trans.setTransferData ( "moz/rdfitem", genData, genData.data.length * 2);  // double byte data
  trans.setTransferData ( "text/unicode", genDataURL, genDataURL.data.length * 2);  // double byte data

  var transArray = 
    Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
  if ( !transArray )  return(false);

  // put it into the transferable as an |nsISupports|
  var genTrans = trans.QueryInterface(Components.interfaces.nsISupports);
  transArray.AppendElement(genTrans);
  
  var dragService = 
    Components.classes["@mozilla.org/widget/dragservice;1"].getService(Components.interfaces.nsIDragService);
  if ( !dragService ) return(false);

  var nsIDragService = Components.interfaces.nsIDragService;
  dragService.invokeDragSession ( event.target, transArray, null, nsIDragService.DRAGDROP_ACTION_COPY + 
                                     nsIDragService.DRAGDROP_ACTION_MOVE );
  dragStarted = true;

  return(!dragStarted);
}



function DragOverTree ( event )
{
  var validFlavor = false;
  var dragSession = null;
  var retVal = true;

  var dragService = 
    Components.classes["@mozilla.org/widget/dragservice;1"].getService(Components.interfaces.nsIDragService);
  if ( !dragService ) return(false);

  dragSession = dragService.getCurrentSession();
  if ( !dragSession ) return(false);

  if ( dragSession.isDataFlavorSupported("moz/rdfitem") ) validFlavor = true;
  else if ( dragSession.isDataFlavorSupported("text/unicode") ) validFlavor = true;
  //XXX other flavors here...

  // touch the attribute on the rowgroup to trigger the repaint with the drop feedback.
  if ( validFlavor )
  {
    var treeRoot = document.getElementById("bookmarksTree");
    if (!treeRoot)  return(false);
    var treeDatabase = treeRoot.database;
    if (!treeDatabase)  return(false);

    //XXX this is really slow and likes to refresh N times per second.
    var rowGroup = event.target.parentNode.parentNode;
    var sortActive = treeRoot.getAttribute("sortActive");
    if (sortActive == "true")
      rowGroup.setAttribute ( "dd-triggerrepaintsorted", 0 );
    else
      rowGroup.setAttribute ( "dd-triggerrepaint", 0 );

    dragSession.canDrop = true;
    // necessary??
    retVal = false;
  }
  return(retVal);
}



function DropOnTree ( event )
{
  var treeRoot = document.getElementById("bookmarksTree");
  if (!treeRoot)  return(false);
  var treeDatabase = treeRoot.database;
  if (!treeDatabase)  return(false);

  // for beta1, don't allow D&D if sorting is active
  var sortActive = treeRoot.getAttribute("sortActive");
  if (sortActive == "true")
  {
  	dump("Sorry, drag&drop is currently disabled when sorting is active.\n");
  	return(false);
  }

  var RDF = 
    Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
  if (!RDF) return(false);
  var RDFC =
    Components.classes["@mozilla.org/rdf/container;1"].getService(Components.interfaces.nsIRDFContainer);
  if (!RDFC)  return(false);

  var Bookmarks = RDF.GetDataSource("rdf:bookmarks");
  if (!Bookmarks) return(false);

  // target is the <treecell>, and "ref/id" is on the <treeitem> two levels above
  var treeItem = event.target.parentNode.parentNode;
  if (!treeItem)  return(false);

  // get drop hint attributes
  var dropBefore = treeItem.getAttribute("dd-droplocation");
  var dropOn = treeItem.getAttribute("dd-dropon");

  // calculate drop action
  var dropAction;
  if (dropBefore == "true") dropAction = "before";
  else if (dropOn == "true")  dropAction = "on";
  else        dropAction = "after";

  // calculate parent container node
  var containerItem = treeItem;
  if (dropAction != "on")
  {
    containerItem = treeItem.parentNode.parentNode;
  }

	// magical fix for bug # 33546: handle dropping after open container
	if (treeItem.getAttribute("container") == "true")
	{
		if (treeItem.getAttribute("open") == "true")
		{
			if (dropAction == "after")
			{
				dropAction = "before";
				containerItem = treeItem;

				// find <treechildren>, drop before first child
				var treeChildren = treeItem;
				treeItem = null;
				for (var x = 0; x < treeChildren.childNodes.length; x++)
				{
					if (treeChildren.childNodes[x].tagName == "treechildren")
					{
						treeItem = treeChildren.childNodes[x].childNodes[0];
						break;
					}
				}

				if (!treeItem)
				{
					dropAction = "on";
					containerItem = treeItem.parentNode.parentNode;
				}
			}
		}
	}

  var targetID = getAbsoluteID("bookmarksTree", treeItem);
  if (!targetID)  return(false);
  var targetNode = RDF.GetResource(targetID, true);
  if (!targetNode)  return(false);

  var containerID = getAbsoluteID("bookmarksTree", containerItem);
  if (!containerID) return(false);
  var containerNode = RDF.GetResource(containerID);
  if (!containerNode) return(false);

  var dragService = 
    Components.classes["@mozilla.org/widget/dragservice;1"].getService(Components.interfaces.nsIDragService);
  if ( !dragService ) return(false);
  
  var dragSession = dragService.getCurrentSession();
  if ( !dragSession ) return(false);

  var trans = 
    Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
  if ( !trans )   return(false);
  trans.addDataFlavor("moz/rdfitem");
  trans.addDataFlavor("text/x-moz-url");
  trans.addDataFlavor("text/unicode");

    var typeRes = RDF.GetResource(RDF_NS + "type");
    if (!typeRes) return false;
    var bmTypeRes = RDF.GetResource(NC_NS + "Bookmark");
    if (!bmTypeRes) return false;

  var dirty = false;

  for ( var i = 0; i < dragSession.numDropItems; ++i )
  {
    dragSession.getData ( trans, i );
    var dataObj = new Object();
    var bestFlavor = new Object();
    var len = new Object();
    trans.getAnyTransferData ( bestFlavor, dataObj, len );
    if ( dataObj )  dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
    if ( !dataObj ) continue;

    var sourceID = null;
    var parentID = null;
    var checkNameHack = false;
    var name=null;

    if (bestFlavor.value == "moz/rdfitem")
    {
	    // pull the URL out of the data object
	    var data = dataObj.data.substring(0, len.value / 2);

        // moz/rdfitem allows parent ID specified on next line; check for it
	    var cr = data.indexOf("\n");
	    if (cr >= 0)
	    {
	    	sourceID = data.substr(0, cr);
	    	parentID = data.substr(cr+1);
	    }
	    else
	    {
	        sourceID = data;
	    }
    }
    else if (bestFlavor.value == "text/x-moz-url")
    {
	    // pull the URL out of the data object
	    var data = dataObj.data.substring(0, len.value / 2);
	    sourceID = data;

    	// we may need to synthesize a name (just use the URL)
    	checkNameHack = true;
    }
    else if (bestFlavor.value == "text/unicode")
    {
    	sourceID = dataObj.data;

    	// we may need to synthesize a name (just use the URL)
    	checkNameHack = true;
    }
    else
    {
        // unknown flavor, skip
        continue;
    }

    // pull the (optional) name out of the URL
    var space = sourceID.indexOf(" ");
    if (space >= 0)
    {
        name = sourceID.substr(space+1);
        sourceID = sourceID.substr(0, space);
    }

    dump("    Node #" + i + ": drop '" + sourceID + "'\n");
    dump("             from container '" + parentID + "'\n");
    dump("             action = '" + dropAction + "'\n");
    dump("             target = '" + targetID + "'\n");
    if (name)
    {
        dump("             name = '" + name + "'\n");
    }

    var sourceNode = RDF.GetResource(sourceID, true);
    if (!sourceNode)  continue;

    var parentNode = null;
    if (parentID != null)
    {
    	parentNode = RDF.GetResource(parentID, true);
    }
    
    // Prevent dropping of a node before, after, or on itself
    if (sourceNode == targetNode) return false;
    // Prevent dropping of a node onto its parent container
    if ((dropAction == "on") && (containerID) && (containerID == parentID)) return false;

    RDFC.Init(Bookmarks, containerNode);

    // make sure appropriate bookmark type is set
    var bmTypeNode = Bookmarks.GetTarget( sourceNode, typeRes, true );
    if (!bmTypeNode)
    {
        // set default bookmark type
        Bookmarks.Assert(sourceNode, typeRes, bmTypeRes, true);
        dump("Setting default bookmark type\n");
    }

    if ((dropAction == "before") || (dropAction == "after"))
    {
      // drop before or after
      var nodeIndex;

      nodeIndex = RDFC.IndexOf(sourceNode);

      if (nodeIndex >= 1)
      {
        // moving a node around inside of the container
        // so remove, then re-add the node
        RDFC.RemoveElementAt(nodeIndex, true, sourceNode);
      }
      
      nodeIndex = RDFC.IndexOf(targetNode);

      if (nodeIndex < 1)  return(false);
      if (dropAction == "after")  ++nodeIndex;

      RDFC.InsertElementAt(sourceNode, nodeIndex, true);

	// select the newly added node
	if (parentID)
	{
	      selectDroppedItems(treeRoot, containerID, sourceID);
	}	     

      dirty = true;
    }
    else
    {
      // drop on
      RDFC.AppendElement(sourceNode);

	// select the newly added node
	if (parentID)
	{
	      selectDroppedItems(treeRoot, containerID, sourceID);
	}	     

      dirty = true;
    }

    if ((checkNameHack == true) || (name != null))
    {
    	var srcArc = RDF.GetResource(sourceID, true);
    	var propArc = RDF.GetResource(NC_NS + "Name", true);
    	if (srcArc && propArc && Bookmarks)
    	{
    		var targetArc = Bookmarks.GetTarget(srcArc, propArc, true);
    		if (!targetArc)
    		{
    		    // if no name, fallback to using the URL as the name
    			var defaultNameArc = RDF.GetLiteral((name != null && name != "") ? name : sourceID);
    			if (defaultNameArc)
    			{
    				Bookmarks.Assert(srcArc, propArc, defaultNameArc, true); 
    			}
    		}
    	}
    }
  }

	// should we move the node? (i.e. take it out of the source container?)
	if ((parentNode != null) && (containerNode != parentNode))
	{
	    try
	    {
    		RDFC.Init(Bookmarks, parentNode);
    		var nodeIndex = RDFC.IndexOf(sourceNode);

    		if (nodeIndex >= 1)
    		{
    			RDFC.RemoveElementAt(nodeIndex, true, sourceNode);
    		}
        }
        catch(ex)
        {
        }
	}

  if (dirty == true)
  {
    var remote = Bookmarks.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
    if (remote)
    {
      remote.Flush();
      dump("Wrote out bookmark changes.\n");
    }
  }

  return(false);
}



function selectDroppedItems(treeRoot, containerID, targetID)
{
	var select_list = treeRoot.getElementsByAttribute("id", targetID);
	for (var x=0; x<select_list.length; x++)
	{
		var node = select_list[x];
		if (!node)	continue;

		var parent = node.parentNode.parentNode;
		if (!parent)	continue;

		var id = parent.getAttribute("ref");
		if (!id || id=="")
		{
			id = parent.getAttribute("id");
		}
		if (!id || id=="")	continue;

		if (id == containerID)
		{
			treeRoot.selectItem(node);
			break;
		}
	}
}
