/*!
 * jQuery Selectbox plugin 0.1.3
 *
 * Copyright 2011, Dimitar Ivanov (http://www.bulgaria-web-developers.com/projects/javascript/selectbox/)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 * 
 * Date: Wed Jul 29 23:20:57 2011 +0200
 */
(function ($, undefined) {
  var PROP_NAME = 'selectbox',
    FALSE = false,
    TRUE = true;
  /**
   * Selectbox manager.
   * Use the singleton instance of this class, $.selectbox, to interact with the select box.
   * Settings for (groups of) select boxes are maintained in an instance object,
   * allowing multiple different settings on the same page
   */
  function Selectbox() {
    this._state = [];
    this._defaults = { // Global defaults for all the select box instances
      classHolder: "sbHolder",
      classHolderDisabled: "sbHolderDisabled",
      classSelector: "sbSelector",
      classOptions: "sbOptions",
      classGroup: "sbGroup",
      classSub: "sbSub",
      classDisabled: "sbDisabled",
      classToggleOpen: "sbToggleOpen",
      classToggle: "sbToggle",
      speed: 200,
      effect: "slide", // "slide" or "fade"
      onChange: null, //Define a callback function when the selectbox is changed
      onOpen: null, //Define a callback function when the selectbox is open
      onClose: null //Define a callback function when the selectbox is closed
    };
  }
  
  $.extend(Selectbox.prototype, {
    /**
     * Is the first field in a jQuery collection open as a selectbox
     * 
     * @param {Object} target
     * @return {Boolean}
     */
    _isOpenSelectbox: function (target) {
      if (!target) {
        return FALSE;
      }
      var inst = this._getInst(target);
      return inst.isOpen;
    },
    /**
     * Is the first field in a jQuery collection disabled as a selectbox
     * 
     * @param {HTMLElement} target
     * @return {Boolean}
     */
    _isDisabledSelectbox: function (target) {
      if (!target) {
        return FALSE;
      }
      var inst = this._getInst(target);
      return inst.isDisabled;
    },
    /**
     * Attach the select box to a jQuery selection.
     * 
     * @param {HTMLElement} target
     * @param {Object} settings
     */
    _attachSelectbox: function (target, settings) {
      if (this._getInst(target)) {
        return FALSE;
      }
      var $target = $(target),
        self = this,
        inst = self._newInst($target),
        sbHolder, sbSelector, sbToggle, sbOptions,
        s = FALSE, optGroup = $target.find("optgroup"), opts = $target.find("option"), olen = opts.length;
        
      $target.attr("sb", inst.uid);
        
      $.extend(inst.settings, self._defaults, settings);
      self._state[inst.uid] = FALSE;
      $target.hide();
      
      function closeOthers() {
        var key, uid = this.attr("id").split("_")[1];
        for (key in self._state) {
          if (key !== uid) {
            if (self._state.hasOwnProperty(key)) {
              if ($(":input[sb='" + key + "']")[0]) {
                self._closeSelectbox($(":input[sb='" + key + "']")[0]);
              }
            }
          }
        }
      }
      
      sbHolder = $("<div>", {
        "id": "sbHolder_" + inst.uid,
        "class": inst.settings.classHolder
      });
      
      sbSelector = $("<a>", {
        "id": "sbSelector_" + inst.uid,
        "href": "#",
        "class": inst.settings.classSelector,
        "click": function (e) {
          e.preventDefault();
          closeOthers.apply($(this), []);
          var uid = $(this).attr("id").split("_")[1];
          if (self._state[uid]) {
            self._closeSelectbox(target);
          } else {
            self._openSelectbox(target);
          }
        }
      });
      
      sbToggle = $("<a>", {
        "id": "sbToggle_" + inst.uid,
        "href": "#",
        "class": inst.settings.classToggle,
        "click": function (e) {
          e.preventDefault();
          closeOthers.apply($(this), []);
          var uid = $(this).attr("id").split("_")[1];
          if (self._state[uid]) {
            self._closeSelectbox(target);
          } else {
            self._openSelectbox(target);
          }
        }
      });
      sbToggle.appendTo(sbHolder);

      sbOptions = $("<ul>", {
        "id": "sbOptions_" + inst.uid,
        "class": inst.settings.classOptions,
        "css": {
          "display": "none"
        }
      });
      
      /* Kochan & Partner Height Fix - (c) sfi */
      if ($target.children().length > 10) 
      {
        sbOptions.css('height', '270px')
                 .css('overflow-y', 'auto')
                 .css('overflow-x', 'hidden');
      }
      /* END */
      
      $target.children().each(function(i) {
        var that = $(this), li, config = {};
        if (that.is("option")) {
          getOptions(that);
        } else if (that.is("optgroup")) {
          li = $("<li>");
          $("<span>", {
            "text": that.attr("label")
          }).addClass(inst.settings.classGroup).appendTo(li);
          li.appendTo(sbOptions);
          if (that.is(":disabled")) {
            config.disabled = true;
          }
          config.sub = true;
          getOptions(that.find("option"), config);
        }
      });
      
      function getOptions () {
        var sub = arguments[1] && arguments[1].sub ? true : false,
          disabled = arguments[1] && arguments[1].disabled ? true : false;
        arguments[0].each(function (i) {
          var that = $(this),
            li = $("<li>"),
            child;
          if (that.is(":selected")) {
            sbSelector.text(that.text());
            s = TRUE;
          }
          if (i === olen - 1) {
            li.addClass("last");
          }
          if (!that.is(":disabled") && !disabled) {
            child = $("<a>", {
              "href": "#" + that.val(),
              "rel": that.val(), 
              "text": that.text(),
              "click": function (e) {
                e.preventDefault();
                var t = sbToggle,
                  uid = t.attr("id").split("_")[1];
                self._changeSelectbox(target, $(this).attr("rel"), $(this).text());
                self._closeSelectbox(target);
              }
            });
            if (sub) {
              child.addClass(inst.settings.classSub);
            }
            child.appendTo(li);
          } else {
            child = $("<span>", {
              "text": that.text()
            }).addClass(inst.settings.classDisabled);
            if (sub) {
              child.addClass(inst.settings.classSub);
            }
            child.appendTo(li);
          }
          li.appendTo(sbOptions);
        });
      }
      
      if (!s) {
        sbSelector.text(opts.first().text());
      }
      
      $.data(target, PROP_NAME, inst);
      
      sbSelector.appendTo(sbHolder);
      sbOptions.appendTo(sbHolder);     
      sbHolder.insertAfter($target);
    },
    /**
     * Remove the selectbox functionality completely. This will return the element back to its pre-init state.
     * 
     * @param {HTMLElement} target
     */
    _detachSelectbox: function (target) {
      var inst = this._getInst(target);
      if (!inst) {
        return FALSE;
      }
      $("#sbHolder_" + inst.uid).remove();
      $.data(target, PROP_NAME, null);
      $(target).show();     
    },
    /**
     * Change selected attribute of the selectbox.
     * 
     * @param {HTMLElement} target
     * @param {String} value
     * @param {String} text
     */
    _changeSelectbox: function (target, value, text) {
      var inst = this._getInst(target),
        onChange = this._get(inst, 'onChange');
      $("#sbSelector_" + inst.uid).text(text);
      $(target).find("option[value='" + value + "']").attr("selected", TRUE);
      if (onChange) {
        onChange.apply((inst.input ? inst.input[0] : null), [value, inst]);
      } else if (inst.input) {
        inst.input.trigger('change');
      }
    },
    /**
     * Enable the selectbox.
     * 
     * @param {HTMLElement} target
     */
    _enableSelectbox: function (target) {
      var inst = this._getInst(target);
      if (!inst || !inst.isDisabled) {
        return FALSE;
      }
      $("#sbHolder_" + inst.uid).removeClass(inst.settings.classHolderDisabled);
      inst.isDisabled = FALSE;
      $.data(target, PROP_NAME, inst);
    },
    /**
     * Disable the selectbox.
     * 
     * @param {HTMLElement} target
     */
    _disableSelectbox: function (target) {
      var inst = this._getInst(target);
      if (!inst || inst.isDisabled) {
        return FALSE;
      }
      $("#sbHolder_" + inst.uid).addClass(inst.settings.classHolderDisabled);
      inst.isDisabled = TRUE;
      $.data(target, PROP_NAME, inst);
    },
    /**
     * Get or set any selectbox option. If no value is specified, will act as a getter.
     * 
     * @param {HTMLElement} target
     * @param {String} name
     * @param {Object} value
     */
    _optionSelectbox: function (target, name, value) {
      var inst = this._getInst(target);
      if (!inst) {
        return FALSE;
      }
      //TODO check name
      inst[name] = value;
      $.data(target, PROP_NAME, inst);
    },
    /**
     * Call up attached selectbox
     * 
     * @param {HTMLElement} target
     */
    _openSelectbox: function (target) {
      var inst = this._getInst(target);
      //if (!inst || this._state[inst.uid] || inst.isDisabled) {
      if (!inst || inst.isOpen || inst.isDisabled) {
        return;
      }
      var el = $("#sbOptions_" + inst.uid),
        viewportHeight = parseInt($(window).height(), 10),
        offset = $("#sbHolder_" + inst.uid).offset(),
        scrollTop = $(window).scrollTop(),
        height = el.prev().height(),
        diff = viewportHeight - (offset.top - scrollTop) - height / 2,
        onOpen = this._get(inst, 'onOpen');
      el.css({
        "top": height + "px",
        "maxHeight": (diff - height) + "px"
      });
      inst.settings.effect === "fade" ? el.fadeIn(inst.settings.speed) : el.slideDown(inst.settings.speed);
      $("#sbToggle_" + inst.uid).addClass(inst.settings.classToggleOpen);
      this._state[inst.uid] = TRUE;
      inst.isOpen = TRUE;
      if (onOpen) {
        onOpen.apply((inst.input ? inst.input[0] : null), [inst]);
      }
      $.data(target, PROP_NAME, inst);
    },
    /**
     * Close opened selectbox
     * 
     * @param {HTMLElement} target
     */
    _closeSelectbox: function (target) {
      var inst = this._getInst(target);
      //if (!inst || !this._state[inst.uid]) {
      if (!inst || !inst.isOpen) {
        return;
      }
      var onClose = this._get(inst, 'onClose');
      inst.settings.effect === "fade" ? $("#sbOptions_" + inst.uid).fadeOut(inst.settings.speed) : $("#sbOptions_" + inst.uid).slideUp(inst.settings.speed);
      $("#sbToggle_" + inst.uid).removeClass(inst.settings.classToggleOpen);
      this._state[inst.uid] = FALSE;
      inst.isOpen = FALSE;
      if (onClose) {
        onClose.apply((inst.input ? inst.input[0] : null), [inst]);
      }
      $.data(target, PROP_NAME, inst);
    },
    /**
     * Create a new instance object
     * 
     * @param {HTMLElement} target
     * @return {Object}
     */
    _newInst: function(target) {
      var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1');
      return {
        id: id, 
        input: target, 
        uid: Math.floor(Math.random() * 99999999),
        isOpen: FALSE,
        isDisabled: FALSE,
        settings: {}
      }; 
    },
    /**
     * Retrieve the instance data for the target control.
     * 
     * @param {HTMLElement} target
     * @return {Object} - the associated instance data
     * @throws error if a jQuery problem getting data
     */
    _getInst: function(target) {
      try {
        return $.data(target, PROP_NAME);
      }
      catch (err) {
        throw 'Missing instance data for this selectbox';
      }
    },
    /**
     * Get a setting value, defaulting if necessary
     * 
     * @param {Object} inst
     * @param {String} name
     * @return {Mixed}
     */
    _get: function(inst, name) {
      return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name];
    }
  });

  /**
   * Invoke the selectbox functionality.
   * 
   * @param {Object|String} options
   * @return {Object}
   */
  $.fn.selectbox = function (options) {
    
    var otherArgs = Array.prototype.slice.call(arguments, 1);
    if (typeof options == 'string' && options == 'isDisabled') {
      return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
    }
    
    if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') {
      return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
    }
    
    return this.each(function() {
      typeof options == 'string' ?
        $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this].concat(otherArgs)) :
        $.selectbox._attachSelectbox(this, options);
    });
  };
  
  $.selectbox = new Selectbox(); // singleton instance
  $.selectbox.version = "0.1.3";
})(jQuery);
