(function() {

    /**
    *
    * By Marco van Hylckama Vlieg (marco@i-marco.nl)
    * Some inspiration taken from the YUI TabView widget
    *
    * THIS IS A WORK IN PROGRESS
    *
    * Many, many thanks go out to Daniel Satyam Barreiro!
    * Please read his article about YUI widget development
    * http://yuiblog.com/blog/2008/06/24/buildingwidgets/
    * Without his excellent help and advice this widget would not
    * be half as good as it is now.
    */
    
    /**
    * The accordionview module provides a widget for managing content bound to an 'accordion'.
    * @module accordionview
    * @requires yahoo, dom, event, element, animation
    */
    
    var YUD = YAHOO.util.Dom, YUE = YAHOO.util.Event, YUA = YAHOO.util.Anim;
    
    /**
    * A widget to control accordion views.
    * @namespace YAHOO.widget
    * @class AccordionView
    * @extends YAHOO.util.Element
    * @constructor
    * @param {HTMLElement | String} el The id of the html element that represents the AccordionView. 
    * @param {Object} oAttr (optional) A key map of the AccordionView's 
    * initial oAttributes.  
    */

    var AccordionView = function(el, oAttr) {
        
        el = YUD.get(el);
        
        // some sensible defaults
        
        oAttr = oAttr || {};

        if(!el) {
            el = document.createElement('ul');
            //YUD.generateId(el, 'acc-');
        }
        if (el.id) {oAttr.id = el.id; }
        YAHOO.widget.AccordionView.superclass.constructor.call(this, el, oAttr); 


        /**
        * Accordion events
        *
        * panelOpen: before a panel opens. Returns the panel element when it fires
        * panelClose: before a panel closes. Returns the panel element when it fires
        * afterPanelOpen: after a panel has finished opening. Returns the panel element when it fires
        * afterPanelClose: after a panel has finished closing. Returns the panel element when it fires
        * accordionInitialized: fires after the acordion is ready to use Returns the accordion element when it fires
        *
        */

        this._initList(el, oAttr);
                
        // This refresh forces all defaults to be set
         
        this.refresh(['id', 'width','hoverActivated'],true);  
        this.fireEvent('accordionInitialized');
    };

    YAHOO.widget.AccordionView = AccordionView;
    
    YAHOO.extend(AccordionView, YAHOO.util.Element, {
                
        /**
        * Initialize attributes for the Acordion
        * @param {Object} oAttr attributes key map
        * @method initAttributes
        */
        
        initAttributes: function (oAttr) {
            AccordionView.superclass.initAttributes.call(this, oAttr);
            var bAnimate = (YAHOO.env.modules.animation) ? true : false;    
            this.setAttributeConfig('id', {
                writeOnce: true,
                validator: function (value) {
                    return (/^[a-zA-Z][\w0-9\-_.:]*$/.test(value));
                },
                value: YUD.generateId(),
                method: function (value) {
                    this.get('element').id = value;
                }
            });
            this.setAttributeConfig('width', {
                value: '400px',
                method: function (value) {
                    this.setStyle('width', value);
                    }
                }
            );
            this.setAttributeConfig('animationSpeed', {
                value: 0.7
                }
            );
            this.setAttributeConfig('animate', {
                value: bAnimate,
                validator: YAHOO.lang.isBoolean
                }
            );          
            this.setAttributeConfig('collapsible', {
                value: false,
                validator: YAHOO.lang.isBoolean
                }
            );
            this.setAttributeConfig('expandable', {
                value: false,
                validator: YAHOO.lang.isBoolean
                }
            );
            this.setAttributeConfig('effect', {
                value: YAHOO.util.Easing.easeBoth,
                validator: YAHOO.lang.isString

                }
            );
            this.setAttributeConfig('hoverActivated', {
                    value: false,
                    validator: YAHOO.lang.isBoolean,
                    method: function (value) {
                            if (value) {
                                    YUE.on(this, 'mouseover', this._onMouseOver, this, true);                        
                            } else {
                                    YUE.removeListener(this, 'mouseover', this._onMouseOver);
                            }        
                    }
            });
            this.setAttributeConfig('_hoverTimeout', {
                value: 500,
                validator: YAHOO.lang.isInteger
                }
            );
        },
        
        /**
        * The className to add when building from scratch. 
        * @property CLASSNAME
        * @default "yui-accordionview"
        */

        CLASSNAME : 'yui-accordionview',
        
        /**
        * Prefix to use for any other classnames
        * @property PREFIX
        * @default "yui-accordion-"
        */
        
        PREFIX : 'yui-accordion-',
        
        /**
        * Internal counter to make sure id's stay unique
        * @property _idCounter
        * @private
        */
        
        _idCounter : '1',
        
        /**
        * Holds the timer for hover activated accordions
        * @property _hoverTimer
        * @private
        */
        
        _hoverTimer : null,      


        /**
        * Initialize the list / accordion
        * @param {HTMLElement} el The element for the accordion
        * @param {Object} oAttr attributes key map
        * @method _initList
        * @private
        */

        _initList : function(el, oAttr) {  
            YUD.addClass(el, this.CLASSNAME);
            el.setAttribute('role', 'tablist');
            var aCollectedItems = [];
            var aListItems = el.getElementsByTagName('LI');

            for(var i=0;i<aListItems.length;i++) {
                if(aListItems[i].parentNode === el) {
                            for (var eHeader = aListItems[i].firstChild; eHeader && eHeader.nodeType != 1; eHeader = eHeader.nextSibling) {
                    // This loop looks for the first non-textNode element
                    }
                    if (eHeader) {
                        for (var eContent = eHeader.nextSibling; eContent && eContent .nodeType != 1; eContent = eContent .nextSibling) {
                        // here we go for the second non-textNode element, if there was a first one
                        }
                    aCollectedItems.push({label: eHeader.innerHTML, content: (eContent && eContent.innerHTML)});
                    }
                }
            }
            el.innerHTML = '';

            this.addPanels(aCollectedItems);
            var aItems = YUD.getElementsByClassName('yui-accordion-content' ,'div', this);
            if((oAttr.expandItem === 0) || (oAttr.expandItem > 0)) {            
                YUD.removeClass(aItems[oAttr.expandItem], 'hidden');            
                var l = YUD.getElementsByClassName('yui-accordion-toggle', 'a', this)[oAttr.expandItem];
                var c = YUD.getElementsByClassName('yui-accordion-content', 'div', this)[oAttr.expandItem];
                if(l && c) {
                    YUD.addClass(l, 'active');
                    this._setARIA(l, 'aria-pressed', 'true');
                    this._setARIA(c, 'aria-hidden', 'false');
                }
            }

            if(true === this.get('hoverActivated')) {
                    YUE.on(el, 'mouseover', this._onMouseOver, this, true);        
                    YUE.on(el, 'mouseout', this._onMouseOut, this, true);         
            }   
            this.on('click', this._onClick, this, true);
        },
        
        /**
        * Wrapper around setAttribute to make sure we only set ARIA roles and states
        * in browsers that support it
        * @ethod _setARIA
        * @param {HTMLElement} el the element to set the attribute on
        * @param {String} sAttr the attribute name
        * @param {String} sValue the value for the attribute
        * @private
        */
        
        _setARIA : function(el, sAttr, sValue) {
            if(YAHOO.env.ua.ie > 7 || YAHOO.env.ua.gecko >= 1.9) {
                el.setAttribute(sAttr, sValue);
            }
        },
        
        /**
        * Closes all panels 
        * @method _collapseAccordion
        * @private
        * @return {Array} aItems array containing all content panel elements
        */

        _collapseAccordion : function() {
            var aItems = YUD.getElementsByClassName('yui-accordion-content' ,'div', this);    
            YUD.batch(aItems, function(e) {
                if(!YUD.hasClass(this.parentNode, 'yui-accordion-content')) { 
                    YUD.removeClass(e.parentNode.firstChild, 'active');
                    YUD.addClass(e, 'hidden');
                    this._setARIA(e, 'aria-hidden', 'true');
                }
            }, this);

            return aItems;
        },

        /**
        * Adds an Accordion panel to the AccordionView instance.  
        * If no index is specified, the panel is added to the end of the tab list.
        * @method addPanel
        * @param {Object} oAttr A key map of the Panel's properties
        * @param {Integer} nIndex The position to add the tab. 
        */

        addPanel : function(oAttr, nIndex) {
            var oPanelParent = document.createElement('li');
            YUD.addClass(oPanelParent, this.PREFIX + 'panel');
            
            var elIndicator = document.createElement('span');
            YUD.addClass(elIndicator, 'indicator');
            
            var elPanelLink = oPanelParent.appendChild(document.createElement('a'));
            elPanelLink.id = this.get('element').id + '-' + this._idCounter + '-label';
            elPanelLink.setAttribute('role', 'tab');
            elPanelLink.innerHTML = oAttr.label || '';
            elPanelLink.appendChild(elIndicator);
            elPanelLink.href = oAttr.href || '#toggle';
            YUD.addClass(elPanelLink, this.PREFIX + 'toggle');
        
            var elPanelContent = document.createElement('div');
            elPanelContent.setAttribute('role', 'tabpanel');
            elPanelContent.innerHTML = oAttr.content || '';
            this._setARIA(elPanelContent, 'aria-labelledby', elPanelLink.id);
            YUD.addClass(elPanelContent, this.PREFIX + 'content');
            oPanelParent.appendChild(elPanelContent);
            
            this._idCounter++;

            if((nIndex !== null) && (nIndex !== undefined)) {
                var panelBefore = this.getPanel(nIndex);
                this.insertBefore(oPanelParent, panelBefore);
            }
            else {
                this.appendChild(oPanelParent);
            }

            if(oAttr.expand) {
                if(!this.get('expandable')) {
                    this._collapseAccordion();
                }
                YUD.removeClass(elPanelContent, 'hidden');
                YUD.addClass(elPanelLink, 'active');
                this._setARIA(elPanelContent, 'aria-hidden', 'false');
                this._setARIA(elPanelLink, 'aria-pressed', 'true');
            }
            else {
                YUD.addClass(elPanelContent, 'hidden');
                this._setARIA(elPanelContent, 'aria-hidden', 'true');
                this._setARIA(elPanelLink, 'aria-pressed', 'false');
            }
        },

        /**
        * Wrapper around addPanel to add multiple panels in one call
        * @method addPanels
        * @param {Array} oPanels array holding all individual panel configs
        */

        addPanels : function(oPanels) {
            for(var i=0;i<oPanels.length;i++) {
                this.addPanel(oPanels[i]);
            }
        },

        /**
        * Removes the specified Panel from the AccordionView.
        * @method removePanel
        * @param {Integer} index of the panel to be removed
        */

        removePanel : function(index) {
            this.removeChild(YUD.getElementsByClassName('yui-accordion-panel', 'li', this)[index]);     
        },

        /**
        * Returns the HTMLElement of the panel at the specified index.
        * @method getPanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {HTMLElement} the requested panel element
        */

        getPanel : function(nIndex) {
            return this.getElementsByClassName('yui-accordion-panel', 'li')[nIndex];
        },

        /**
        * Open a panel
        * @method openPanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {Boolean} whether action resulted in opening a panel
        * that was previously closed
        */

        openPanel : function(nIndex) {
            var ePanelNode = this.getElementsByClassName('yui-accordion-toggle')[nIndex];
            if(!ePanelNode) {return false;} // invalid node
            if(YUD.hasClass(ePanelNode, 'active')) {return false;} // already open
            var eElementNode = this.getElementsByClassName('yui-accordion-content')[nIndex];
            this._onClick(ePanelNode);
            return true;
        },

        /**
        * Close a panel
        * @method closePanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {Boolean} whether action resulted in closing a panel or not
        * that was previously open
        *
        * This method honors all constraints imposed by the properties collapsible and expandable
        * and will return false if the panel can't be closed because of a constraint in addition
        * to if it was already closed
        *
        */

        closePanel : function(nIndex) {
            var aItems = this.getElementsByClassName('yui-accordion-toggle');
            var ePanelNode = aItems[nIndex];
            if(!ePanelNode) {return false;} // invalid node
            if(!YUD.hasClass(ePanelNode, 'active')) {return true;} // already closed
            if(this.get('collapsible') === false) {
                if(this.get('expandable') === true) {
                    this.set('collapsible', true);
                    for(var i=0;i<aItems.length;i++) {
                        if((YUD.hasClass(aItems[i], 'active') && i !== nIndex)) {
                            this._onClick(ePanelNode);
                            this.set('collapsible', false);               
                            return true;
                        }
                    }
                    this.set('collapsible', false);                
                }
            } // can't collapse
            var eElementNode = this.getElementsByClassName('yui-accordion-content')[nIndex];
            this._onClick(ePanelNode);
            return true;
        },


        /**
        * Mouseover event handler
        * @method _onMouseOver
        * @param {Event} ev The Dom event
        * @private
        */

        _onMouseOver : function(ev) {
            YUE.stopPropagation(ev);
            // must provide the TARGET or IE will destroy the event before we can
            // use it. Thanks Nicholas Zakas for pointing this out to me
            var target = YUE.getTarget(ev);
            this._hoverTimer = YAHOO.lang.later(this.get('_hoverTimeout'), this, function(){
                this._onClick(target);
             });
        },

        /**
        * Mouseout event handler
        * Cancels the timer set by AccordionView::_onMouseOver
        * @method _onMouseOut
        * @param {Event} ev The Dom event
        * @private
        */
        
        _onMouseOut : function() {
            if (this._hoverTimer) { 
				this._hoverTimer.cancel();
				this._hoverTimer = null;
			}
        },
        
        /**
        * Global event handler for mouse clicks
        * This method can accept both an event and a node so it can be called internally if needed
        * @method _onClick
        * @param {HTMLElement|Event} arg The Dom event or event target
        * @private
        */

        _onClick : function(arg) {
            var ev;

            if(arg.nodeType === undefined) {
                ev = YUE.getTarget(arg);
                if(!YUD.hasClass(ev, 'yui-accordion-toggle') && !YUD.hasClass(ev, 'indicator')) {
                    return false;
                }
		YUE.preventDefault(arg);
                YUE.stopPropagation(arg);
            }
            else {
                ev = arg;
            }

            var elClickedNode = ev;

            /**
            *
            * helper function to fix IE problems with nested accordions
            * still looking for something better but for now this will have to do
            * @param {Object} el element to apply the fix to
            * @param {String} sHide whether to set visibility to hidden or visible
            *
            */

            function iehide(el, sHide) {
                if(YAHOO.env.ua.ie < 7 && YAHOO.env.ua.ie > 0) {
                    var aInnerAccordions = YUD.getElementsByClassName('yui-accordionview', 'ul', el);
                    if(aInnerAccordions[0]) {
                        YUD.setStyle(aInnerAccordions[0], 'visibility', sHide);
                    }
                }
            }

            /**
            *
            * Toggle an accordion panel
            * @param {Object} el element to toggle
            * @param {Object} elClicked the element that was clicked to toggle the corresponding panel
            *
            */

            function toggleItem(el, elClicked) {      
                var that = this;
				function fireEvent(type,panel) {
					if (!YUD.hasClass(panel,'yui-accordion-panel')) {
						panel = YUD.getAncestorByClassName(panel,'yui-accordion-panel');
					}
					for (var i = 0, p = panel; p.previousSibling; i++) {
						p = p.previousSibling;
					}
					return that.fireEvent(type, {panel: panel, index: i});
				}
                      
                if(!elClicked) {
                    if(!el) { return false ;}
                    elClicked = el.parentNode.firstChild;
                }
                var oOptions = {};
                var bHideAfter = false;
                var nHeight = 0;
                var bIsHidden = YUD.hasClass(el, 'hidden');
                if(!YUD.hasClass(el, 'hidden')) {
                    bHideAfter = true;
                }
                if(this.get('animate')) {
                    if(YUD.hasClass(el, 'hidden')) {
                        // still a bit experimental. trying to eliminate mild flash sometimes seen in FF //
                        YUD.setStyle(el, 'display', 'block');
                        YUD.addClass(el, 'almosthidden');
                        YUD.removeClass(el, 'hidden');
                        nHeight = el.offsetHeight;
                        YUD.setStyle(el, 'height', 0);
                        YUD.removeClass(el, 'almosthidden');
                        oOptions = {height: {from: 0, to: nHeight}};
                    }
                    else {
                        nHeight = el.offsetHeight;
                        oOptions = {height: {from: nHeight, to: 0}};
                    }
                    var nSpeed = (this.get('animationSpeed')) ? this.get('animationSpeed') : 0.5;
                    var sEffect = (this.get('effect')) ? this.get('effect') : YAHOO.util.Easing.easeBoth;
                    var oAnimator = new YUA(el, oOptions, nSpeed, sEffect);
                    var that = this;
                    if(bHideAfter) {
                        if (this.fireEvent('panelClose', el) === false) { return; }
                        YUD.removeClass(elClicked, 'active');
                        iehide(el, 'hidden');
                        that._setARIA(el, 'aria-hidden', 'true');
                        that._setARIA(elClicked, 'aria-pressed', 'false');
                        oAnimator.onComplete.subscribe(function(){
                            YUD.addClass(el, 'hidden');
                            YUD.setStyle(el, 'height', 'auto');
                            YUD.setStyle(el, 'display', 'none');
                            fireEvent('afterPanelClose', el);
                        });
                    }
                    else {
                        if (fireEvent('panelOpen', el) === false) { return; }
                        //changed from visible to hidden so it doesn't show up behind the parent accordion until after the animation
                        iehide(el, 'hidden');
                        oAnimator.onComplete.subscribe(function(){
                            YUD.setStyle(el, 'height', 'auto');
                            //Added to make the inner accordion visible again
                            iehide(el, 'visible');
                            that._setARIA(el, 'aria-hidden', 'false');
                            that._setARIA(elClicked, 'aria-pressed', 'true');
                            fireEvent('afterPanelOpen', el);
                        });                 
                        YUD.addClass(elClicked, 'active');
                    }
                    oAnimator.animate();
                }
                else {
                    if(bHideAfter) {
                        if (fireEvent('panelClose', el) === false) { return; }
                        YUD.addClass(el, 'hidden');
                        YUD.setStyle(el, 'height', 'auto');
                        YUD.setStyle(el, 'display', 'none');
                        YUD.removeClass(elClicked, 'active');
                        that._setARIA(el, 'aria-hidden', 'true');
                        that._setARIA(elClicked, 'aria-pressed', 'false');
                        fireEvent('afterPanelClose', el);
                    }
                    else {
                        if (fireEvent('panelOpen', el) === false) { return; }
                        YUD.removeClass(el, 'hidden');
                        YUD.setStyle(el, 'height', 'auto');
                        YUD.setStyle(el, 'display', 'block');
                        YUD.addClass(elClicked, 'active');
                        that._setARIA(el, 'aria-hidden', 'false');
                        that._setARIA(elClicked, 'aria-pressed', 'true');
                        fireEvent('afterPanelOpen', el);
                    }
                }
                return true;
            }
            var eTargetListNode = (elClickedNode.nodeName.toUpperCase() === 'SPAN') ? elClickedNode.parentNode.parentNode : elClickedNode.parentNode;

            var containedPanel = YUD.getElementsByClassName('yui-accordion-content', 'div', eTargetListNode)[0]; 

            if(this.get('collapsible') === false) {
                if (!YUD.hasClass(containedPanel, 'hidden')) {
                    return false;
                }
            }
            else {
                if(!YUD.hasClass(containedPanel, 'hidden')) {
                    toggleItem.call(this, containedPanel);
                    return false;               
                }
            }
            if (this.fireEvent('beforeStateChange', this) === false) { return; }        

            if(this.get('expandable') !== true) {
                var aPanelCollection = YUD.getElementsByClassName('yui-accordion-content', 'div', this);

                // skip panels within a panel

                for(var element in aPanelCollection) {
                    if(this.get('element') === aPanelCollection[element].parentNode.parentNode) {
                        var bMustToggle = YUD.hasClass(aPanelCollection[element], 'hidden');
                        if(!bMustToggle) {
                            toggleItem.call(this,aPanelCollection[element]);
                        }
                    }
                }
            }
            if(elClickedNode.nodeName.toUpperCase() === 'SPAN')  {
                toggleItem.call(this, containedPanel, elClickedNode.parentNode);
            }
            else {
                toggleItem.call(this, containedPanel, elClickedNode);
            }
            return true;
        },
        
        /**
        * Provides a readable name for the AccordionView instance.
        * @method toString
        * @return {String} String representation of the object 
        */
        toString : function() {
            var name = this.get('id') || this.get('tagName');
            return "AccordionView " + name; 
        }
    });    
})();

YAHOO.util.Event.onDOMReady(function()
{
	YAHOO.register("accordionview", YAHOO.widget.AccordionView, {version: "0.99", build: "23"});
});

