// widget Dropdown class

(function($) { // hide the namespace

/* jQuery extend now ignores nulls! */
function extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props)
		if (props[name] == null || props[name] == undefined)
			target[name] = props[name];
	return target;
};

var __log_counter = 0;

function Dropdown () {
  this.debug = false; // Change this to true to start debugging
  this._nextId = 0;
  this._instances = []; // list of instances
  this._defaults = {
  };
}

$.extend( Dropdown.prototype,
  {

    /* Debug logging (if enabled). */
    log: function () {
      if (this.debug && window.console && typeof console.log === "function" ) {
        __log_counter = ++__log_counter % 1024;
        var args = [];
        for( i = 0, l = arguments.length; i < l; i++ ) {
          args.push( arguments[i] );
        }
        args.unshift( '[' + __log_counter + ']' );
        console.debug.apply( console, args);
      }
    },

      /* Register a new widget dropdown instance - with custom settings. */
    _register: function(inst) {
      this.debug && this.log('Dropdown._register(', inst, ') called()');
      var id = this._nextId++;
      this._instances[id] = inst;
      return id;
    },

    create: function(target, settings){
      this.debug && this.log('Dropdown.create(', target, ',', settings, ') called()');
      // check for settings on the control itself - in namespace 'dropdown:'
      var inlineSettings = null;
      for (attrName in this._defaults) {
        var attrValue;
        if( target != document ) {
          attrValue = target.getAttribute('dropdown:' + attrName);
        } else {
          attrValue = target['dropdown:' + attrName];
        }
        if (attrValue) {
          inlineSettings = inlineSettings || {};
          try {
            inlineSettings[attrName] = eval(attrValue);
          } catch (err) {
            inlineSettings[attrName] = attrValue;
          }
        }
      }
      var instanceSettings = (inlineSettings ? 
        $.extend(settings || {}, inlineSettings) : settings);
      var instance = new DropdownInstance(instanceSettings);

      var dropdownNamespace = 'dropdown_' + instance._id;

      var container = instanceSettings.container;
      var background = instanceSettings.background;
      var iframe = null;
      if( 0 && $.browser.msie ) {
        iframe = document.createElement('iframe');
        iframe.id = dropdownNamespace + '-iframe';
        iframe.style.display = 'none';
        if( background ) {
          background.after( iframe );
        } else {
          container.after( iframe );
        }
        iframe = $(iframe);
        instanceSettings.iframe = iframe;
      }

      if( instanceSettings.match && instanceSettings.match.length == 2 ) {
        this.debug && this.log('instanceSettings.match detected:', instanceSettings.match);
        container && container.css('position','absolute');
        background && background.css('position','absolute');
        iframe && iframe.css('position','absolute');
        var t_offset = $(instanceSettings.match[0]).offset();
        var s_offset = $(instanceSettings.match[1]).offset();
        var e_offset = { left: ( t_offset.left - s_offset.left ), top: ( t_offset.top - s_offset.top ) };
        var offset_fix = function(e_offset){
          return function( index, coords ) {
            return { left: ( coords.left + e_offset.left ), top: ( coords.top + e_offset.top ) };
          };
        }(e_offset);
        container && container.offset(offset_fix);
        background && background.offset(offset_fix);
        iframe && iframe.offset(offset_fix);
      }

      // z-index to imaginary
      var zIndex = instanceSettings.zIndex || 100;
      container && container.css('z-index', -1 * zIndex);
      background && background.css('z-index', -1 * (zIndex+1));
      iframe && iframe.css('z-index', -1 * (zIndex+2));
      // display unless already
      container && container.show();
      background && background.show();
      iframe && iframe.show();
      // visibility to hidden
      container && container.css('visibility','hidden');
      background && background.css('visibility','hidden');
      iframe && iframe.css('visibility','hidden');
      // workaround for jitters top and left
      container && container.css('top','0px').css('left','0px');
      background && background.css('top','0px').css('left','0px');
      iframe && iframe.css('top','0px').css('left','0px');
      // fix width and height
      instanceSettings.fixWidth && background && background.css('width', container.width());
      iframe && iframe.css('width', container.width());
      instanceSettings.fixHeight && background && background.css('height', container.height());
      iframe && iframe.css('height', container.height());

      var mouseoverEventName = 'mouseover.' + dropdownNamespace;
      var mouseoutEventName = 'mouseout.' + dropdownNamespace;
      var mousemoveEventName = 'mousemove.' + dropdownNamespace;
      var clickEventName = 'click.' + dropdownNamespace;
      var mouseoveroutEventName = mouseoverEventName + ', ' + mouseoutEventName;
      var mouseoveroutmoveEventName = mouseoverEventName + ', ' + mouseoutEventName + ', ' + mousemoveEventName;

      var mouseEnterLeaveEventName = mouseoveroutmoveEventName;
      if( instanceSettings.controlMouseEnterShow || instanceSettings.controlMouseLeaveHide ) {
        this.debug && this.log('instanceSettings.controlMouseEnterShow and instanceSettings.controlMouseLeaveHide detected');
        instance._mouseEnterLeaveUsed = true;

        var mouseEventHandlerFunc = function(instance){
          return function(e){
            $.dropdown.debug && $.dropdown.log( 'mouseEventHandlerFunc called ', e );
            //console.log( e, e.pageX, e.pageY );
            var isOver = false;
            for( var i = 0, l = instance._zones.length; i < l; i++ ) {
              var zone = instance._zones[i];
              if( zone.minX != null && zone.maxX != null && zone.minY != null && zone.maxY != null ) {
                if( zone.minX <= e.pageX && zone.maxX >= e.pageX && zone.minY <= e.pageY && zone.maxY >= e.pageY ) {
                  isOver = true;
                  break;
                }
              }
            }
            $.dropdown.debug && $.dropdown.log( 'mouseEventHandlerFunc isOver: ', isOver );
            if( !instance._showed ) {
              if( isOver ) $.dropdown._show( instance );
            } else {
              if( !isOver ) $.dropdown._hide( instance );
            }
          }
        }(instance);

        instance._mouseMoveOnFunc = function(target, instance, mouseEnterLeaveEventName, mouseEventHandlerFunc){
          return function(state){
            $.dropdown.debug && $.dropdown.log('mouseMoveOn: ', mouseEnterLeaveEventName, ', state: ', state );
            //$( target ).unbind( mouseEnterLeaveEventName );
            var controls;
            instance._mouseWaitState = state;
            switch( instance._mouseWaitState ) {
              case 'enter':
                controls = instance._settings.controlMouseEnterShow;
                break;
              case 'leave':
                controls = instance._settings.controlMouseLeaveHide;
                break;
            }
            for( var i = 0, l = controls.length; i < l; i++ ) {
              $( controls ).unbind( mouseEnterLeaveEventName );
            }
            instance._zones = [];
            for( var i = 0, l = controls.length; i < l; i++ ) {
              var control = controls[i];
              var controlOffset = $(control).offset();
              var controlWidth = $(control).width();
              var controlHeight = $(control).height();
              var zone = {};
              zone.minX = Math.floor(controlOffset.left);
              zone.maxX = Math.ceil(controlOffset.left + controlWidth);
              zone.minY = Math.floor(controlOffset.top);
              zone.maxY = Math.ceil(controlOffset.top + controlHeight);
              //console.log(instance._id,control,zone.minX,zone.maxX,zone.minY,zone.maxY);
              instance._zones.push( zone );
            }
            //console.log( instance._zones );
            //$( target ).bind( mouseEnterLeaveEventName, mouseEventHandlerFunc );
            for( var i = 0, l = controls.length; i < l; i++ ) {
              $( controls[i] ).bind( mouseEnterLeaveEventName, mouseEventHandlerFunc );
            }
            // listen on container too at leave waiting state
            if( instance._mouseWaitState == 'leave' ) {
              $( instance._settings.container ).bind( mouseEnterLeaveEventName, mouseEventHandlerFunc );
            }
          };
        }(target, instance, mouseEnterLeaveEventName, mouseEventHandlerFunc);
        instance._mouseMoveOffFunc = function(target, instance, mouseEnterLeaveEventName, mouseEventHandlerFunc){
          return function(){
            $.dropdown.debug && $.dropdown.log('mouseMoveOff: ', mouseEnterLeaveEventName);
            var controls;
            switch( instance._mouseWaitState ) {
              case 'enter':
                controls = instance._settings.controlMouseEnterShow;
                break;
              case 'leave':
                controls = instance._settings.controlMouseLeaveHide;
                break;
            }
            for( var i = 0, l = controls.length; i < l; i++ ) {
              $( controls ).unbind( mouseEnterLeaveEventName );
            }
            instance._zones = [];
            // drop listener on container too at leave waiting state
            if( instance._mouseWaitState == 'leave' ) {
              $( instance._settings.container ).bind( mouseEnterLeaveEventName, mouseEventHandlerFunc );
            }
          };
        }(target, instance, mouseEnterLeaveEventName, mouseEventHandlerFunc);

        // add mouse event handlers
        instance._mouseMoveOnFunc('enter');
        
        // on resize window or change a scale need do recalculate a sensitive zones
        1 && jQuery(window).resize(
          function(instance){
            return function(){
              instance._mouseMoveOffFunc( instance._mouseWaitState );
              instance._mouseMoveOnFunc( instance._mouseWaitState );
            };
          }(instance)
        );
      }

      // setup click event handlers
      var clickShowFunc = function(instance, target, dropdownNamespace){
        return function(e){
          $.data(target,'event.click.'+dropdownNamespace+'.source',dropdownNamespace);
          if( !instance._showed ) {
            $.dropdown._show( instance );
          }
        }
      }(instance,target,dropdownNamespace);
      var clickHideFunc = function(instance, target, dropdownNamespace){
        return function(e){
          $.data(target,'event.click.'+dropdownNamespace+'.source',dropdownNamespace);
          if( instance._showed ) {
            $.dropdown._hide( instance );
          }
        }
      }(instance,target,dropdownNamespace);

      if( instanceSettings.controlClickShow ) {
        this.debug && this.log('instanceSettings.controlClickShow detected:', instanceSettings.controlClickShow);
        var controlClickShow = instanceSettings.controlClickShow;
        $( controlClickShow ).unbind( clickEventName );
        $( controlClickShow ).bind( clickEventName, clickShowFunc );
      }
      if( instanceSettings.controlClickHide ) {
        this.debug && this.log('instanceSettings.controlClickHide detected:', instanceSettings.controlClickHide);
        var controlClickHide = instanceSettings.controlClickHide;
        $( controlClickHide ).unbind( clickEventName );
        $( controlClickHide ).bind( clickEventName, clickHideFunc );
      }
      if( instanceSettings.hideOnClickOut ) {
        $( target ).unbind( clickEventName );
        $( target ).bind( clickEventName,
          function(instance,target){
            return function(e){
              $.dropdown.debug && $.dropdown.log( 'click, event:', e );
              $.dropdown.debug && $.dropdown.log( 'event.click.'+dropdownNamespace+'.source:', $.data(target,'event.click.'+dropdownNamespace+'.source') );
              if( $.data(target,'event.click.'+dropdownNamespace+'.source') == dropdownNamespace ) {
                $.removeData(target,'event.click.'+dropdownNamespace+'.source');
              } else {
                instance._showed && $.dropdown._hide( instance );
              }
            }
          }(instance,target,dropdownNamespace)
        );
      }
    },

    _show: function(instance){
      this.debug && this.log('Dropdown._show(', instance, ') called()');
      var dropdownNamespace = 'dropdown_' + instance._id;
      var container = instance._settings.container;
      var background = instance._settings.background;
      var iframe = instance._settings.iframe;
      if( 0 && $.browser.msie && !iframe ) {
        iframe = $('#'+ dropdownNamespace + '-iframe');
      }
      if( instance._settings.groupName && instance._settings.hideOnSwitchGroup ) {
        for( var i = 0, l = this._instances.length; i < l; i++ ) {
          if( this._instances[i] != instance ) {
            if( this._instances[i]._settings.groupName == instance._settings.groupName ) {
              this._hide( this._instances[i] );
            }
          }
        }
      }
      if( instance._mouseEnterLeaveUsed && instance._mouseMoveOffFunc ) instance._mouseMoveOffFunc();
      if( instance._settings.onShowFunc ) {
        instance._settings.onShowFunc();
      }
      if( instance._settings.matchOnShow && instance._settings.match && instance._settings.match.length == 2 ) {
        this.debug && this.log('instance._settings.matchOnShow detected:', instance._settings.match);
        var t_offset = $(instance._settings.match[0]).offset();
        var s_offset = $(instance._settings.match[1]).offset();
        var e_offset = { left: ( t_offset.left - s_offset.left ), top: ( t_offset.top - s_offset.top ) };
        var offset_fix = function(e_offset){
          return function( index, coords ) {
            return { left: ( coords.left + e_offset.left ), top: ( coords.top + e_offset.top ) };
          };
        }(e_offset);
        container && container.offset(offset_fix);
        background && background.offset(offset_fix);
        iframe && iframe.offset(offset_fix);
      }
      // z-index to real
      var zIndex = instance._settings.zIndex || 100;
      iframe && iframe.css('z-index', zIndex);
      background && background.css('z-index', zIndex+1);
      container && container.css('z-index', zIndex+2);
      // visibility to visible
      iframe && iframe.css('visibility','visible');
      background && background.css('visibility','visible');
      container && container.css('visibility','visible');
      if( instance._mouseEnterLeaveUsed && instance._mouseMoveOnFunc ) instance._mouseMoveOnFunc('leave');
      // store a state
      instance._showed = true;
    },

    _hide: function(instance){
      this.debug && this.log('Dropdown._hide(', instance, ') called()');
      var dropdownNamespace = 'dropdown_' + instance._id;
      var container = instance._settings.container;
      var background = instance._settings.background;
      var iframe = instance._settings.iframe;
      if( 0 && $.browser.msie && !iframe ) {
        iframe = $('#'+ dropdownNamespace + '-iframe');
      }
      if( instance._mouseEnterLeaveUsed && instance._mouseMoveOffFunc ) instance._mouseMoveOffFunc();
      if( instance._settings.onHideFunc ) {
        instance._settings.onHideFunc();
      }
      // visibility to hidden
      container && container.css('visibility','hidden');
      background && background.css('visibility','hidden');
      iframe && iframe.css('visibility','hidden');
      // workaround for jitters top and left
      container && container.css('top','0px').css('left','0px');
      background && background.css('top','0px').css('left','0px');
      iframe && iframe.css('top','0px').css('left','0px');
      // z-index to imaginary
      var zIndex = instance._settings.zIndex || 100;
      container && container.css('z-index', -1*zIndex);
      background && background.css('z-index', -1*(zIndex+1));
      iframe && iframe.css('z-index', -1*(zIndex+2));
      if( instance._mouseEnterLeaveUsed && instance._mouseMoveOnFunc ) instance._mouseMoveOnFunc('enter');
      // store a state
      instance._showed = false;
    },

    terminator: function(){ return null; }

  }
);

/* Individualised settings for widget dropdown functionality applied to one or more related elements
   Instances are managed and manipulated through the Dropdown manager. */
function DropdownInstance (settings) {
	this._id = $.dropdown._register(this);
	this._settings = extendRemove(settings || {}); // clone
  this._showed = false;
  this._mouseEnterLeaveUsed = false;
  this._mouseWaitState = null;
  this._zones = [];
}

$.dropdown = new Dropdown(); // singleton instance

})(jQuery);
