/*global $ jQuery window */
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */

/**
* jQuery workaround for the cropped option in set width select.
* 
* Copyright (c) 2010 Ewen Elder
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @author: Ewen Elder <glomainn at yahoo dot co dot uk> <ewen at jainaewen dot com>
* @version: 0.1.1 beta
**/

'use strict';
jQuery.fn.ieSelectWidth = function (options) {
    var defaults, selectFix, elements;

    elements = $(this);


    defaults = {
        width: null,
        containerClassName: 'ie-select-width-container',
        overlayClassName: 'ie-select-width-overlay',
        overlayCSS: null,
        containerCSS: null
    };


    // Extend the defaults.
    options = $.extend(defaults, options);


    selectFix = {

        /**
        * Initiate the class, check if the browser is IE, if not then return false, check if the element has an ID, 
        * if not then apply one, add a new container around the element and set it's CSS, add event listeners.
        **/
        init: function () {
            var self = this;


            // If not IE, return false.
            if (!document.all) {
                return false;
            }


            $.each
			(
				elements,
				function () {
				    var element = $(this);


				    // If the element has multiple set, or has a size higher than 0, continue to the next element.
				    if (element.attr('multiple') || element.attr('size') > 0) {
				        return false;
				    }

				    // If this element doesn't have an ID, apply one.
				    if (!element.attr('id').length) {
				        element.attr('id', String((new Date()).getTime()).replace(/\D/gi, '').substr(8));
				    }


				    // If the width option has been passed, then apply and store it,
				    // if the width is set via CSS, then store than instead.
				    if (options.width !== null) {
				        element.css('width', options.width + 'px');
				    }

				    element.data('origWidth', element.outerWidth());


				    // Add the container.
				    self.addContainer(element);


				    // Add the overlay.
				    self.addOverlay(element);


				    // Remove any positioning attributes from the select element.
				    $(element).css
					({
					    position: 'relative',
					    top: 'auto',
					    left: 'auto',
					    bottom: 'auto',
					    right: 'auto',
					    margin: '0'
					});
				}
			);


            // Event listener for opening the select.
            $(elements).bind
			(
				'dblclick mousedown change blur',
				function (event) {
				    self.openSelect(event);
				}
			);


            // Event listener for setting the various classes depending on the current event.
            $(elements).bind
			(
				'mousedown mouseup mouseover mouseout blur change',
				function (event) {
				    self.setClass(event);
				}
			);
        },



        addContainer: function (element) {
            var browserClass;

            // Which browser??
            if (!window.XMLHttpRequest) {
                browserClass = 'ie6';
            }

            else if (window.XMLHttpRequest && !document.documentMode) {
                browserClass = 'ie7';
            }

            else if (document.documentMode) {
                browserClass = 'ie8';
            }
            // Add a container around the element.
            element.after('<span id="' + element.attr('id') + '-container" class="' + options.containerClassName + ' ' + browserClass + '"></span>');
            element.next().append(element);

            // Set the container's CSS.
            element.parent().css
			({
			    position: element.css('position') === 'static' ? 'relative' : element.css('position'),
			    display: 'block',
			    top: element.css('top'),
			    right: element.css('right'),
			    bottom: element.css('bottom'),
			    left: element.css('left'),
			    overflow: 'hidden',
			    width: element.outerWidth() + 'px',
			    margin: (element.css('margin-top') !== 'auto' ? element.css('margin-top') : '0') + ' ' +
						 (element.css('margin-right') !== 'auto' ? element.css('margin-right') : '0') + ' ' +
						 (element.css('margin-bottom') !== 'auto' ? element.css('margin-bottom') : '0') + ' ' +
						 (element.css('margin-left') !== 'auto' ? element.css('margin-left') : '0')
			});


            // Apply container custom CSS.
            if (options.containerCSS !== null) {
                element.parent().css(options.containerCSS);
            }
        },



        /**
        * Add the overlay, apply bgiframe if present, add event listener, set the overlays's CSS,
        **/
        addOverlay: function (element) {
            var borderBottom, borderRight, borderTop, childWidth, elementId, height, left, margin, overlay, overlayId, paddingRight, paddingTop, width;

            elementId = element.attr('id');

            overlayId = elementId + '-' + options.overlayClassName;


            // Add the overlay.
            element.after('<a id="' + overlayId + '" class="' + options.overlayClassName + '"><span></span></a>');


            // Get the overlay.
            overlay = $('a#' + overlayId);


            // Check for bgiframe for IE 6.
            if (!window.XMLHttpRequest && ($.fn.bgIframe || $.fn.bgiframe)) {
                overlay.bgiframe();
            }


            // If the overlay is clicked, then the element will loose focus, set focus back to the element.
            overlay.bind
			(
				'mousedown',
				function () {
				    setTimeout
					(
						function () {
						    element.focus();
						},
						1
					);
				}
			);


            // Get styles for position and size calculations.
            childWidth = overlay.children('span').width();
            borderTop = element.css('border-top-style') !== 'none' ? +element.css('border-top-width').replace('px', '') : 0;
            borderRight = element.css('border-right-style') !== 'none' ? +element.css('border-right-width').replace('px', '') : 0;
            borderBottom = element.css('border-bottom-style') !== 'none' ? +element.css('border-bottom-width').replace('px', '') : 0;
            paddingTop = +element.css('padding-top').replace('px', '');
            paddingRight = +element.css('padding-right').replace('px', '');
            left = element.outerWidth() - childWidth;
            width = childWidth;
            height = element.outerHeight();


            // CSS for IE8+ to incorporate border padding etc
            // (the overlay container has to be the full inner height of the select element in order to replicate 
            //  the border-right, which is pushed to the right when the select element's width is set to auto.)
            if (document.documentMode) {
                // Compensate for border width.
                if (borderRight > 0) {
                    left = left - (borderRight + paddingRight);
                    borderRight = borderRight + 'px ' + element.css('border-right-style') + ' ' + element.css('border-right-color');
                }


                // Compensate for border height.
                if (borderTop > 0 || borderBottom > 0) {
                    height = height - (borderTop + borderBottom);
                }


                // Compensate for padding.
                if (paddingRight > 0) {
                    width = (childWidth + paddingRight);
                }


                margin = borderTop + 'px 0';


                overlay.children('span').css
				({
				    margin: paddingTop + 'px 0'
				});
            }


            // Position the overlay to sit where the select's arrow thingie is.
            overlay.css
			({
			    position: 'absolute',
			    display: 'none',
			    top: element.position().top + 'px',
			    left: left + 'px',
			    width: width + 'px',
			    height: height + 'px',
			    margin: margin,
			    borderRight: borderRight
			});


            // Apply overlay custom CSS.
            if (options.overlayCSS !== null) {
                overlay.css(options.overlayCSS);
            }
        },



        /**
        * Open the element and display the overlay.
        **/
        openSelect: function (event) {
            var element, elementId, offset, overlay, pageX, pageY, type; // element, id, offset, overlay, pageX, pageY, type

            element = $(event.target);
            elementId = element.attr('id');
            offset = element.offset();
            overlay = $('a#' + elementId + '-' + options.overlayClassName);
            pageX = event.pageX;
            pageY = event.pageY;
            type = event.type;


            // Double clicks eventually cause the select box to open without applying the workaround,
            // this causes it to close again, although you can still it open and close.
            if (type === 'dblclick') {
                element.css
				({
				    width: element.data('origWidth') + 'px'
				});
            }

            // If the event is blur, or change then reset the element back to normal,
            // if it is mousedown, and the element's overlay exists, AND the mousedown event happenned on the currently selected option, then reset the element back to normal,
            // ignore the mousedown event in any other circumstances.
            if (type === 'change' || type === 'blur' || (type === 'mousedown' && overlay.css('display') === 'block' && offset.left < pageX && (offset.left + element.data('origWidth')) > pageX && offset.top < pageY && (offset.top + element.outerHeight()) > pageY)) {
                return this.closeSelect(event);
            }


            // Show the overlay.
            if (overlay.css('display') === 'none') {
                overlay.css('display', 'block');
            }


            // Set the select's width to auto.
            if (!element.data('ignore')) {
                element.css('width', 'auto');


                // If the select's auto width is less than it's original width, then set it back to it's original width
                // and add a flag so that there are no future attempts to resize this element.
                if (element.outerWidth() < element.parent().innerWidth()) {
                    element.css('width', element.data('origWidth') + 'px');
                    element.data('ignore', true);
                }
            }
        },


        /**
        * Hide the overlay, and restore the select element's width.
        **/
        closeSelect: function (event) {
            var element = $(event.target);

            element.siblings('a.' + options.overlayClassName + '').css('display', 'none');

            if (!element.data('ignore')) {
                setTimeout
				(
					function () {
					    element.css
						({
						    width: element.data('origWidth') + 'px'
						});
					},
					1
				);
            }
        },


        /**
        * Apply class names to represent the current event state, hover, mousedown etc.
        **/
        setClass: function (event) {
            var element, offset, overlay, pageX, pageY, type;

            element = $(event.target);
            offset = element.offset();
            overlay = $('a#' + element.attr('id') + '-' + options.overlayClassName);
            pageX = event.pageX;
            pageY = event.pageY;
            type = event.type;

            if (!overlay.length) {
                return false;
            }

            if (type === 'mousedown' && offset.left < pageX && (offset.left + element.data('origWidth')) > pageX && offset.top < pageY && (offset.top + element.outerHeight()) > pageY) {
                overlay.removeClass().addClass(options.overlayClassName + ' ' + options.overlayClassName + '-active');
            }

            else if (type === 'mouseover') {
                overlay.removeClass().addClass(options.overlayClassName + ' ' + options.overlayClassName + '-hover');
            }

            else if (type === 'mouseup' || type === 'mouseout' || type === 'blur' || type === 'change') {
                overlay.removeClass().addClass(options.overlayClassName);
            }
        }
    };

    selectFix.init();
};
