var com = com||{};
com.wo2online = com.wo2online||{};

com.wo2online.Menu = function() {
  var FRAMERATE = 20;
  var ANIMATION_IN_LENGTH = 0.8;
  var ANIMATION_OUT_LENGTH = 0.3;
  var MOUSEOUT_TIMEOUT = 100;
  var MOUSEIN_TIMEOUT = 100;
  var EASE_IN_FACTOR = -0.2;
  var EASE_OUT_FACTOR = -0.2;
  var getElement = com.wo2online.getElement;
  
  
  /**
   * The Menu class, it will slide in and out an element by means of adjusting its height. 
   *
   * @param {String|HTMLElement}  activator An id or an element reference that will invoke the sliding menu
   * @param {String|HTMLElement}  element An id or an element reference that holds the actual sliding content
   * @param {Object} options Optional options parameter. This may contain:
   *        {Number} frameratesilent (defaults to 20 frames per second)
   *        {Number} animationLength (defaults to 0.2 seconds)
   *        {Number} easeFactor (defaults to 1)
   * @namespace com.wo2online
   * @class Menu
   * @constructor
   */
  var Menu = function(activator,element,options) {
    if (activator && element) {
      this.activator = getElement(activator);
      this.element = getElement(element);
      this.options = options||{};
      this.initialize();
      Menu.instances.push(this);
      this.animator = new com.wo2online.Animator(null,null,{animationLength: ANIMATION_IN_LENGTH, easeInFactor: EASE_IN_FACTOR, easeOutFactor: EASE_OUT_FACTOR});
    }
  };
  
  /**
   * holds all instances
   * @property instances
   * @type Array
   * @static
   */  
  Menu.instances = [];
  
  /** 
    * mask animator
    */
  Menu.maskAnimator = new com.wo2online.Animator(null,null,{animationLength: ANIMATION_IN_LENGTH, easeInFactor: EASE_IN_FACTOR, easeOutFactor: EASE_OUT_FACTOR});
  Menu.maskOpacity = 0;
  /**
   * indicates whether at least one menu is active (animating or visible)
   * @method active
   * @returns Boolean
   * @static
   */   
  Menu.active = function() {
    var i,inst;
    for (i=0; (inst=Menu.instances[i]); i++) {
      if (inst.isActive) {
        return true;
      }
    }
    return false;
  };
  
  /**
   * indicates whether at least one menu is visible
   * @method visible
   * @returns Boolean
   * @static
   */  
  Menu.visible = function() {
    var i,inst;
    for (i=0; (inst=Menu.instances[i]); i++) {
      if (inst.isVisible) {
        return true;
      }
    }
    return false;
  };
  
  /**
   * @method _clearMouseoutTimer
   * @private
   */
  function _clearMouseoutTimer() {
    if (this.mouseoutTimer) {
      window.clearTimeout(this.mouseoutTimer);
      this.mouseoutTimer = false;
    }
  }
  
  /**
   * @method _clearMouseinTimer
   * @private
   */
  function _clearMouseinTimer() {
    if (this.mouseinTimer) {
      window.clearTimeout(this.mouseinTimer);
      this.mouseinTimer = false;
    }
  }
  
  /**
   * initializes the thing, binds event handlers to activator
   * @method initialize
   */  
  Menu.prototype.initialize = function() {
    var self = this;
    var fs = this.element.getElementsByTagName('div')[0];
    fs.style.position = 'absolute';
    fs.style.bottom = '0px';
    this.defaultHeight = fs.offsetHeight;
    this.animationTimer = false;    
    this.mouseoutTimer = false;
        
    this.activator.onmouseover = function() {
      if (!self.disabled) {
        _clearMouseoutTimer.call(self);
        self.mouseinTimer = window.setTimeout(function() {
          self.slideIn();
        },MOUSEIN_TIMEOUT);
      }
    };//);
    this.activator.onmouseout = function() {
      if (!self.disabled) {
        _clearMouseoutTimer.call(self);
        _clearMouseinTimer.call(self);
        self.mouseoutTimer = window.setTimeout(function() {
          self.slideOut();
        },MOUSEOUT_TIMEOUT);
      }
    };//);
    this.hide();
  };
  
  /**
   * fires method name prepended with "on" if it exists, returns result
   * this is used for basic event type system
   * @method trigger
   * @param {String} eventName
   * @returns Boolean
   */   
  Menu.prototype.trigger = function(eventName) {
    var args = [].concat(arguments);
    args.splice(0,1);
    if (this['on'+eventName]) {
      var res = this['on'+eventName].apply(this,args);
      return (res!==false);
    }
  };
  
  /**
   * stops running animation and creates a new one and plays it;
   * @method createAnimator
   * @param {Number} start pixel menu value
   * @param {Number} end pixel menu value
   * @param {Number} al animation length in seconds
   * @param {function} endCallBack callback function to execute when animation is done;
   * @returns Animator
   */   
  Menu.prototype.createAnimator = function(start,end,al,endCallBack) {
    var element = this.element;
    var mask = this.options.mask;
    if (this.animator && this.animator.playing) {
      this.animator.reset();
      Menu.maskAnimator.stop();
    }
    this.animator.options.animationLength = al;
    this.animator.onAnimate = function(value) {
      //calculate actual value
      var pos = start+value*(end-start);
      element.style.height = pos+'px';
    };
    this.animator.onAnimationEnd = endCallBack;
    this.animator.play();
    
    var mstart = Menu.maskOpacity;
    var mend = (end<=0)?0:0.5;
    Menu.maskAnimator.options.animationLength = al;
    Menu.maskAnimator.onAnimate = function(value) {
      if (mask) {
        var mv = mstart+value*(mend-mstart);
        Menu.maskOpacity = mv;
        mask.style.opacity = mv;
        mask.style.filter = 'alpha(opacity='+100*mv+')';
      }
    };
    Menu.maskAnimator.play();
    
    return this.animator;
  };
  
  /**
   * instantly shows the menu (no animation)
   * @method show
   */   
  Menu.prototype.show = function() {
    this.trigger('beforeshow');
    this.element.style.height = this.defaultHeight+'px';
    this.isVisible = true;
    this.isActive = true;
    this.trigger('aftershow');
  };
  
  /**
   * instantly hides the menu (no animation)
   * @method hide
   */   
  Menu.prototype.hide = function() {
    this.trigger('beforehide');
    this.element.style.height = '0px';
    this.isVisible = false;
    this.isActive = false;
    this.trigger('afterhide');
  };
  
  /**
   * gradually shows the menu (animated)
   * @method slideIn
   */
  Menu.prototype.slideIn = function() {
    if (this.isSlidingIn) {return;}
    var self = this;
    self.trigger('beforeshow');
    self.trigger('beforeslidein');
    var start = this.element.clientHeight;
    var end = this.defaultHeight;
    self.isActive = true;
    self.isSlidingIn = true;
    self.isSlidingOut = false;
    
    self.createAnimator(start,end,this.options.animationInLength||ANIMATION_IN_LENGTH,function() {
      self.show();
      self.isVisible = true;
      self.isSlidingIn = false;
      self.trigger('afterslidein');
      self.trigger('aftershow');
    });
  };
  
  /**
   * gradually hides the menu (animated)
   * @method slideOut
   */
  Menu.prototype.slideOut = function() {
    if (this.isSlidingOut) {return;}
    var self = this;
    self.trigger('beforehide');
    self.trigger('beforeslideout');
    var start = this.element.clientHeight;
    var end = 0;
    self.isVisible = false;
    self.isSlidingIn = false;
    self.isSlidingOut = true;
    
    self.createAnimator(start,end,this.options.animationInLength||ANIMATION_OUT_LENGTH,function() {
      self.hide();
      self.isActive = false;
      self.isSlidingOut = false;
      self.trigger('afterslideout');
      self.trigger('afterhide');
    });
  };
  
  return Menu;
}();