/**
 * --------------------------------------------------------------------
 * jQuery-Plugin "MaxWidthSelect"
 * Copyright (c) 2009 Gabriel Florit, velir.com
 * Dual licensed under the MIT and GPL licenses.
 *
 * Adds max-width functionality to an HTML <select>.
 *
 * Usage example:
 *
 *          jQuery(document).ready(function() {
 *              var myMaxWidthSelect =
 *                  new MaxWidthSelect(jQuery('select'), 100, callback, 10);
 *          });
 *
 * where callback is defined as
 *
 *          function callback(value) {
 *              // do something
 *          }
 *
 *
 * You can retrieve the 'value' attribute of the selected <option>
 * element:
 *
 *          var myValue = myMaxWidthSelect.value;
 *
 *
 * Requires:
 *      MaxWidthSelect.css
 *      arrow.png
 *      arrow-over.png
 *
 *
 * Parameters:
 *
 * 1st argument points to the <select> you want to enhance.
 * 2nd argument (optional) is the desired width (in pixels) of
 *      the <select>.
 * 3rd argument (optional) is a callback function to be executed when a
 *      <option> is clicked. The 'value' argument of this callback
 *      function is the 'value' attribute of the selected <option>.
 * 4th argument (optional) is the z-index.
 *
 *
 * Thanks to Edward J. Pring for his article on JavaScript finite state
 * machines, http://www.ibm.com/developerworks/library/wa-finitemach1/.
 * --------------------------------------------------------------------
 */

function MaxWidthSelect(originalSelect, width, callback, zIndex) {
    var self = this;

    if (width && typeof width == 'number' && width > this.width) this.width = width;
    if (callback) this.onChange = callback;
    if (zIndex && typeof zIndex == 'number') this.zIndex = zIndex;

    this.masterNode = this.transformOriginalSelect(originalSelect);

    this.setupEvents(jQuery('.select', this.masterNode), this.elementType.select);

    var options = jQuery('.option', jQuery('.options', this.masterNode));
    options.each(function() {
        var thisOption = jQuery(this);
        self.setupEvents(thisOption, self.elementType.option);
    });

    this.setupEvents(jQuery('#maxWidthSelectCurtain'), this.elementType.curtain);

    this.currentState = this.initialState;
}

