/*******************************************************************************
 * Moozy
 * 
 * Imports:
 * 
 *   Chain
 *   Class
 *   Element
 *   Fx
 *   Options
 *   Window
 * 
 */



/*******************************************************************************
 * Chain class extension
 */
Chain.implement({
  /**
   * Retrieves the number of functions already added to this chain.
   */
  count: function() {
    if ($defined(this.$chain)) {
      return this.$chain.length;
    } else {
      return 0;
    }
  }
});



/*******************************************************************************
 * Element class extension
 */
Element.implement({
  /**
   * Add the specified value to the given property if not already present.
   */
  addProperty: function(key, value) {
    var values = this.getProperty(key);
    
    if (values !== null) {
      values = values.split(' ');
    } else {
      values = [];
    }
    
    if (!values.contains(value)) {
      values.push(value);
      values = values.join(' ');
      
      this.setProperty(key, values);
    }
  },
  /**
   * Appends the specified suffix to the given property.
   */
  appendProperty: function(key, suffix) {
    var value = this.getProperty(key);
    
    if (value !== null) {
      value = value + suffix;
    } else {
      value = suffix;
    }
    
    this.setProperty(key, value);
  },
  /**
   * Removes the following property from any style attribute set on this 
   * element.
   */
  removeStyle: function(property) {
    // Formats the property name
    property = property.camelCase();
    
    // Resets the property value
    if ($defined(this.style[property])) {
      this.style[property] = "";
    }
  }
});



/*******************************************************************************
 * String class extension
 */
String.implement({
  /**
   * Checks whether this string ends with the specified suffix or not.
   */
  endsWith: function(suffix) {
    // Retrieves the position of the last specified token
    var index = this.lastIndexOf(suffix);
    
    // Determines if this token was found and located at the end of this string
    return ((index + suffix.length) === this.length); 
  },
  /**
   * Checks whether this string is empty or not.
   */
  isEmpty: function() {
    return (this.valueOf() === ""); 
  },
  /**
   * Checks whether this string starts with the specified prefix or not.
   */
  startsWith: function(prefix) {
    // Retrieves the position of the first specified token
    var index = this.indexOf(prefix);
    
    // Determines if this token was found and located at the beginning of this 
    // string
    return (index === 0);
  }
});



/*******************************************************************************
 * Window class extension
 */
Window.implement({
  /**
   * Retrieves the first element whose tag name matches the tag or the CSS
   * selectors specified.
   */
  $E: function(selector){
    return this.document.getElement(selector);
  },
  /**
   * Loads the specified url in a new window and uses if necessary a failover
   * mechanism.
   */
  load: function(url, focus) {
    // Handles the case where no parameter was provided for the last argument
    focus = $defined(focus) ? focus : false;
    
    // Defines the name of the new window
    var name = "_blank";
    
    // Opens the target url in a new window
    var handler = this.open(url, name);
    
    // Defaults to a workaround to circumvent the browser popup blocker
    if (!$defined(handler) || (handler === null)) {
      // Opens just a blank window
      handler = this.open('', name);
      
      if ($defined(handler) && (handler !== null)) {
        // Defines now the new url of this window
        handler.location.href = url;
      } else {
        // Updates the current window itself (at last)
        this.location = url;
      }
    }
    
    // Gives back focus to main window if required
    if (focus && (handler !== false)) {
      handler.opener.focus();
    }
  }
});



/*******************************************************************************
 * Fx.Opacity class definition
 */
Fx.Opacity = new Class({
  Extends: Fx.Tween,
  /**
   * Creates a new opacity effect.
   */
  initialize: function(element, options){
    this.parent(element, $merge(options, {property: "opacity"}));
  },
  /**
   * Checks whether the element this opacity effect is applied on is visible or
   * not.
   */
  isDisplayed: function() {
    var opacity = this.element.getStyle("opacity");
    
    if ($defined(this.from) && $defined(this.to)) {
      var from = this.from[0].value;
      var to = this.to[0].value;
      
      // Makes sure a transition is not started to hide the element this opacity
      // effect is applied on
      return ((opacity > 0) && ((from !== to) && (to > 0)));
    } else {
      return (opacity > 0);
    }
  }
});



/*******************************************************************************
 * Fx.Ramp class definition
 */
Fx.Ramp = new Class({
  Implements: Options,
  options: {
    duration: 300,
    speed: 300
  },
  initialize: function(elements, options) {
    // Merges the default options with the ones given as parameters
    this.setOptions(options);
    
    // Declares chain of events
    var chain = new Chain();
    
    // Associates a visual effect to each element and adds it to the chain
    elements.each(function(element) {
      // Initializes visual effect
      var effect = new Fx.Tween(element, {property: "opacity", duration: this.options.duration});
      
      // Hides the element at startup
      effect.set(0);
      
      // Stacks up the visual effect into the chain
      chain.chain(function() {
        effect.start(1);
      });
    }.bind(this));
    
    // Save reference for later use
    this.chain = chain;
  },
  start: function() {
    // Retrieves the total number of effects
    var count = this.chain.count();
    
    // Runs every effects in the chain sequentially
    for (i = 0; i < count; i++) {
      this.chain.callChain.delay(i * this.options.speed, this.chain);
    }
  }
});
