var AjaxLoader = {
  activeRequests: [], // simple array to track when requests are active
  morphDuration: 700, // duration of the effect (in milliseconds)... must be greater than fadeDuration
  fadeDuration: 350, // duration of the effect (in milliseconds)
  cache: new Cache(),
  cacheDuration: 15, // duration to store items in cache (in minutes)

  /**
   * Makes a simple Ajax call that doesn't implicitly update the user interface.
   * All server events, external css, and eval scripts in the response will be processed however. */
  loadWithoutUpdate: function(url) {
    try{AjaxActivity.showIndicator();}catch(e){};
    this.activeRequests.push(url);

    new Ajax.Request(url, {
      asynchronous: true, 
      evalScripts: false,
      onComplete: function(request) {
        AjaxLoader.onNoUpdateLoadComplete(request, url);
      }
    });
  },
  
  /**
   * Makes a simple Ajax call that doesn't implicitly update the user interface.
   * Accepts an optional callback function that will be invoked upon completion of the Ajax request.
   * The callback's method signature should accept the html string returned from the Ajax request.
   * All server events, external css, and eval scripts in the response will be processed
   * before the callback is invoked. */
  loadWithCallback: function(url, callback) {
    try{AjaxActivity.showIndicator();}catch(e){};
    this.activeRequests.push(url);
    new Ajax.Request(url, {
      asynchronous: true, 
      evalScripts: false,
      onComplete: function(request) {
        AjaxLoader.onNoUpdateLoadComplete(request, url, callback);
      }
    });
  },

  /** DEPRECATED, use loadUrl instead! */
  loadPartial: function(element, partial, objectClassName, id, morphOptions, canCache) {
    var url = "/pages/ajax_content/" + id + "?partial=" + partial + "&class_name=" + objectClassName;
    this.loadUrl(element, url, morphOptions, canCache);
  },
  
  loadUrl: function(element, url, morphOptions, canCache) {
    var outerElement = $(element);
    if (!outerElement) return; // element is not in the DOM
    var elementId = outerElement.id;
    if (!elementId) return; // element has no id
 
    canCache = canCache || false;
 
 
    // init optional vars
    var stringMorphOptions = null;
    var hashMorphOptions = null;
    if (morphOptions) {
      if (typeof(morphOptions) == "string") {
        stringMorphOptions = morphOptions;
        hashMorphOptions = morphOptions.evalJSON();
        
      } else {
        stringMorphOptions = $H(morphOptions).toJSON();
        hashMorphOptions = morphOptions;
      }
    }
    
    try{AjaxActivity.showIndicator();}catch(e){};
    
    // Fade all child nodes 
    var children = outerElement.childNodes;
    var len = children.length;
    for (var i =0; i < len; i++) {
      var child = children[i];
      if (child.style) {
        var fade = new Fx.Styles(child, {duration: this.fadeDuration, transition: Fx.Transitions.sineInOut });
        fade.custom({"opacity": [1, 0.0]});
      }
    }
    
    var innerElement = outerElement.innerElement;
    
    // Append inner element for first time
    if (!innerElement) {
      setTimeout("AjaxLoader.appendInnerElement('" + elementId + "', '" + url + "', '" + stringMorphOptions + "', " + canCache + ")", this.fadeDuration + 10);
      return;
    } else {
      // Remove old content 
      if (innerElement.innerHTML.strip().length > 0) {
        setTimeout("var el = $('" + innerElement.id + "'); el.innerHTML = ''; el.style.display = 'none';", this.fadeDuration + 10);
      }
    }
    
    // Ignore if there is already a pending request
    if (this.getActiveRequest(elementId)) {
      return;
    }
    this.activeRequests.push(elementId);
    outerElement.morphOptions = hashMorphOptions;
    
    // Apply morphing effects
    if (hashMorphOptions) {
      var morph = new Fx.Styles(outerElement, {duration: this.morphDuration, transition: Fx.Transitions.sineInOut });
      morph.custom(hashMorphOptions);
    }
    
    innerElement.morphing = true;
    setTimeout("$('" + innerElement.id + "').morphing = false", this.morphDuration + 10);
    
    // cache the current contents before loading new
    if (outerElement.url) {
      var cacheKey = innerElement.id + ":" + outerElement.url;
      if (this.cache.getItem(cacheKey)) {
        var now = new Date();
        var expireAt = new Date(now.getTime() + (AjaxLoader.cacheDuration * 60000));
        AjaxLoader.cache.setItem(cacheKey, innerElement.innerHTML, {expirationAbsolute: expireAt});
      }
    }
    
    // Load content
    innerElement.canCache = canCache;
    innerElement.cacheKey = innerElement.id + ":" + url;
    innerElement.cachedContent = null;
    if (canCache) innerElement.cachedContent = this.cache.getItem(innerElement.cacheKey);
    if (innerElement.cachedContent) innerElement.cachedContent = innerElement.cachedContent.strip();
    
    if (innerElement.cachedContent && innerElement.cachedContent.length > 0) {
      setTimeout("AjaxLoader.showCachedContent('" + innerElement.id + "')", this.morphDuration + 10);
    } else {
      innerElement.AjaxLoaderCallback = function(request) {
        AjaxLoader.onLoadComplete(request, innerElement);
      }
      setTimeout("AjaxLoader.sendRequest('" + innerElement.id + "', '" + url + "')", this.morphDuration + 10);
    }
    
    outerElement.url = url;
  },
  
  sendRequest: function(innerElementId, url) {
    var innerElement = $(innerElementId);
    
    new Ajax.Updater(innerElement, url, {
      asynchronous: true, 
      evalScripts: false,
      onComplete: innerElement.AjaxLoaderCallback
    });
  },
  
  appendInnerElement: function(elementId, url, morphOptions, canCache) {
    var outerElement = $(elementId);
    var innerElementId = elementId + "AJAXLoader";
    outerElement.innerHTML = "";
    var innerElement = document.createElement("span");
    innerElement.id = innerElementId;
    innerElement.style.display = "none";
    outerElement.appendChild(innerElement);
    outerElement.innerElement = innerElement;
    this.loadUrl(elementId, url, morphOptions, canCache);
  },
  
  onNoUpdateLoadComplete: function(request, url, callback) {
    if (callback) {
      try {
        callback.call(window, request.responseText.stripScripts());
      } catch (ex) {
        alert("AjaxLoader.onNoUpdateLoadComplete\n\nFailed while executing callback!\n\n" + callback);
      }
    }
    
    var responseText = EventManager.fireServerGeneratedEvents(request);
    this.removeActiveRequest(url);
    
    // Hide the activity indicator
    if (this.activeRequests.length == 0) {
      try{AjaxActivity.hideIndicator();}catch(e){};
    }
  },
  
  onLoadComplete: function(request, innerElement) {
    var outerElement = innerElement.parentNode;

    // Apply reverse morph for outer element
    if (outerElement.morphOptions) {
      var morph = new Fx.Styles(outerElement, {duration: AjaxLoader.morphDuration, transition: Fx.Transitions.sineInOut });
      var morphOptions = {};
      for (var key in outerElement.morphOptions) {
        morphOptions[key] = outerElement.morphOptions[key].reverse();
      }
      morph.custom(morphOptions);
    }

    EventManager.fireServerGeneratedEvents(request);
    setTimeout("AjaxLoader.showLoadedContent('" + innerElement.id + "')", AjaxLoader.morphDuration * .75);
    setTimeout("AjaxLoader.wrapUp('" + innerElement.id + "')", AjaxLoader.morphDuration);
    
    // cache the content
    if (innerElement.canCache) {
      var now = new Date();
      var expireAt = new Date(now.getTime() + (AjaxLoader.cacheDuration * 60000));
      AjaxLoader.cache.setItem(innerElement.cacheKey, innerElement.innerHTML, {expirationAbsolute: expireAt});
    }
  },

  showCachedContent: function(innerElementId) {
    try {
    var innerElement = $(innerElementId);
    var outerElement = innerElement.parentNode;
    
    innerElement.innerHTML = innerElement.cachedContent;

    // Apply reverse morph for outer element
    if (outerElement.morphOptions) {
      var morph = new Fx.Styles(outerElement, {duration: AjaxLoader.morphDuration, transition: Fx.Transitions.sineInOut });
      var morphOptions = {};
      for (var key in outerElement.morphOptions) {
        morphOptions[key] = outerElement.morphOptions[key].reverse();
      }
      morph.custom(morphOptions);
    }

    EventManager.fireEvent("LoadComplete");
    setTimeout("AjaxLoader.showLoadedContent('" + innerElement.id + "')", AjaxLoader.morphDuration * .75);
    setTimeout("AjaxLoader.wrapUp('" + innerElement.id + "')", AjaxLoader.morphDuration);
    } catch (ex) {
      alert("AjaxLoader: showCachedContent failed!\n\n" + ex.name + ":" + ex.message);
    }
  },

  showLoadedContent: function(innerElementId) {
    try {
    var innerElement = $(innerElementId);
    innerElement.style.display = '';
    
    // Fade inner element back in
    var appear = new Fx.Styles(innerElement, {duration: AjaxLoader.fadeDuration, transition: Fx.Transitions.sineInOut });
    appear.custom({"opacity": [0.0, 1]});
    } catch (ex) {
      alert("AjaxLoader: showLoadedContent failed!\n\n" + ex.name + ":" + ex.message);
    }
  },
  
  wrapUp: function(innerElementId) {
    try {
    var innerElement = $(innerElementId);
    var outerElement = innerElement.parentNode;

    this.removeActiveRequest(outerElement.id);
  
    // Hide the activity indicator
    if (this.activeRequests.length == 0) {
      try{AjaxActivity.hideIndicator();}catch(e){};
    }
    } catch(ex) {
      alert("AjaxLoader: wrapUp failed!\n\n" + ex.name + ":" + ex.message);
    }
  },
  
  removeActiveRequest: function(elementId) {
    var index = this.indexOfActiveRequest(elementId);
    if (index >= 0) {
      this.activeRequests.splice(index, 1);
    }
    
    return null;
  },
  
  getActiveRequest: function(elementId) {
    var index = this.indexOfActiveRequest(elementId);
    if (index >= 0) {
      return this.activeRequests[index];
    }
    
    return null;
  },
  
  indexOfActiveRequest: function(elementId) {
    var len = this.activeRequests.length;
    for (var i = 0; i < len; i++) {
      if (this.activeRequests[i] == elementId) {
        return i;
      }
    }
    
    return -1;
  }
}