/**
 * jSelectBox - jQuery Plugin
 * Examples and documentation at: www.marcelthole.de/jQuery-jSelectBox/
 *
 * Copyright (c) 2011 Marcel Thole ( info@marcelthole.de )
 *
 * Version: 1.0.0
 * Requires: jQuery v1.x.x+ ( Versions below were not tested! )
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * @todo Disabled states
 *
 * Simple Usage
 * API Method for all Objects: $.jSelectBox( apiMethod );
 * API Method for one Object: $('selector').jSelectBox( apiMethod );
 * Api Methods:
 * - addEventListener( String eventName, function callFunc )
 * - closeMenu()
 * - destroy()
 * - getSettings()
 * - init( object settings )
 * - openMenu()
 * - setActiveIndex( int index )
 * - toggleMenu()
 * - update()
 */

(function( $ ){
    var namespace = 'jSelectBox-' + parseInt( Math.random() * 1000000 ),
    jSelectBox = function()
    {
        var settings = {
            classNameSpace: 'jSelectBox', // Namespace of the classes
            topPositionDiff: 0, // add this value to the Options top css attribute
            trimTitle: false, // Trim the title Text
            trimTitleEnd: '...', // Text to append to the trimed Title
            showFirstOptionInList: true, // Show the first Option in the Option list: For Example "-- Choose --"
            classes: { // Class Names
                parentElementClass: 'outer',
                titleElementClass: 'title',
                titleElementInnerClass: 'text',
                dropwdownElementClass: 'dropdown',
                optionsElementClass: 'options',
                optionElementClass: 'option',
                activeClass: 'active',
                disabledClass: 'disabled'
            },
            markup: {
                optionBefore: '<div class="top"></div>',
                optionAfter: '<div class="bottom"></div>',
                titleBefore: '<div class="top"></div>',
                titleAfter: '<div class="bottom"></div>'
            },
            events: { // Events
                onBeforeUpdate: [],
                onAfterUpdate: [],
                onDestroy: [],
                onBeforeChangeValue: [],
                onAfterChangeValue: [],
                onOptionClick: [],
                onTitleClick: [],
                onOpenMenu: [],
                onCloseMenu: []
            }

        },

        /**
         * Check if this jQuery Object has this Plugin
         * @param jQueryObject jQuery Object
         * @return bool
         */
        checkValid = function( jQueryObject )
        {
            if( jQueryObject.data( namespace + '.active' ) == true )
            {
                return true;
            }
            return false;
        },

        /**
         * Calculate the Width of the given Text
         * @param text string
         * @return int
         */
        calculateTextWidth = function( text, fontSize ) {
            var $tmpText = $('<span>').css({margin:0,padding:0,display:'none',fontSize:fontSize}).html(text)
                width = 0;

            $('body').append( $tmpText );
            width = $tmpText.width();
            $tmpText.remove();
            return width;
        },

        /**
         * Trim the Text to the given width
         * @param text String
         * @param width int
         * @param endText string [optional]
         * @return string
         */
        trimTextToWidth = function( text, width, endText, fontSize )
        {
            if( typeof text != 'string' )
            {
                return text;
            }

            var textWidth = calculateTextWidth( text, fontSize ),
                length = text.length,
                trimmend = false;

            // If isset EndText, the width is lower
            if( typeof endText == 'string' )
            {
                width -= calculateTextWidth(endText,fontSize);
            }

            if( width < 5 )
            {
                return text;
            }

            // While textWidth to long...
            while( textWidth > width )
            {
                text = text.substr(0, length);
                length--;
                textWidth = calculateTextWidth( text, fontSize );
                trimmend = true;
            }

            if( typeof endText == 'string' && trimmend )
            {
                text += endText;
            }
            return text;
        },

        /**
         * Trim the $titleElement automaticly
         * @param $titleElement jQuery Object
         * @return void
         */
        trimTitleTextToWidth = function( onObject, $titleElement )
        {
            var settings = methods.getSettings.apply(onObject,[]),
                $titleInner = $titleElement.find('.'+settings.classes.titleElementInnerClass),
                oldText = $titleInner.html(),
                newText = trimTextToWidth( oldText, $titleInner.width(), settings.trimTitleEnd, $titleInner.css('font-size') );
            $titleInner.html(newText);
        },

        /**
         * Fires the event on the target object
         * @param onObject object
         * @param eventName string
         * @return void
         */
        fireEvent = function( onObject, eventName )
        {
            var evName = 'on' + eventName,
                args = Array.prototype.slice.call( arguments, 2 ),
                settings = methods.getSettings.apply(onObject,[]);

            $.each( settings.events[evName], function(index,funct){
                if( typeof funct == 'function' )
                {
                    funct.apply( this, args );
                }
            });
        },

        /**
         * Copy the styles from the on to the other Object
         * @param fromObject jQuery Object
         * @param toObject jQuery Object
         * @return void
         */
        copyStyles = function( fromObject, toObject )
        {
            var st = fromObject.get(0).style,
                style;
            for( style in st)
            {
                if( typeof st[style] == 'string' && st[style] != '' )
                {
                    toObject.css( style , st[style] );
                }
            }
        },

        /**
         * Public API Methods
         */
        methods = {

            /**
             * Init the Plugin
             * Call with apply!
             * @param options Object
             * @return Object
             */
            init : function( options ) {
                // Global settings
                settings = $.extend(true,{},settings,options);

                this.each(function(){
                    var $this = $(this);
                    $this.data( namespace + '.active',true)
                         .data( namespace + '.settings',settings);

                });
                // Update the box
                return methods.update.apply(this, []);
            },

            /**
             * Destroy the Plugin
             * Call with apply!
             * @return object
             */
            destroy : function( ) {
                return this.each(function(){
                    var $this = $(this);
                    fireEvent($this,'Destroy', $this);
                    if( checkValid($this) && $this.prev().data(namespace) == true )
                    {
                        $this.prev().remove();
                        $this.css('display','');
                    }
                });
            },

            /**
             * Sets the Select field above the select field
             * Call with apply!
             * @return object
             */
            update: function(){
                return this.each(function(){
                    var $this = $(this),
                        settings = methods.getSettings.apply(this,[]),
                        $selectedOpt = $this.find('option:selected'),

                        // add the Options Class and set the div absolute
                        $optionsElement = $('<div>').addClass(settings.classes.optionsElementClass)
                                                    .html('')
                                                    .css({display:'none',position:'absolute'}),

                        // The Title Element. Append the markup and set the html with the titleInnerClass
                        $titleElement = $('<div>').addClass(settings.classes.titleElementClass)
                                                    .append( settings.markup.titleBefore )
                                                    .append( $('<div>').addClass(settings.classes.titleElementInnerClass).html($selectedOpt.html()) )
                                                    .append( settings.markup.titleAfter )
                                                    .append( $optionsElement ),

                        // The dropdown div
                        $dropdownArrow = $('<div>').addClass(settings.classes.dropwdownElementClass),

                        // The Outer Element of the Selectbox
                        $outer = $('<div>').addClass(settings.classNameSpace+" "+settings.classes.parentElementClass)
                                            .append( $titleElement )
                                            .append( $dropdownArrow )
                                            .css({position:'relative'});

                    fireEvent($this,'BeforeUpdate', $this);

                    // Append the styles from the select box to the outer element
                    copyStyles( $this, $outer );
                    $this.css('display','none');

                    if( checkValid($this) )
                    {
                        // has Element before? Remove!
                        if( $this.prev().data(namespace) == true )
                        {
                            $this.prev().remove();
                        }
                        
                        // Each Option!
                        $.each( $this.find('option'), function(index,option){

                            var $o = $(option),
                                $option = $('<div>').addClass(settings.classes.optionElementClass)
                                                    .html( $o.html() );

                            // Check is option active
                            if( $this.val() == $o.val() )
                            {
                                $option.addClass( settings.classes.activeClass );
                            }
                            else
                            {
                                $option.removeClass( settings.classes.activeClass );
                            }

                            $option.bind('click.'+namespace,function(){
                                fireEvent($this,'OptionClick',$this,index);
                                methods.setActiveIndex.apply($this, [index]);
                            });

                            // Add this option to the options div
                            $optionsElement.append( $option );
                        } );

                        if( settings.showFirstOptionInList === false ) {
                            $optionsElement.find('.option:first').css('display','none');
                        }

                        // Set the markup before and after
                        $optionsElement.prepend( settings.markup.optionBefore )
                                       .append( settings.markup.optionAfter );

                        // The title and SelectArrow div
                        $outer.find('>div').bind('click.'+namespace,function(){
                            fireEvent($this,'TitleClick',$this);
                            methods.toggleMenu.apply($this, []);
                        }).data(namespace,true);

                        $this.before($outer);

                        // Trim title?
                        if( settings.trimTitle )
                        {
                            trimTitleTextToWidth( $this, $titleElement );
                        }
                        
                    }

                    $outer.data( namespace, true );
                    fireEvent($this,'AfterUpdate', $this);
                });
            },

            /**
             * Sets the value of the selectbox
             * Call with apply!
             * @param index int
             * @return object
             */
            setActiveIndex: function( index ){
                return this.each(function(){

                    var $this = $(this),
                        $prev = $this.prev(),
                        settings = methods.getSettings.apply(this,[]),
                        $titleElement = $prev.find('.' + settings.classes.titleElementClass ),
                        $titleElementText = $titleElement.find('.'+settings.classes.titleElementInnerClass),
                        // the Select option to which to set
                        $newSelectOption = $($this.find('option').get(index)),
                        $options = $prev.find('.' + settings.classes.optionElementClass);

                    fireEvent($this,'BeforeChangeValue',$this,$prev,index);
                    if( checkValid($this) )
                    {
                        $titleElementText.html( $newSelectOption.html() );
                        if( settings.trimTitle )
                        {
                            trimTitleTextToWidth( $this, $titleElement );
                        }
                        // Set the value to the Select value
                        $this.val( $newSelectOption.val() );

                        // each option
                        $.each( $options, function(ind,option){
                                // the option from the Select container
                            var $o = $(option),
                                // the option from the selectbox
                                $curSelectOption = $($this.find('option').get(index));

                            // Add Active
                            if( $curSelectOption.html() == $o.html() )
                            {
                                $o.addClass(settings.classes.activeClass);
                            }
                            // Remove Active
                            else
                            {
                                $o.removeClass(settings.classes.activeClass);
                            }
                        } );
                    }
                    fireEvent($this,'AfterChangeValue',$this,$prev,index);
                });
            },

            /**
             * Opens the Options
             * Call with apply!
             * @return object
             */
            openMenu: function(){
                methods.closeMenu.apply(this, []);

                return this.each(function(){
                    var $this = $(this),
                        $prev = $this.prev(),
                        settings = methods.getSettings.apply(this,[]),
                        $optionsContainer = $prev.find('.'+settings.classes.optionsElementClass);

                    fireEvent($this,'OpenMenu',$this,$prev);
                    if( checkValid($this) )
                    {
                        $optionsContainer.css({
                            display: 'block',
                            top: $prev.height() + settings.topPositionDiff,
                            zIndex: 2
                        });
                        $prev.css({
                            zIndex: 2
                        });

                        // Bin a document click, to close the Menu
                        window.setTimeout(function(){
                            $(document).bind('click.'+namespace,function(){
                                methods.closeMenu.apply($this, []);
                                $(document).unbind('click.'+namespace);
                            });
                        }, 200);
                    }
                });
            },

            /**
             * Closes the Options
             * Call with apply!
             * @return object
             */
            closeMenu: function(){
                return this.each(function(){
                    var $this = $(this),
                        $prev = $this.prev(),
                        settings = methods.getSettings.apply(this,[]),
                        $optionsContainer = $prev.find('.'+settings.classes.optionsElementClass );

                    fireEvent($this,'CloseMenu',$this,$prev);
                    if( checkValid($this) )
                    {
                        $optionsContainer.css({
                            display: 'none',
                            zIndex: 1
                        });
                        $prev.css({
                            zIndex: 1
                        });
                    }
                });
            },

            /**
             * Toogles the options
             * Call with apply!
             * @return object
             */
            toggleMenu: function(){
                return this.each(function(){
                    var $this = $(this),
                        $prev = $this.prev(),
                        settings = methods.getSettings.apply(this,[])
                        $optionsContainer = $prev.find('.'+settings.classes.optionsElementClass);

                    if( checkValid($this) )
                    {
                        if( $optionsContainer.is(':visible') )
                        {
                            methods.closeMenu.apply( $this , [] );
                        }
                        else
                        {
                            methods.openMenu.apply( $this , [] );
                        }
                    }
                });
            },

            /**
             * Add an event to the Object
             * Call with apply!
             * @param eventName String
             * @param callFunc Function
             * @return void
             */
            addEventListener: function(eventName, callFunc)
            {
                var settings = methods.getSettings.apply(this,[]);

                if( typeof settings.events['on'+eventName] == 'object' )
                {
                    settings.events['on'+eventName].push( callFunc );
                }
                else
                {
                    $.error( 'Event "',eventName,'" does not exits in this settings.events: ', settings.events );
                }
            },

            /**
             * Returns the Settings
             * Call with apply!
             * @return Object
             */
            getSettings: function(){
                if( typeof this != 'undefined' )
                {
                    return $(this).data( namespace + '.settings' );
                }
                else
                {
                    return settings.pluginNamespace;
                }
            }
        };

        return methods;

    };

    /**
     * $(selector).jSelectBox() Function
     * @param method mixed
     * @return object
     */
    $.fn.jSelectBox = function( method ) {
        var args = [this,method],
            callArgs = Array.prototype.slice.call( arguments, 1 );

        // Merge all Arguments to one Array
        $.each(callArgs,function(){
            args.push(this);
        });

        return $.jSelectBox.apply( window, args );
    };

    /**
     * $.jSelectBox() Api Function
     * @param object Object
     * @param method String
     * @return object
     */
    $.jSelectBox = function( object, method ) {

        // new Class
        var csb = new jSelectBox(),
            activeSelects = [];

        // Object is not set. Apply to all!
        if( typeof object != 'object' )
        {
            method = object;
            $.each( $('select'), function(index,value){
                if( $(value).data( namespace + '.active') == true )
                {
                    activeSelects.push( $(value) );
                }
            });
            object = $(activeSelects);
        }

        if( typeof method == 'undefined' )
        {
            method = 'init';
        }

        if ( csb[method] ) {
            return csb[method].apply( object, Array.prototype.slice.call( arguments, 2 ));
        } else if ( typeof method === 'object' || ! method ) {
            return csb.init.apply( object, Array.prototype.slice.call( arguments, 1 ) );
        } else {
            $.error( 'Method ' +  method + ' does not exist' );
            return object;
        }

    };

})( jQuery );
