
// ------------------------------------------------------------------------------------
//  AJAX TREE PACKAGE
//
//  TODO: Try to use JcmsJsonRequest
// ------------------------------------------------------------------------------------

'Ajax.Tree'.namespace({
 
 enableDragNDrop: true, 
 dragdropArray: $A(new Array()),

  // ------------------------------------------------------------------------------------
  //  AJAX Function
  // ------------------------------------------------------------------------------------
  _downloadChildrenHooks: $H(),
  
  /**
   * Allow other javascript code to register a custom callback
   * when a new tree branch is retrieved in ajax.
   */
  registerDownloadChildrenHook: function(ajaxSuffix, customCallback) {
    var callbacks = Ajax.Tree._downloadChildrenHooks.get(ajaxSuffix);
    if (!callbacks) {
      callbacks = $A();
      Ajax.Tree._downloadChildrenHooks.set(ajaxSuffix,callbacks);
    }
    callbacks.push(customCallback);
  },
  
  invokeDownloadChildrenHook: function(ajaxSuffix, ul) {
    var callbacks = Ajax.Tree._downloadChildrenHooks.get(ajaxSuffix);
    if (!callbacks) { return; }
    callbacks.each(function(item) {
      item(ul);
    });
  },
  
  /**
   * Get HTML of children 
   * - for the ID in the img.classname (ID_j_42)
   * - for the full tree with given checkedArray
   * 
   * Update UL innerHTML with this new content.
   * Remove Image click function to toggleVisibility instead of calling a new Ajax call.
   * 
   * @param img the clicked image
   * @param ajaxSuffix String that represents the main UL id
   * @param ul the root branch to fill
   * @param checkedArray a Array of checked category ids
   */
  downloadChildren: function(img, ajaxSuffix, ul, checkedArray, openedArray, customCallback) {
    
    JcmsLogger.debug('TreeCat','downloadChildren():', ajaxSuffix, img, checkedArray, openedArray);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(img); 
    
    // Init RPC with jsonRequest
    var funcRPC = function(){
      if (img){
        Ajax.Tree._getRpcTree(ajaxSuffix).getChildren(jsonRequest.asyncJsonCallBack.bind(jsonRequest), $(img).getJcmsId());
      } else {
        Ajax.Tree._getRpcTree(ajaxSuffix).getChildren(jsonRequest.asyncJsonCallBack.bind(jsonRequest),checkedArray,openedArray);
      }
    }
    
    // Init Callback with jsonRequest
    var funcCallback = function(returnValue){
      if (!returnValue){
        ul.parentNode.removeChild(ul);
        return;
      }
      
      // Clean
	    Ajax.Tree._disposeUL(ul);
	    Util.cleanDOMElements(ul,true);
	    ul.innerHTML = returnValue;
      
      // Update DragDrop
  	  if($(ajaxSuffix).hasClassName('dragdrop') && Ajax.Tree.enableDragNDrop){
  	    setTimeout(function(){ Ajax.Tree._initDragDrop(ul); },10);
  	  }
  	  
  	  // Call custom callback
  	  if (customCallback){
  	    customCallback();
  	  }
    
      Ajax.Tree.invokeDownloadChildrenHook(ajaxSuffix, ul);
    }
    
    // Init custom exception handle
    var funcException = function(){
      ul.parentNode.removeChild(ul);
      Ajax.Tree.toggleOpenClose(img);
    }

     // Run JSON Request
    jsonRequest.rpc       = funcRPC;
    jsonRequest.callback  = funcCallback;
    jsonRequest.exception = funcException;
    jsonRequest.asyncJsonCall();
    
  },  
  
  /**
   * Rename the given category using ajax call
   * Refresh the full category tree in ajax
   * 
   * @param ajaxSuffix String that represents the main UL id
   * @param catId The JCMS Category id
   * @param value The new category name in current language
   */
  rename: function(ajaxSuffix, catId, value){
    JcmsLogger.debug('TreeCat','rename():', ajaxSuffix, catId, value);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(); 
    
    // Init RPC with jsonRequest callback
    var funcRPC = function(){
      Ajax.Tree._getRpcTree(ajaxSuffix).rename(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); }, catId, value);
    };
        
    // Run JSON Request
    jsonRequest.rpc      = funcRPC;
    jsonRequest.callback = Ajax.Tree._callbackRefresh;
    jsonRequest.asyncJsonCall();
  },
  
  /**
   * Add a new category using ajax call
   * Refresh the full category tree in ajax
   * 
   * @param ajaxSuffix String that represents the main UL id
   * @param catId The JCMS Category id
   * @param value The category name to add in current language
   */
  addSubCat: function(ajaxSuffix, catId, value){
    JcmsLogger.debug('TreeCat','addSubCat():', ajaxSuffix, catId, value);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(); 
    
    // Init RPC with jsonRequest callback
    var funcRPC = function(){
      Ajax.Tree._getRpcTree(ajaxSuffix).addSubCat(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, value);
    };

    // Run JSON Request
    jsonRequest.rpc        = funcRPC;
    jsonRequest.callback   = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect, catId); };
    jsonRequest.asyncJsonCall();
  },
  
  /**
   * Add a sibling category using ajax call
   * Refresh the full category tree in ajax
   * 
   * @param ajaxSuffix String that represents the main UL id
   * @param catId The JCMS Category id
   * @param value The category name to add in current language
   */
  addSiblingCat: function(ajaxSuffix, catId, value){
    JcmsLogger.debug('TreeCat','addSiblingCat():', ajaxSuffix, catId, value);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(); 
    
    // Init RPC with jsonRequest callback
    var funcRPC = function(){
      Ajax.Tree._getRpcTree(ajaxSuffix).addSiblingCat(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, value);
    };

    // Run JSON Request
    jsonRequest.rpc        = funcRPC;
    jsonRequest.callback   = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect); };
    jsonRequest.asyncJsonCall();
  },
  
 /**
  * Remove a category using ajax call
  * Refresh the full category tree in ajax
  * 
  * @param ajaxSuffix String that represents the main UL id
  * @param catId The JCMS Category id
  */
  remove: function(ajaxSuffix, catId){
    JcmsLogger.debug('TreeCat','remove():', ajaxSuffix, catId);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(); 
    
    // Init RPC with jsonRequest callback
    var funcRPC = function(){
      Ajax.Tree._getRpcTree(ajaxSuffix).remove(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId);
    };

    // Run JSON Request
    jsonRequest.rpc        = funcRPC;
    jsonRequest.callback   = Ajax.Tree._callbackRefresh;
    jsonRequest.asyncJsonCall();
  },
  
  
 /**
  * Update the parent of a givent category
  * Refresh the full category tree in ajax
  * 
  * @param ajaxSuffix String that represents the main UL id
  * @param catId The JCMS Category id
  */
  setParent: function(ajaxSuffix, catId, parentId){
    JcmsLogger.debug('TreeCat','setParent():', ajaxSuffix, catId, parentId);
    
    // Init Json Request
    var jsonRequest = new JcmsJsonRequest(); 
    
    // Init RPC with jsonRequest callback
    var funcRPC = function(){
      Ajax.Tree._getRpcTree(ajaxSuffix).setParent(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, parentId);
    };

    // Run JSON Request
    jsonRequest.rpc        = funcRPC;
    jsonRequest.callback   = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect, parentId); };
    jsonRequest.asyncJsonCall();
  },
  
  /**
   * Refresh an Ajax TreeCat
   * 1. Parse tree to record checked nodes.
   * 2. Remove all children of main branch.
   * 3. Retrieve a new Tree using Ajax.Tree.downloadChildren
   * 4. Update main branch
   * 
   * If UL.TreeCat has className 'follow' then the tree will open (not select)
   * the given branch ids then follow the Ahref link.
   *
   * If UL.TreeCat has className 'follow' AND 'fire' then the tree will not 
   * follow the href link but instead fire a jcms:click event
   * (this event is observe by util.js / observeClass).
   * 
   * @param ajaxSuffix String that represents the main UL id
   * @param ids an array of ids to add to checked/open elements
   *        or a single id (that will follow url after refresh)
   */
  refresh: function(ajaxSuffix, ids, checked){
    
    JcmsLogger.debug('TreeCat','refresh():',ajaxSuffix,ids);
    
    Ajax.setWaitState(true);
    
    var ul  = $(ajaxSuffix);
    if (ids){
      ids = Object.isArray(ids) ? $A(ids) : $A([ids]);
    }
    
    // Search checked categories
    var openedArray  = new Array();
    var checkedArray = new Array();
    
    $A(ul.getElementsByTagName('INPUT')).each(function(elm, idx){
      if (elm.checked){
        checkedArray.push(elm.value);
        return;
      }

      // Remove disabled ids
      if (ids && elm.disabled && ids.indexOf(elm.value) >= 0){
       ids = ids.without(elm.value);
      }
    });
    
    // Search opened nodes
    $A(ul.select('LI.open')).each(function(elm, idx){
      var node = elm.down();
      if (node){
        var nodeCatId = $(node).getJcmsId();
        openedArray.push(nodeCatId);
        JcmsLogger.debug('TreeCat', 'Opened:', nodeCatId);
      }
    });
    
    
    // Add given additional checked ids
    if (ids){
      openedArray = openedArray.concat(ids);
      if (checked && !ul.hasClassName('follow')){
        checkedArray = checkedArray.concat(ids);
      }
    }
    
    // Removing all children
    Ajax.Tree._disposeUL(ul);
	  Util.cleanDOMElements(ul,true);

    // Append message
    ul.innerHTML = "<li><img src='s.gif' class='loading'/> Loading...</li>";
    

    // Prepare custom callback follow
    var customCallback = null; 
    if (ul.hasClassName('follow') && ids && ids.length == 1 ){
      customCallback = function(){  Ajax.Tree._followRefreshCallback(ajaxSuffix, ids); }
    }
    
    // Dowload children  
    Ajax.Tree.downloadChildren(null, ajaxSuffix, ul, checkedArray, openedArray, customCallback);

  },
  
  /**
   * Called by refresh() for Treecat with class 'follow'
   * @param ajaxSuffix String that represents the main UL id
   * @param ids the id to work with to find URL to follow
   */
  _followRefreshCallback: function(ajaxSuffix, ids){
    var treecat = $(ajaxSuffix);
    var elms = treecat.select('IMG.ID_'+ids);
    if (!elms || !elms[0]){
      JcmsLogger.warn('TreeCat','Id not found',ids);
      return;
    }
    var img = elms[0];
    var ahref = img.next('A');
    if (treecat.hasClassName('fire')) {
      // Fire a custom jcms:click event (this event is observed by util.js / Util.observeClass)
      JcmsLogger.debug('TreeCat', 'fire jcms:click', ahref);
      ahref.fire('jcms:click');
    } else if (ahref) {
      document.location = ahref.href;
    }
  },
  
  // ------------------------------------------------------------------------------------
  //  Utility Function
  // ------------------------------------------------------------------------------------
   
   
  
  /**
   * Makes AJAX call to import all the children 
   * for the given img LI and given AJAX suffix.
   * 
   * @param img the clicked image
   */
  _importChildren: function(img) {
    var li = $(img.parentNode);
    if (li.hasClassName('imported')) {
      return;
    }
    
    Ajax.setWaitState(true,img);
    
    // Set wait icon
    var ul = document.createElement('UL');
    ul.innerHTML = "<li><img src='s.gif' class='loading'/> Loading...</li>";
    li.appendChild(ul);
    
    // Mark this branch as imported
    li.addClassName('imported');
    
    // Asynchronous call
    var ajaxSuffix = $(img).fastUp('UL', 'TreeCat').id;
    Ajax.Tree.downloadChildren(img, ajaxSuffix, ul);
  },
   
  /**
   * Toggles className 'open' and 'close' on parent LI of image.
   * 
   * @param img the clicked image
   */
  toggleOpenClose: function(img) {
    var li = $(img.parentNode);
    li.toggleClassName('close');
    li.toggleClassName('open');
    Ajax.Tree._importChildren(img);
  },
  
  /**
   * Returns the ajaxSuffix of the parent UL of 
   * the given element.
   * 
   * @param the children element
   */
  getAjaxSuffix: function(elm){
    
    if (!elm){
      return;
    }
    
    var elm = $(elm);
    
    // Hook for an element handle click as a proxy
    if (elm.id && elm.id.indexOf('proxy') >= 0){
      return elm.id.substring(6);
    }
    
    var ul = elm.fastUp('UL', 'TreeCat');
    if (!ul){
      return;
    }
    
    JcmsLogger.debug('TreeCat','getAjaxSuffix():', ul.id);
    return ul.id;
  },
  
  /**
   * Returns the category id for the given element
   * 
   * @param elm the element to work with
   */
  getCategoryId: function(elm){
    if (!elm){
      return;
    }
    
    if (elm.tagName == 'LI'){
      return Ajax.Tree.getCategoryId(elm.down(0));
    }
    else if (elm.tagName == 'IMG'){
      return $(elm).getJcmsId();
    }
    else{
      return Ajax.Tree.getCategoryId(elm.up('UL.TreeCat LI'));
    }
  },
  
  // ------------------------------------------------------------------------------------
  //  Internal Function
  // ------------------------------------------------------------------------------------
  
  /**
   * Returns the correct JSON-RPC AjaxTree from given ajaxSuffix
   * 
   * @param ajaxSuffix the ajaxSuffix or null
   */
  _getRpcTree: function(ajaxSuffix){
    if (!ajaxSuffix){
      return JcmsJsContext.getJsonRPC().AjaxTree;
    }
    else {
      return JcmsJsContext.getJsonRPC()['AjaxTree'+ajaxSuffix];
    }
  },
  
  /**
   * Convenient callback function called to refresh 
   * treecat after json-rpc call
   * 
   * @param ajaxSuffix the ajaxsuffix used to refresh tree
   * @param returnEffect the Effect (not used)
   */
  _callbackRefresh: function(ajaxSuffix, returnEffect, openId){ 
    JcmsLogger.debug('TreeCat','Callback Refresh',ajaxSuffix,returnEffect,openId);
    if (ajaxSuffix){
      if (openId){
        var ids = new Array();
        ids.push(openId);
        Ajax.Tree.refresh(ajaxSuffix, ids);
      }
      else {
        Ajax.Tree.refresh(ajaxSuffix);
      }
    }
  },
  
  /**
   * Convenient function used to handle RPC reponse.
   * If RPC returns a message then call alert and finish JsonRequest job
   * else finish JsonRequest Job with the given return value.
   * 
   * @param msg the error message
   * @param returnValue the value to return if there is no errors
   */
  _handleRPCResponse: function(jsonRequest, msg, returnValue){
    if (msg){
      alert(msg);
      jsonRequest.asyncJsonCallBack();
      return;
    }
    jsonRequest.asyncJsonCallBack(returnValue);
  },
  
  /**
   * Init Sortable on TreeCat
   */
  _initTreeCat: function(event){
    
    if (!Ajax.Tree.enableDragNDrop){
      return;
    }
    
    var t0 = new Date().getTime();
    
    if (event && event.memo && event.memo.wrapper){
      var wrapper =  $(event.memo.wrapper); if (!wrapper){ return; }
      wrapper.select('UL.TreeCat').each(function(elm, idx){  
        if (elm.hasClassName('dragdrop')){
          Ajax.Tree._initDragDrop(elm);
        }
      });
      return;
    }
    
    $$('UL.TreeCat').each(function(elm, idx){  
      if (elm.hasClassName('dragdrop')){
        Ajax.Tree._initDragDrop(elm);
      }
    });
    
    var t1 = new Date().getTime();
    JcmsLogger.info('TreeCat', 'Init TreeCat', ' in '+(t1-t0)+' ms');
  },
  
  /**
   * Clean the cached dragdropArray
   */
  dispose: function(){
    
    // Because underlaying function only dispose DrangNDrop
    if (!Ajax.Tree.enableDragNDrop){
      return;
    }
    
    $$('UL.TreeCat').each(function(elm, idx){ 
      Ajax.Tree._disposeUL(elm);
    });
    Ajax.Tree.dragdropArray.clear();
  },
  
  /**
   * Clean LI items
   * 
   * @param li the li to clean
   */
  _disposeLI: function(li){
    // Remove previous draggable
    if (li.treedrag){
      li.treedrag.destroy();
      li.treedrag = null;
    }
    
    // Remove previous droppable
    Droppables.remove(li);
  },
  
  /**
   * Remove all drag/drop object bind to LIs under given UL
   * 
   * @param ul the ul element to work with
   */
  _disposeUL: function(ul){
    var ul = $(ul);
    
    // Remove LI Drag/Drop
    $A(ul.getElementsByTagName('LI')).each(function(li,idx){
      Ajax.Tree._disposeLI(li);
      Ajax.Tree.dragdropArray.splice(idx,1);
    });
    
    // Remove onclick events
    $A(ul.getElementsByTagName('A')).each(function(ahref,idx){
      ahref.onclick = null;
    });
  },
  
  /**
   * Inits all drag/drop object bind to LIs under given UL
   * 
   * @param ul the ul element to work with
   */
  _initDragDrop: function(ul){
    
    var ul = $(ul);
    $A(ul.getElementsByTagName('LI')).each(function(li,idx){
      
      var li = $(li);
      if (!li.hasClassName('mng')){ return; }
      
      var anchor = li.down('IMG.visual');
      Event.observe(anchor,'mousedown',Ajax.Tree._lazyDrag);
      
      
      // Init Droppable
      Droppables.remove(li);
      Droppables.add(li,{greedy:false, onHover:Ajax.Tree._onHover, onDrop:Ajax.Tree._onDrop});
      
      // Cache LI's for clean purpose
      Ajax.Tree.dragdropArray.push(li);
    });
  },
  
  /**
   * Used by _initDragDrop() to lazy initialise draggable onmousedown
   * @param event the mousedown event
   */
  _lazyDrag: function(event){
    
    var anchor = Event.element(event);
    
    JcmsLogger.debug('TreeCat','_lazyDrag',anchor);
      
    // Destroy previous Draggable
    var li = anchor.fastUp('LI');
    if (li.treedrag){
      li.treedrag.destroy();
    }
    
    // Init new Draggable
    li.treedrag = new Draggable(li,{revert: true, handle:'visual'});
    
    // Remove observer
    Event.stopObserving(anchor,'mousedown',Ajax.Tree._lazyDrag);
    
    // Kick start dnd
    li.treedrag.initDrag(event);
    Draggables.updateDrag(event);
  },
  
  /**
   * Convenient internal function to stop Drag/Drop events
   * 
   * @param event the event
   */
  _stopEvent: function(event){
    Event.stop(event);
  },
  
  /**
   * Internal function called by _initDragDrop()
   * 
   * @param dragElm the object that encapsulate the real element
   */
  _onChange: function(dragElm){
    
    // Skip quickly
    if (!dragElm.element.dragObserver){
      JcmsLogger.debug('TreeCat','Start dragObserver');
      Event.observe(dragElm.element, 'click', Ajax.Tree._stopEvent);
      dragElm.element.dragObserver = true;
    }
  },
  
  /**
   * Internal function called by _initDragDrop()
   * 
   * @param dragElm Dragged element
   * @param dropElm Dropped element
   * @param overlap Percetage of overlaping
   */
  _onHover: function(dragElm, dropElm, overlap){
    
    // Skip quickly
    if (dropElm.className.indexOf('droppable') >= 0){
      return;
    }
    
    // Remove previous droppable if item changes
    if (dragElm.oldDropElm && dragElm.oldDropElm != dropElm){
      dragElm.oldDropElm.removeClassName('droppable');
    }
    
    // Do not highlight it's own parent
    // if (dragElm.up('LI') == dropElm){
    //   return;
    // }
    
    // Add class name
    dropElm.addClassName('droppable');
    dragElm.oldDropElm = dropElm;
  },
  
  /**
   * Internal function called by _initDragDrop()
   * 
   * @param dragElm Dragged element
   * @param dropElm Dropped element
   * @param event the Drag/Drop event
   */
  _onDrop: function(dragElm, dropElm, event){
    
    // Stop event handler
    // JcmsLogger.debug('TreeCat','Stop dragObserver');
    // Event.stopObserving(dragElm, 'click', Ajax.Tree._stopEvent);
    
    // Remove previous droppable if item changes
    if (dragElm.oldDropElm){
      dragElm.oldDropElm.removeClassName('droppable');
    }
    
    // Do not work on parent element
    if (dragElm.fastUp('LI') === dropElm){
      return;
    }
    
    // Ask question
    var dnd = top.confirm(I18N.glp('msg.confirm.dragdrop'));
    
    // Make AJAX Call
    if (dnd){
      dragElm.hide();  // Hide element
      Ajax.Tree.setParent(Ajax.Tree.getAjaxSuffix(dragElm), 
                          Ajax.Tree.getCategoryId(dragElm), 
                          Ajax.Tree.getCategoryId(dropElm));
    }
  }
});

  // ------------------------------------------------------------------------------------
  //  EVENT OBSERVER
  // ------------------------------------------------------------------------------------

  Event.observe(window, 'load'  , function(){ Ajax.Tree._initTreeCat.defer(); });
  
  if (navigator.appVersion.match(/\bMSIE\b/)){
    Event.observe(window, 'unload', function() { Ajax.Tree.dispose(); }, false);
  }
  Event.observe(document, 'refresh:after',  Ajax.Tree._initTreeCat, false);
  Event.observe(document, 'refresh:before', Ajax.Tree.dispose, false);
  
