/**
This library is intended to simplify use of AJAX in portlets (or any <div> displaying dynamic content). The principle is to refresh a given <div>
when a given action occured.
 
Usage: 
------
   * Add js/jalios/ajax-refresh.js
   * The <div> to be refreshed is tagged as "ajax-refresh-div" with a CSS class
   * The refresh trigger (eg a link or a form button) is tagged as "ajax-refresh" with a CSS class
   * When the user clicks on the trigger, the content of the <div> is refreshed with the target URL (i.e. href of the link or action + params of the form). 
   * The CSS class "confirm" can be used to ask the user to confirm the refresh of the <div>. 
     The title of the trigger element is used for the confirm message.

Example 1: 
----------
Refresh with a link

<% jcmsContext.addJavaScript("js/jalios/ajax-refresh.js"); %>  
<div class="ajax-refresh-div">
  <%= new Date() %><br/>
  <a class="ajax-refresh" href="testLink.jsp">Refresh</a>
</div>

Example 2: 
----------
Refresh with a form (with confirm)

<% jcmsContext.addJavaScript("js/jalios/ajax-refresh.js"); %>  
<div class="ajax-refresh-div">
	<% String text = request.getParameter("text"); %>
	<% if (Util.notEmpty(text)) { %>
	  Your text: <%= text %>
	<% } %>
	<form action="testForm.jsp">
		<input type="text" name="text" />
		<input type="submit" class="ajax-refresh confirm" title="Please, confirm the refresh" />
	</form>
</div>

Advanced:
---------

   * ajax-lazy:          CSS on DIV to lazy load Portlet (ajax-lazy-custom to do your own stuff)  
   * ajax-refresh:       CSS on Link to trigger AHREF, INPUT, BUTTON, ...
   * ajax-action:        CSS on Link indicating an action (no history) 
   * ajax-nofocus:       CSS on DIV prevent scroll to top of the DIV (or options noscroll)
   * ajax-noerror:       CSS on DIV prevent alert() for error
   * ajax-lazy-chunk:    CSS on Response IMG will fire an event onload of IMG
   * ajax-periodical-{0} CSS on DIV to refresh periodically time in seconds. Use it with ajax-lazy.
   * 
   * Technical:
   * ajax-refresh-job:   CSS on DIV used to lock refresh
   * ajax-refresh-url:   CSS on First AHREF used has URL (Magic Trick)
   * ajax-refresh-debug: CSS on DIV used to debug/display refreshed area
   * ajax-lazy-load:     CSS on Response used to remove JS, CSS initialisation
   * 
   * Event:
   * refresh:before       @wrapper the wrapper id Fired before an ajax refresh, @trigger the AHref/Input Id
   * refresh:after        @wrapper the wrapper id Fired after an ajax refresh
   * refresh:lazy         @wrapper the wrapper id, @lazy the elment id Fired on each loaded ajax-lazy-chunk 
   * refresh:asynchronous @asynchronous flag boolean, @trigger the AHref/Input Id @wrapper the wrapper id, @callback the callback to call 
*/

// ---------------------------------------
//  AJAX REFRESH 
// ---------------------------------------


