/**
 * Copyright (c) 2008 Oktay Acikalin | symmetrics gmbh (http://www.symmetrics.de)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $CreateDate: 2008-10-04 12:00:00 +0002 (Sat, 05 Oct 2008) $
 *
 * Version: 0.4
 * 
 * @author Oktay Acikalin <ok@symmetrics.de>
 *
 * Requires: jQuery 1.2.6+
 **/
var accordion = function ( options )
{
    
    var defaults = {
        base_obj: null,
        menu_obj_path: null,
        update_interval: 300,
        wait_timeout: 500,
        multi_slide: true,
        break_on_click: false,
        close_on_break: true,
        keep_selection_open: false,
        defer_on_mousemove: false,
        mousemove_defer_msecs: 300,
        mousemove_defer_timeout: 50
    } ;
    
    jQuery . extend ( defaults, options ) ;
    
    this . menu = defaults . base_obj ;
    this . submenus = defaults . base_obj . find ( defaults . menu_obj_path ) ;
    this . multi_slide = defaults . multi_slide ;
    this . elements_update_interval = defaults . update_interval ;
    this . single_slide_wait_to_show_timeout = defaults . wait_timeout ;
    this . break_on_click = defaults . break_on_click ;
    this . close_on_break = defaults . close_on_break ;
    this . keep_selection_open = defaults . keep_selection_open ;
    this . defer_on_mousemove = defaults . defer_on_mousemove ;
    this . mousemove_defer_msecs = defaults . mousemove_defer_msecs ;
    this . mousemove_defer_timeout = defaults . mousemove_defer_timeout ;
    
    this . slide_count = 0 ;
    this . old_slide_count = 0 ;
    this . timeout = null ;
    this . fixed_ul = null ;
    this . deactivated = false ;
    
    this . selected_parent = null ;
    this . selected_child = null ;
    
    this . last_move = 0 ;

    this . wait_to_show = function ( element )
    {
        if ( this . timeout ) clearTimeout ( this . timeout ) ;
        this . timeout = null ;

        if ( element . leafs . length == 0 ) return ;

        var $this = this ;

        if ( !this . multi_slide && ( this . slide_count > 0 || element . slide == true ) )
        {
            this . timeout = setTimeout ( function () {
                $this . wait_to_show ( element ) ;
            }, this . single_slide_wait_to_show_timeout ) ;
            return ;
        }
        else
        {
            if ( element . state != 1 ) return ;

            this . slide_count ++ ;
            element . slide = true ;

            element . obj . slideDown ( 'normal', function () {
                $this . slide_count = Math.max ( 0, $this . slide_count - 1 ) ;
                element . slide = false ;
                if ( $this . fixed_ul && ! $this . fixed_ul . style . height && element != $this . selected_parent )
                {
                    var height = $( $this . fixed_ul ) . height () ;
                    
                    if ( jQuery.browser.msie && jQuery.browser.version == 6 )
                    	height -= 22 ;
                    
                    $this . fixed_ul . style . height = height + 'px' ;
                }
            } ) ;
        }
    }

    this . show_element = function ( element )
    {
        if ( element . leafs . length == 0 ) return ;
        
        if ( element . slide == true || this . timeout ) return ;

        var obj = element . obj ;
        if ( element . slide == true )
        {
            obj . stop () ;
            this . slide_count = Math.max ( 0, this . slide_count - 1 ) ;
        }

        if ( ! this . keep_selection_open )
        {
            this . submenus . each ( function () {
                if ( this != element )
                    this . state = -1 ;
            } ) ;
        }

        this . wait_to_show ( element ) ;
    }

    this . hide_element = function ( element )
    {
        if ( element . leafs . length == 0 ) return ;
        
        if ( element . slide == true || this . timeout ) return ;

        var obj = element . obj ;
        if ( element . slide == true )
        {
            obj . stop () ;
            this . slide_count = Math.max ( 0, this . slide_count - 1 ) ;
        }

        this . slide_count ++ ;
        element . slide = true ;

        var $this = this ;

        obj . slideUp ( 'normal', function () {
            $this . slide_count = Math.max ( 0, $this . slide_count - 1 ) ;
            element . slide = false ;
        } ) ;
    }

    this . update_elements = function ()
    {
        var $this = this ;
        
        this . submenus . each ( function () {
            if ( this . state == -1 )
            {
                $this . hide_element ( this ) ;
            }
            else if ( this . state == 1 )
            {
                $this . show_element ( this ) ;
            }

            if ( $this . old_slide_count != $this . slide_count )
            {
                if ( $this . slide_count > 0 )
                {
                    if ( $this . keep_selection_open && this == $this . selected_parent )
                    {
                        // nothing
                    }
                    else
                    {
                        this . header . removeClass ( 'anchor' ) ;
                        this . leafs . removeClass ( 'anchor' ) ;
                        this . leafs . attr ( 'disabled', true ) ;
                    }
                }
                else
                {
                    this . header . addClass ( 'anchor' ) ;
                    this . leafs . addClass ( 'anchor' ) ;
                    this . leafs . attr ( 'disabled', false ) ;
                }
            }
        } ) ;
        
        this . old_slide_count = this . slide_count ;
    }
    
    this . select_element = function ( element )
    {
        if ( this . deactivated ) return ;
        
        if ( this . timeout ) clearTimeout ( this . timeout ) ;
        this . timeout = null ;

        var $this = this ;
        
        if ( this . defer_on_mousemove )
        {
            var t = new Date () ;
            var cur_move = t . getTime () ;
            if ( this . last_move > cur_move - this . mousemove_defer_msecs )
                this . timeout = setTimeout ( function () {
                    $this . select_element ( element ) ;
                }, this . mousemove_defer_timeout ) ;
        }

        if ( this . slide_count > 0 )
        {
            this . timeout = setTimeout ( function () {
                $this . select_element ( element ) ;
            }, this . single_slide_wait_to_show_timeout ) ;
            return ;
        }

        var ul = element . parentNode ;
        if ( this . fixed_ul )
        {
            var cur_element = $( this . fixed_ul ) . find ( '> li[state=1]' ) . get ( 0 ) ;
            var ul_changed = this . fixed_ul != ul ;
            var kso_same_ul_and_el = $this . keep_selection_open && ul == this . fixed_ul && element == $this . selected_parent && cur_element == element ;
            var number_of_el_differ = false ;
            if ( cur_element )
                number_of_el_differ = element . leafs . length != cur_element . leafs . length ;
            else
                number_of_el_differ = true ;
            if ( ul_changed || kso_same_ul_and_el || number_of_el_differ )
            {
                this . fixed_ul . style . height = '' ;
                this . fixed_ul = ul ;
            }
        }
        else
        {
            this . fixed_ul = ul ;
        }

        this . submenus . each ( function () {
            if ( $this . keep_selection_open && this == $this . selected_parent )
                this . state = 1 ;
            else
                this . state = -1 ;
        } ) ;

        element . state = 1 ;
    }

    this . collapse_all = function ()
    {
        var $this = this ;
        
        $this . submenus . each ( function () {
            if ( this . state != -1 )
            {
                if ( this . slide == true )
                {
                    var _this = this ;
                    var interval = setInterval ( function () {
                        // console.log ( 'waiting' ) ;
                        if ( _this . slide == true ) return ;
                        
                        clearInterval ( interval ) ;
                        if ( $this . fixed_ul )
                        {
                            $this . fixed_ul . style . height = '' ;
                            $this . fixed_ul = null ;
                        }
                        $this . hide_element ( _this ) ;
                        _this . state = -1 ;
                    }, 100 );
                }
                else
                {
                    if ( $this . fixed_ul )
                    {
                        $this . fixed_ul . style . height = '' ;
                        $this . fixed_ul = null ;
                    }
                    $this . hide_element ( this ) ;
                    this . state = -1 ;
                }
            }
        } ) ;
    }

    this . construct = function ()
    {
        var $this = this ;
        
        this . menu . hover (
            function () {
                if ( this . deactivated ) return ;
                
                if ( $this . selected_parent )
                {
                    if ( $this . selected_child )
                        $( $this . selected_child ) . removeClass ( 'active' ) ;
                    else
                        $( $this . selected_parent ) . removeClass ( 'active' ) ;
                }
            },
            function () {
                if ( this . deactivated ) return ;
                
                if ( $this . selected_parent )
                {
                    var interval = setInterval ( function () {
                        // console.log ( 'waiting' ) ;
                        if ( $this . slide_count > 0 ) return ;
                        
                        clearInterval ( interval ) ;
                        
                        $this . select_element ( $this . selected_parent ) ;

                        if ( $this . selected_child )
                            $( $this . selected_child ) . addClass ( 'active' ) ;
                        else
                            $( $this . selected_parent ) . addClass ( 'active' ) ;
                    }, 500 ) ;
                }
                else
                {
                    if ( $this . timeout ) clearTimeout ( $this . timeout ) ;
                    $this . timeout = null ;
                    
                    var interval = setInterval ( function () {
                        // console.log ( 'waiting' ) ;
                        if ( $this . slide_count > 0 ) return ;
                        
                        clearInterval ( interval ) ;
                        
                        $this . collapse_all () ;
                    }, 500 ) ;
                }
            }
        ) ;
        
        $('li.submenu_content').after('<li class="submenu_bottom"></li>');
        
        this . submenus . each ( function () {
            var is_open = false ;
            this . state = -1 ;
            this . obj = $( this ) . find ( '> ul' ) ;
            this . header = $( this ) . find ( '> a' ) ;
            this . leafs = $( this ) . find ( '> ul > li' ) ;
            
            var this_obj = $( this ) ;
            this_obj . mouseover ( function () {
                $this . select_element ( this ) ;
            } ) ;
            this_obj . mousemove ( function () {
                $this . select_element ( this ) ;
            } ) ;
            
            var parent = this ;
            this . leafs . each ( function () {
                if ( $( this ) . hasClass ( 'active' ) )
                {
                    $this . selected_parent = parent ;
                    $this . selected_child = this ;
                    $( this ) . addClass ( 'hover' ) ;
                    $this . select_element ( parent ) ;
                    is_open = true ;
                }
            } ) ;
            
            if ( ! $this . selected_child && this_obj . hasClass ( 'active' ) )
            {
                $this . selected_parent = this ;
                this_obj . addClass ( 'hover' ) ;
                $this . select_element ( this ) ;
                is_open = true ;
            }
            else if ( this_obj . hasClass ( 'active' ) )
            {
                this_obj . removeClass ( 'active' ) ;
            }
            
            if ( ! is_open )
                this . obj . hide () ;
        } ) ;
        
        if ( this . break_on_click == true )
        {
            this . menu . find ( '> ul > li.submenu_content > a' ) . click ( function () {
                if ( $this . selected_child )
                {
                    $( $this . selected_child ) . removeClass ( 'active' ) ;
                    $( $this . selected_child ) . removeClass ( 'hover' ) ;
                }
                if ( $this . selected_parent )
                {
                    $( $this . selected_parent ) . removeClass ( 'active' ) ;
                    $( $this . selected_parent ) . removeClass ( 'hover' ) ;
                    
                    if ( $this . selected_parent . state != -1 )
                    {
                        $this . selected_parent . state = -1 ;
                        $this . hide_element ( $this . selected_parent ) ;
                    }
                }
                $this . selected_parent = null ;
                $this . selected_child = null ;
                $this . collapse_all () ;
                $this . deactivated = true ;
            } ) ;
            
            this . submenus . each ( function () {
                var element = this ;
                
                this . header . click ( function () {
                    if ( $this . close_on_break )
                    {
                        if ( $this . selected_child )
                        {
                            $( $this . selected_child ) . removeClass ( 'active' ) ;
                            $( $this . selected_child ) . removeClass ( 'hover' ) ;
                        }
                        if ( $this . selected_parent && $this . selected_parent != element )
                        {
                            $( $this . selected_parent ) . removeClass ( 'active' ) ;
                            $( $this . selected_parent ) . removeClass ( 'hover' ) ;
                            
                            if ( $this . selected_parent . state != -1 )
                            {
                                $this . selected_parent . state = -1 ;
                                $this . hide_element ( $this . selected_parent ) ;
                            }
                        }
                        $this . selected_parent = element ;
                        $this . selected_child = null ;
                        if ( $this . fixed_ul )
                            $this . fixed_ul . style . height = '' ;
                        $( $this . selected_parent ) . addClass ( 'active' ) ;
                        $( $this . selected_parent ) . addClass ( 'hover' ) ;
                        $this . show_element ( $this . selected_parent ) ;
                        $this . selected_parent . state = 1 ;
                        $this . update_elements () ;
                        $this . wait_to_show ( $this . selected_parent ) ;
                        $this . deactivated = true ;
                    }
                    else
                        $this . deactivated = true ;
                    
                    return true ;
                } ) ;
                
                this . leafs . each ( function () {
                    var leaf = this ;
                    $( this ) . find ( 'a' ) . click ( function () {
                        if ( $this . close_on_break )
                        {
                            if ( $this . selected_child )
                            {
                                $( $this . selected_child ) . removeClass ( 'active' ) ;
                                $( $this . selected_child ) . removeClass ( 'hover' ) ;
                            }
                            if ( $this . selected_parent )
                            {
                                $( $this . selected_parent ) . removeClass ( 'active' ) ;
                                $( $this . selected_parent ) . removeClass ( 'hover' ) ;
                                if ( $this . selected_parent && $this . selected_parent != element )
                                    $this . selected_parent . state = -1 ;
                            }
                            $this . selected_parent = element ;
                            $this . selected_child = leaf ;
                            $this . fixed_ul . style . height = '' ;
                            $this . selected_parent . state = 1 ;
                            $( $this . selected_child ) . addClass ( 'active' ) ;
                            $( $this . selected_child ) . addClass ( 'hover' ) ;
                            $this . update_elements () ;
                            $this . wait_to_show ( $this . selected_parent ) ;
                            $this . deactivated = true ;
                        }
                        else
                            $this . deactivated = true ;
                        
                        return true ;
                    } ) ;
                } ) ;
            } ) ;
        }
        
        if ( this . defer_on_mousemove == true )
        {
            this . menu . mousemove ( function () {
                var t = new Date () ;
                $this . last_move = t . getTime () ;
            } ) ;
        }

        setInterval ( function () {
            $this . update_elements () ;
        }, this . elements_update_interval ) ;
    }
    
    return this . construct () ;
    
}