MaxWidthSelect.prototype = {

    currentState: null,
    initialState: 'Inactive',
    width: 100,
    onChange: null,
    value: null,
    zIndex: 1,

    setupEvents: function(node, elementType) {
        var self = this;

        node.bind('click',      function() { self.handleEvent('click',      elementType, node); });
        node.bind('mouseover',  function() { self.handleEvent('mouseover',  elementType, node); });
        node.bind('mouseout',   function() { self.handleEvent('mouseout',   elementType, node); });
    },

    transformOriginalSelect: function(node) {
        var maxWidthSelect = jQuery('<div class="maxWidthSelect"></div>');
        var select = jQuery('<div class="select"></div>');
        var options = jQuery('<div class="options"></div>');
        maxWidthSelect.css('z-index', this.zIndex);
        select.css('z-index', this.zIndex);
        options.css('z-index', this.zIndex);

        var originalOptions = jQuery('option', node);
        var firstOption;

        originalOptions.each(function() {
            var option = jQuery('<div class="option"></div>');
            var value = jQuery('<span class="value"></span>');
            var text = jQuery('<span class="text"></span>');
            value.text(this.value);
            text.text(this.text);
            option.append(value);
            option.append(text);
            options.append(option);

            if (!firstOption || this.selected) {
                firstOption = jQuery('<div class="option"></div>');
                var firstValue = jQuery('<span class="value"></span>');
                var firstText = jQuery('<span class="text"></span>');
                firstValue.text(this.value);
                firstText.text(this.text);
                firstOption.append(firstValue);
                firstOption.append(firstText);
            }
        });

        this.value = jQuery('.value', firstOption).text();

        if (jQuery.support.boxModel) {
            firstOption.css('width', (this.width - 27) + 'px');
            options.css('width', (this.width - 2) + 'px');
        }
        else {
            firstOption.css('width', (this.width - 17) + 'px');
        }

        select.append(firstOption);
        maxWidthSelect.append(select);
        maxWidthSelect.append(options);
        maxWidthSelect.css('width', this.width + 'px');
        node.hide();
        node.parent().append(maxWidthSelect);

        var curtain = jQuery('<div id="maxWidthSelectCurtain"></div>');
        jQuery('body').append(curtain);

        return maxWidthSelect;
    },

    handleEvent: function(eventType, elementType, element) {
        var action = this.actions[this.currentState][eventType];
        if (action) {
            var nextState = action.call(this, elementType, element);
            if (nextState)
                this.currentState = nextState;
        }
    },

    actions: {
        Inactive: {
            mouseover: function(elementType) {
                if (elementType == this.elementType.select) {
                    jQuery('.select', this.masterNode).addClass('selectHover');
                    return 'SelectHover';
                }
            }
        },
        SelectHover: {
            click: function(elementType) {
                if (elementType == this.elementType.select) {
                    jQuery('.options', this.masterNode).show();
                    var selectOption = jQuery('.option', jQuery('.select', this.masterNode));
                    jQuery('.option', jQuery('.options', this.masterNode)).each(function() {
                        if (jQuery('.value', jQuery(this)).text() == jQuery('.value', selectOption).text()) {
                            jQuery(this).addClass('optionHover');
                        }
                    });
                    jQuery('#maxWidthSelectCurtain').show();
                    return 'ShowOptions';
                }
            },
            mouseout: function(elementType) {
                if (elementType == this.elementType.select) {
                    jQuery('.select', this.masterNode).removeClass('selectHover');
                    return 'Inactive';
                }
            }
        },
        ShowOptions: {
            click: function(elementType, element) {
                if (elementType == this.elementType.curtain) {
                    element.hide();
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    jQuery('.options', this.masterNode).hide();
                    jQuery('.select', this.masterNode).removeClass('selectHover');
                    return 'Inactive';
                }

                if (elementType == this.elementType.select) {
                    jQuery('#maxWidthSelectCurtain').hide();
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    jQuery('.options', this.masterNode).hide();
                    return 'SelectHover';
                }
            },
            mouseover: function(elementType, element) {
                if (elementType == this.elementType.option) {
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    element.addClass('optionHover');
                    return 'OptionOn';
                }
            }
        },
        OptionOn: {
            click: function(elementType, element) {
                if (elementType == this.elementType.option) {
                    var selectOption = jQuery('.option', jQuery('.select', this.masterNode));
                    var newValue = jQuery('.value', element).text();
                    var newText = jQuery('.text', element).text();
                    jQuery('.value', selectOption).text(newValue);
                    jQuery('.text', selectOption).text(newText);
                    jQuery('.options', this.masterNode).hide();
                    jQuery('.select', this.masterNode).removeClass('selectHover');
                    this.value = newValue;
                    jQuery('#maxWidthSelectCurtain').hide();

                    if (this.onChange) this.onChange(this.value);

                    return 'Inactive';
                }

                if (elementType == this.elementType.curtain) {
                    element.hide();
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    jQuery('.options', this.masterNode).hide();
                    jQuery('.select', this.masterNode).removeClass('selectHover');
                    return 'Inactive';
                }

                if (elementType == this.elementType.select) {
                    jQuery('#maxWidthSelectCurtain').hide();
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    jQuery('.options', this.masterNode).hide();
                    return 'SelectHover';
                }
            },
            mouseover: function(elementType, element) {
                if (elementType == this.elementType.option) {
                    jQuery('.option', jQuery('.options', this.masterNode)).removeClass('optionHover');
                    element.addClass('optionHover');
                    return 'OptionOn';
                }
            }
        }
    },

    elementType: {
        select: 0,
        option: 1,
        curtain: 2
    }
}