'JCMS.ajax.Refresh'.namespace({
  
  histIdx : 0,     // History index
  histInit: false, // History initalization
  
  init: function() {
    var t0 = new Date().getTime();
    
    // Register RSH
    JCMS.History.observe(JCMS.ajax.Refresh._getRefreshHistory.bind());
    
    // Register on click
    Util.observeDocument('click', JCMS.ajax.Refresh.refresh.bindAsEventListener());
    
    // Ajax Lazy -> Quid Lazy > Lazy ?
    $$('DIV.ajax-refresh-div.ajax-lazy').each(function(elm, idx){
      var jcmsId = elm.getJcmsId();
      if (!jcmsId){ return; } 
      var url = JcmsJsContext.getContextPath()+'/jcore/portal/ajaxPortal.jsp?portletId='+jcmsId + JCMS.ajax.Refresh._getJcmsUsage(elm,'&usage=');
      JCMS.ajax.Refresh._request(elm, url, {history: false});
    });
    
    JCMS.ajax.Refresh._includeLazy();
    
    var t1 = new Date().getTime();
    JcmsLogger.info('AjaxRefresh', 'Init AjaxRefresh', ' in '+(t1-t0)+' ms');
  },
  
  _includeLazy: function(event){
    var fragment = JCMS.ajax.Refresh.getFragment(event);
    if (!fragment){ return; }
    if (!document.body){ return; }
    
    fragment.select('A.ajax-include').each(function(ahref,idx){
      ahref.removeClassName('ajax-include');
      var div = new Element('div',  { 'style':'display:none;', 'class':'ajax-include-div'});
      document.body.appendChild(div);
      JCMS.ajax.Refresh._request(div, ahref.href, {history: false, callback: function(refreshDiv){
        ahref.parentNode.replaceChild(refreshDiv,ahref);
        $(refreshDiv).show();
      }});
    });
  },
  
  /**
   * This function dispatches the click on "ajax-refresh" event
   * @param event the event
   * @return false if refresh is performed otherwise true (to follow links)
   */
  refresh: function(event){
    
    if (!Util.isLeftClick(event)){
      return false;
    }
    
    var elm = Event.element(event); 
    if (!JCMS.ajax.Refresh.refreshFromElement(elm)){
      return true;
    }
    
    Event.stop(event);
    return false;
  },
  
  /**
   * This function dispatches the click on "ajax-refresh" elements
   * @param elm the element
   * @param callback (optional)
   * @return true if refresh is performed otherwise false
   */
  refreshFromElement: function(elm, callback){
    
    if (!elm || !elm.fastUp){ return false; }
    
    var func;
 
    // Case 1. Retrieve target elm from a link
    var link = $(elm.fastUp('A', 'ajax-refresh', true));
    if (link) {
      func = JCMS.ajax.Refresh.refreshFromLink;
      elm = link;
    }
    
    // Case 2. Retrieve target elm from a form input
    else if ((elm.tagName == 'INPUT' || elm.tagName == 'BUTTON') && elm.hasClassName('ajax-refresh')) {
      func = JCMS.ajax.Refresh.refreshFromInput;
    } 
    
    // Skip if no ajax-refresh elm
    if (!func) {
      return false;
    }
    
    // Skip if no ajax-refresh-div
    var refreshDiv = JCMS.ajax.Refresh._findRefreshDiv(elm);
    if (!refreshDiv) {
      return false;
    }
    
    // Do not execute the action twice
    if (refreshDiv.hasClassName('ajax-refresh-job')){
      JcmsLogger.warn('AjaxRefresh','Ajax refresh already running');
      return true;
    }
    if (elm.hasClassName('ajax-action')){
      refreshDiv.addClassName('ajax-refresh-job',true);
    }
    
    // Call the function with confirm
    if (elm.hasClassName('confirm')) {
      var refreshId = refreshDiv.identify();
      var modcb = function(confirm) {
        if (!confirm) {
          refreshDiv.removeClassName('ajax-refresh-job');
          return true; 
        } 
        func(elm, refreshDiv, callback);
      }
      JCMS.window.Modal.confirm(elm.title , modcb);
    }
    // Call the function (without confirm)
    else {
      func(elm, refreshDiv, callback);
    }
    
    return true;
  },
  
  /**
   * This function refresh portlets with the given JcmsId
   * Note: should use handleAjaxPortletAction instead
   * @param jcmsId the Jcms Id
   * @param params additional parameters 
   * @param callback (optional)
   * @return false
   */
  refreshPortlet: function(jcmsId,params, callback){
    params = params ? '&'+params : '';
    $$('DIV.ajax-refresh-div.ID_'+jcmsId).each(function(elm, idx){
      var url = JcmsJsContext.getContextPath()+'/jcore/portal/ajaxPortal.jsp?portletId='+jcmsId + JCMS.ajax.Refresh._getJcmsUsage(elm,'&usage=');
      JCMS.ajax.Refresh._request(elm,url+params,{ history: false, callback: callback});
    });
    return false;
  },
  
  /**
   * This function refresh given DIV with the given URL
   * @param refreshDiv the DIV to refresh
   * @param url the url to call (use JcmsJsContext.getContextPath()+'/')
   */
  refreshDIV: function(refreshDiv, url){
    JCMS.ajax.Refresh._refresh(refreshDiv, url, { history: false });
  },
  
  /**
   * This function refresh the div from the clicked link.
   * @param link the link
   * @param refreshDiv the refreshDiv (optional)
   * @param callback (optional)
   * @return true if refresh has been performed
   */
  refreshFromLink: function (link, refreshDiv, callback) {
    
    var history    = !link.hasClassName('ajax-action');
    refreshDiv = refreshDiv || JCMS.ajax.Refresh._findRefreshDiv(link);
    
    return JCMS.ajax.Refresh._refresh(refreshDiv, link.href, { 
      history: history, 
      formParams: false, 
      trigger: $(link).identify(), 
      callback: callback
    });
  },
  
  /**
   * This function refresh the div from the submitted form.
   * @param input the form input
   * @param refreshDiv the refreshDiv (optional)
   * @param callback (optional)
   * @return true if refresh has been performed
   */
  refreshFromInput: function (input, refreshDiv, callback) {
    refreshDiv = refreshDiv || JCMS.ajax.Refresh._findRefreshDiv(input);
    var form = (input.tagName == 'INPUT') ? $(input.form) : input.fastUp('FORM');
    var params = form.serialize({submit: input.name});
    var history = form.method == 'get';
    var actionUrl = form.readAttribute('action'); // do not use form.action as it could interfere if there is an input named "action"
    
    // Override form.action parameter by form value. 
    // According to w3c spec action must not contains request parameters 
    var qs = actionUrl.parseQuery();
    Object.extend(qs,params);
    params = qs;
    
    return JCMS.ajax.Refresh._refresh(refreshDiv, actionUrl, { 
      history: history, 
      formParams: params, 
      trigger: $(input).identify(),
      callback : callback
    });
  },
  
  // -----------------------------------------------------
  //  UTILITY
  // -----------------------------------------------------

  /**
   * Convenient funtion to retrieve the scope of Ajax-Refresh events
   * @param event the ajax-refresh event
   * @return fragment the scope ofrefresh or the body
   */
  getFragment: function(event){
    var fragment = $(document.body);
    if (event && event.memo && event.memo.wrapper){
      fragment = $(event.memo.wrapper);
      if (!fragment){ return false; }
    }
    return fragment;
  },
  
  // -----------------------------------------------------
  //  INTERNAL
  // -----------------------------------------------------
  
  /**
   * Retrieve the usage for the given 
   *
   * @param elm the triggered element
   */
  _getJcmsUsage : function(element, prefix) {
    var prefix = prefix || '';
    var usageClass = $w(element.className).find(function(elm){ 
      return elm.startsWith('USAGE_');
    });
    
    return usageClass ? (prefix+usageClass.substring(6)) : '';
  },
  
  /**
   * Retrieve the ajax-refresh-div froml the current element.
   * This method is smart and can retrieve DIV throught contextual menus
   *
   * @param elm the triggered element
   */
  _findRefreshDiv: function(elm){
    var refreshDiv = elm.fastUp('DIV', 'ajax-refresh-div');
    
    // CtxMenu hack (for ctx links under body tag)
    if (!refreshDiv && CtxMenuManager.latestElement){
      refreshDiv = $(CtxMenuManager.latestElement).fastUp('DIV', 'ajax-refresh-div');
    }
    return refreshDiv
  },
    
  /**
   * Performs the AJAX refresh. If refreshDiv is a Portlet's Div then 
   * refresh the portlet with given parameters.
   * 
   * @param refreshDiv the div to be refreshed
   * @param url the url to be called
   * @param history update browser history
   * @param formParams the form parameters (optional)
   * @param trigger the trigger of the refresh (optional)
   * @deprecated use instead _refresh()
   */
  _ajaxRefresh: function(refreshDiv, url, history, formParams, trigger){
    return JCMS.ajax.Refresh._refresh(refreshDiv, url,{
      history: history,
      formParams: formParams,
      trigger: trigger ? $(trigger).identify() : false
    });
  },
  
  _refresh: function(refreshDiv, url, options){

    refreshDiv = $(refreshDiv); 
    if (!refreshDiv) {
      JcmsLogger.warn("AjaxRefresh", "Cannot retrieve refresh div wrapper");
      return false;
    }
    
    // Magic Portlet Trick
    if (refreshDiv.hasClassName('Portlet') && refreshDiv.getJcmsId()){
      var qs  = url.indexOf('?') < 0 ? '' : url.substring(url.indexOf('?')+1);
      var id  = refreshDiv.getJcmsId();
      var usage = JCMS.ajax.Refresh._getJcmsUsage(refreshDiv);
      url = JcmsJsContext.getContextPath()+'/jcore/portal/ajaxPortal.jsp';
      
      if(options.formParams){
        options.formParams.portletId = id;
        if (usage){
          options.formParams.usage = usage;
        }
      } else {
        if (usage){
          var rg2 = new RegExp('usage=[^&]*','img');
          qs = (qs.match(rg2)) ? qs.replace(rg2,'usage='+usage) 
                               : 'usage='+usage+'&'+qs;
        }
        
        var rg1 = new RegExp('portletId=[^&]*','mg');
        qs = (qs.match(rg1)) ? qs.replace(rg1,'portletId='+id) 
                             : 'portletId='+id+'&'+qs;
        url += '?'+qs;
      }
    } else { // Magic URL Trick
      // if there is <a href="..." class="ajax-refresh-url"></a> below the ajax-refresh-div, use it to find the ajax URL
      var child = $(refreshDiv).fastChild('A', 'ajax-refresh-url');
      if (child) {
     
        var uri = child.href;
        var uri = uri.indexOf('?') < 0 ? uri : uri.substring(0,uri.indexOf('?'));
        
        var qs1 = child.href; qs1 = qs1.indexOf('?') < 0 ? $H() : $H(qs1.parseQuery('&'));
        var qs2 = url;        qs2 = qs2.indexOf('?') < 0 ? $H() : $H(qs2.parseQuery('&'));
        var qs3 = $H();
        
        qs1.each(function(pair){
          if (!pair.value){ return; }
          if (!qs2.get(pair.key)){ qs3.set(pair.key, pair.value); }
        });
        
        var sep = '?';
        url = uri; 
        if (qs3.size() > 0){ url += sep + qs3.toQueryString(); sep = '&'; }
        if (qs2.size() > 0){ url += sep + qs2.toQueryString(); }
      }
    }
    
    var memo = { 
      asynchronous : false,
      trigger: options.trigger, 
      wrapper: refreshDiv.identify(), 
      callback: function(){ JCMS.ajax.Refresh._request(refreshDiv.identify(), url, options); }
    };
    
    // Should we perform an asynchronous call ?
    document.fire('refresh:asynchronous', memo); 
    if (memo.asynchronous){ return false; }
    
    // Jcms Ajax Request
    return JCMS.ajax.Refresh._request(refreshDiv, url, options);
  },
  
  /**
   * Performs the AJAX request
   *.
   * @param refreshDiv the div to be refreshed
   * @param url the url to be called
   * @param history update browser history
   * @param formParams the form parameters (optional)
   * @param trigger the trigger of the refresh (optional)
   * @deprecated use instead _request()
   */
  _ajaxRequest: function(refreshDiv, url, history, formParams, trigger){
    return JCMS.ajax.Refresh._request(refreshDiv, url,{
      history: history,
      formParams: formParams,
      trigger: trigger ? $(trigger).identify() : false
    });
  },
  
  _request: function(refreshDiv, url, options){
  
    var refreshDiv = $(refreshDiv);
    
    var debug = document.location.href.indexOf('debug=true') > 0;
    if (debug){ refreshDiv.addClassName("ajax-refresh-debug"); }
    
    // Set history
    options.history = (options.history == undefined) ? true : options.history;
    
    // Is periodical request
    var periodical = refreshDiv.className.match(/ajax-periodical-(\d+)/);
    if (periodical){ options.history = false; }
    
    // Init Jcms Request
    var jcmsRequest = new JcmsAjaxRequest();
    
    // Init RPC with jcmsRequest callback
    var funcRPC = function(){
      new Ajax.Request(url, {
        evalScripts: true,
        parameters:  options.formParams || {},
        onComplete:  jcmsRequest.asyncJsonCallBack.bind(jcmsRequest),
        onException: jcmsRequest._handleException.bind(jcmsRequest),
        onFailure:   jcmsRequest._handleException.bind(jcmsRequest)
      });
    }

    // Init CallBack
    var funcCallBack = function(transport, returnEffect){ 
      var response = transport.responseText;
      var html     = response.fastStrip();
      var jsCode   = '';
      
      // Extract and remove the div containing lazy javascript and css loading
      var jsHtml = '<div class="ajax-lazy-load">';
      var jsCodeIndexOf = html.indexOf(jsHtml);
      if (jsCodeIndexOf > 0) {
        jsCode = html.substring(jsCodeIndexOf + jsHtml.length,
                                html.lastIndexOf("</div>"));
        html = html.substring(0, jsCodeIndexOf);
      }

      // Remove the ajax-refresh div wrapper
      var rg = new RegExp('^<div[^>]*ajax-refresh[^>]*>(.*)','gi');
      if (html.match(rg)){
        html = html.replace(rg, '$1');
        html = html.substring(0, html.lastIndexOf("</div>"));
      }
      
      // Insert back the lazy loading javascript code
      html += jsCode;
      
      // Fix refresh div
      refreshDiv = $(refreshDiv);
      
      if (!html || html.blank()){
        refreshDiv.removeClassName('ajax-refresh-job');
        Element.removeClassName.delay(2,refreshDiv,"ajax-refresh-debug");
        return;
      }
      
      // Init history
      if (options.history){
        JCMS.ajax.Refresh._initRefreshHistory(refreshDiv);
      }
      
      // ScrollTo DIV
      var trigger = $(options.trigger);
      var tfocus  = trigger ? !trigger.hasClassName('ajax-nofocus') : true;
      if (refreshDiv.viewportOffset().top < 0 && !options.noscroll  && tfocus && !refreshDiv.hasClassName('ajax-nofocus') && !refreshDiv.retrieve('_periodical')){
        refreshDiv.scrollTo();
      }
      
      document.fire('refresh:before',{ wrapper: refreshDiv.identify(), trigger: options.trigger });
      
      // Update DIV content
      refreshDiv.update(html);
      
      // Update history
      if (options.history){
        JCMS.ajax.Refresh._setRefreshHistory(refreshDiv, url, options);
      }
      
      document.fire('refresh:after' ,{ wrapper: refreshDiv.identify() });
      
      
      refreshDiv.select(['.ajax-lazy-chunk']).each(function(elm, idx){
        elm.onload = function(){
          document.fire('refresh:lazy' ,{ wrapper: refreshDiv.identify(), lazy: elm.identify() });
        };
      });
      
      // Callback at last
      if (options.callback){
        options.callback(refreshDiv);
      }
      
      refreshDiv.removeClassName('ajax-refresh-job');
      Element.removeClassName.delay(2,refreshDiv,"ajax-refresh-debug");
    }

    jcmsRequest.rpc      = funcRPC;
    jcmsRequest.callback = funcCallBack;
    jcmsRequest.timeout  = 60000;
    jcmsRequest.quiet    = refreshDiv.hasClassName('ajax-noerror');
    
    if (periodical && periodical.length > 1 && !refreshDiv.retrieve('_periodical')){
      refreshDiv.store('_periodical',jcmsRequest); // Stored to be stoped
      jcmsRequest.quiet = true;
      jcmsRequest.asyncJsonCallPeriodical(parseInt(periodical[1]));
    } else {
      jcmsRequest.asyncJsonCall();
    }
    
    return true;
  },
  
  _initRefreshHistory: function(wrapper){
    if (JCMS.ajax.Refresh.histInit){ return; }
    
    JCMS.ajax.Refresh.histInit = { 
      'wrappperId'  : $(wrapper).identify()
    };
  },
  
  _setRefreshHistory: function(wrapper, url, options){
    
    JCMS.History.add("refresh-" + JCMS.ajax.Refresh.histIdx, { 
      'wrappperId'  : $(wrapper).identify(), 
      'url'         : url,
      'formParams'  : options.formParams
    });

    JCMS.ajax.Refresh.histIdx++;
  },
  
  _getRefreshHistory: function(newLocation, historyData){
    
    // Update with current history data
    if(historyData){
      if (!historyData.wrappperId){
        return;
      }
      
      // Check if id (sets by identify()) has been lost by back button
      var wrapper = $(historyData.wrappperId);
      if (!wrapper){
        JcmsLogger.warn("AjaxRefresh", "Cannot retrieve refresh div wrapper, html id lost by back button");
        return;
      }
      
      JCMS.ajax.Refresh._request($(historyData.wrappperId), historyData.url, { history: false, formParams: historyData.formParams });
      return;
    }
    
    // Update with first backuped data
    if(JCMS.ajax.Refresh.histInit){
      var wrapper = $(JCMS.ajax.Refresh.histInit.wrappperId);
      var jcmsId = wrapper.getJcmsId();
      if (jcmsId){
        var url = JcmsJsContext.getContextPath()+'/jcore/portal/ajaxPortal.jsp?portletId='+jcmsId + JCMS.ajax.Refresh._getJcmsUsage(wrapper,'&usage=');
        JCMS.ajax.Refresh._request(wrapper, url, { history: false });
      } else {
        Popup.reload();
      }
    }
  }
  
});

// ---------------------------------------
//  EVENTS 
// ---------------------------------------

Event.observe(window,   'load',          function() { JCMS.ajax.Refresh.init.defer();         });
Event.observe(document, 'refresh:after', function() { JCMS.ajax.Refresh._includeLazy.defer(); });

