YUI.add('aui-toggler-base', function (A, NAME) {

/**
 * The Toggler Component
 *
 * @module aui-toggler
 */

var Lang = A.Lang,
    isBoolean = Lang.isBoolean,
    isObject = Lang.isObject,
    isString = Lang.isString,
    isUndefined = Lang.isUndefined,

    toInt = Lang.toInt,
    CUBIC_BEZIER = 'cubic-bezier(0, 0.1, 0, 1.0)',

    getCN = A.getClassName,

    CSS_TOGGLER_CONTENT = getCN('toggler', 'content'),
    CSS_TOGGLER_CONTENT_COLLAPSED = getCN('toggler', 'content', 'collapsed'),
    CSS_TOGGLER_CONTENT_EXPANDED = getCN('toggler', 'content', 'expanded'),
    CSS_TOGGLER_CONTENT_WRAPPER = getCN('toggler', 'content', 'wrapper'),
    CSS_TOGGLER_HEADER = getCN('toggler', 'header'),
    CSS_TOGGLER_HEADER_COLLAPSED = getCN('toggler', 'header', 'collapsed'),
    CSS_TOGGLER_HEADER_EXPANDED = getCN('toggler', 'header', 'expanded'),

    CSS_TOGGLER_CONTENT_STATE = {
        'false': CSS_TOGGLER_CONTENT_COLLAPSED,
        'true': CSS_TOGGLER_CONTENT_EXPANDED
    },

    CSS_TOGGLER_HEADER_STATE = {
        'false': CSS_TOGGLER_HEADER_COLLAPSED,
        'true': CSS_TOGGLER_HEADER_EXPANDED
    },

    TPL_CONTENT_WRAPPER = '<div class="' + CSS_TOGGLER_CONTENT_WRAPPER + '"></div>';

/**
 * A base class for Toggler.
 *
 * Check the [live demo](http://alloyui.com/examples/toggler/).
 *
 * @class A.Toggler
 * @extends Base
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */
var Toggler = A.Component.create({

    /**
     * Static property provides a string to identify the class.
     *
     * @property NAME
     * @type String
     * @static
     */
    NAME: 'toggler',

    /**
     * Static property used to define the default attribute
     * configuration for the `A.Toggler`.
     *
     * @property ATTRS
     * @type Object
     * @static
     */
    ATTRS: {

        /**
         * Determine if the `A.Toggler` transitions will animate.
         *
         * @attribute animated
         * @default false
         * @type Boolean
         * @writeOnce
         */
        animated: {
            validator: isBoolean,
            value: false,
            writeOnce: true
        },

        /**
         * Determine if the `A.Toggler` transitions are being animated in that
         * moment.
         *
         * @attribute animating
         * @default false
         * @type Boolean
         */
        animating: {
            validator: isBoolean,
            value: false
        },

        /**
         * Determine if the `A.Toggler` should bind DOM events or not.
         *
         * @attribute bindDOMEvents
         * @default true
         * @type Boolean
         * @writeOnce
         */
        bindDOMEvents: {
            validator: isBoolean,
            value: true,
            writeOnce: true
        },

        /**
         * The content of a Toogler instance.
         *
         * @attribute content
         */
        content: {
            setter: A.one
        },

        /**
         * Determine if the content starts as toggled on/off on page load.
         *
         * @attribute expanded
         * @default true
         * @type Boolean
         */
        expanded: {
            validator: isBoolean,
            value: true
        },

        /**
         * The header of a Toogler instance.
         *
         * @attribute header
         */
        header: {
            setter: A.one
        },

        /**
         * User interaction that triggers the Toggler instance.
         *
         * @attribute toggleEvent
         * @type String
         * @writeOnce
         */
        toggleEvent: {
            validator: isString,
            value: 'tap',
            writeOnce: true
        },

        /**
         * Transition definitions such as duration and type of easing effect.
         *
         * @attribute transition
         * @type Object
         */
        transition: {
            validator: isObject,
            value: {
                duration: 0.4,
                easing: CUBIC_BEZIER
            }
        }

    },

    /**
     * Static property used to define which component it extends.
     *
     * @property EXTENDS
     * @type Object
     * @static
     */
    EXTENDS: A.Base,

    /**
     * Handle header events.
     *
     * @method headerEventHandler
     * @param event
     * @param instance
     */
    headerEventHandler: function(event, instance) {
        if (event.type === instance.get('toggleEvent') || event.isKey('enter') || event.isKey('space')) {
            return instance.toggle();
        }
        else if (event.isKey('down') || event.isKey('right') || event.isKey('num_plus')) {
            return instance.expand();
        }
        else if (event.isKey('up') || event.isKey('left') || event.isKey('esc') || event.isKey('num_minus')) {
            return instance.collapse();
        }
    },

    prototype: {

        /**
         * Construction logic executed during `A.Toggler` instantiation. Lifecycle.
         *
         * @method initializer
         * @protected
         */
        initializer: function() {
            var instance = this;

            instance.bindUI();
            instance.syncUI();

            instance._uiSetExpanded(instance.get('expanded'));
        },

        /**
         * Bind the events on the `A.Toggler` UI. Lifecycle.
         *
         * @method bindUI
         * @protected
         */
        bindUI: function() {
            var instance = this;
            var header = instance.get('header');

            header.setData('toggler', instance);

            var eventHandles = [
                instance.on('expandedChange', A.bind(instance._onExpandedChange, instance))
            ];

            if (instance.get('bindDOMEvents')) {
                eventHandles.push(
                    header.on([instance.get('toggleEvent'), 'keydown'], A.rbind(Toggler.headerEventHandler, null, instance))
                );
            }

            instance._eventHandles = eventHandles;
        },

        /**
         * Sync the events on the `A.Toggler` UI. Lifecycle.
         *
         * @method syncUI
         * @protected
         */
        syncUI: function() {
            var instance = this;

            instance.get('content').addClass(CSS_TOGGLER_CONTENT);
            instance.get('header').addClass(CSS_TOGGLER_HEADER);
        },

        /**
         * Destructor lifecycle implementation for the `A.Toggler` class.
         *
         * @method destructor
         * @protected
         */
        destructor: function() {
            var instance = this,
                content,
                wrapper;

            instance.get('header').setData('toggler', null);

            if (instance.wrapped) {
                content = instance.get('content');
                wrapper = content.get('parentNode');

                wrapper.insert(content, 'before');
                wrapper.remove();
            }

            (new A.EventHandle(instance._eventHandles)).detach();
        },

        /**
         * Expand/collapse `A.Toggler` with an animation.
         *
         * @method animate
         * @param config
         * @param fn
         */
        animate: function(config, fn) {
            var instance = this;

            instance._uiSetExpanded(true);

            var transition = A.merge(config, instance.get('transition'));

            instance.get('content').transition(transition, A.bind(fn, instance));
        },

        /**
         * Hide `A.Toggler` content.
         *
         * @method collapse
         * @param {Object} payload
         * @return {Boolean} expand
         */
        collapse: function(payload) {
            var instance = this;

            return instance.toggle(false, payload);
        },

        /**
         * Show `A.Toggler` content.
         *
         * @method expand
         * @param {Object} payload
         * @return {Boolean} expand
         */
        expand: function(payload) {
            var instance = this;

            return instance.toggle(true, payload);
        },

        /**
         * Return the height of content.
         *
         * @method getContentHeight
         * @return {Number} height
         */
        getContentHeight: function() {
            var instance = this;
            var content = instance.get('content');
            var expanded = instance.get('expanded'),
                height;

            if (!expanded) {
                instance._uiSetExpanded(true);
            }

            if (content.hasMethod('getBoundingClientRect')) {
                var preciseRegion = content.invoke('getBoundingClientRect');

                if (preciseRegion) {
                    height = preciseRegion.bottom - preciseRegion.top;
                }
            }
            else {
                height = content.get('offsetHeight');
            }

            if (!expanded) {
                instance._uiSetExpanded(false);
            }

            return height;
        },

        /**
         * Show or hide content.
         *
         * @method toggle
         * @param {Boolean} expand
         * @param {Object} payload
         * @return {Boolean} expand
         */
        toggle: function(expand, payload) {
            var instance = this,
                header = instance.get('header'),
                expanded;

            if (!header.test(':visible')) {
                return;
            }

            expanded = instance.get('expanded');

            if (isUndefined(expand)) {
                expand = !expanded;
            }

            if (expand !== expanded) {
                if (instance.get('animated')) {
                    if (instance.get('animating')) {
                        return expand;
                    }

                    instance._animation(expand, payload);
                }
                else {
                    instance.set('expanded', expand, payload);
                }
            }

            return expand;
        },

        /**
         * Apply animation on `toggle`.
         *
         * @method _animation
         * @param {Boolean} expand
         * @param {Object} payload
         * @protected
         */
        _animation: function(expand, payload) {
            var instance = this,
                content = instance.get('content'),
                gutter = instance.contentGutter,
                height = instance.getContentHeight();

            if (isUndefined(gutter)) {
                gutter = instance.contentGutter = toInt(content.getStyle('marginTop'));
            }

            if (!instance.wrapped) {
                instance._uiSetExpandedContent();

                if (expand) {
                    content.setStyle('marginTop', -(height + gutter));
                }
            }

            instance.set('animating', true);

            instance.animate(
                {
                    marginTop: (expand ? gutter : -(height + gutter)) + 'px'
                },
                function() {
                    instance.set('animating', false);

                    instance.set('expanded', expand, payload);
                }
            );
        },

        /**
         * Trigger when the `expanded` attribute change its value.
         *
         * @method _onExpandedChange
         * @param event
         * @protected
         */
        _onExpandedChange: function(event) {
            var instance = this;

            instance._uiSetExpanded(event.newVal);
        },

        /**
         * Set the "expanded"/"collapsed" classes on the UI.
         *
         * @method _uiSetExpanded
         * @param {Boolean} val
         * @protected
         */
        _uiSetExpanded: function(val) {
            var instance = this,
                expanded = instance.get('expanded');

            if (expanded && !instance.wrapped) {
                instance._uiSetExpandedContent();
            }

            instance.get('content').replaceClass(CSS_TOGGLER_CONTENT_STATE[!val], CSS_TOGGLER_CONTENT_STATE[val]);
            instance.get('header').replaceClass(CSS_TOGGLER_HEADER_STATE[!val], CSS_TOGGLER_HEADER_STATE[val]);
        },

        /**
         * Wrap the content HTML if `expanded` attribute is true.
         *
         * @method _uiSetExpandedContent
         * @protected
         */
        _uiSetExpandedContent: function() {
            var instance = this;

            instance.get('content').wrap(TPL_CONTENT_WRAPPER);

            instance.wrapped = true;
        }

    }
});

A.Toggler = Toggler;


}, '3.1.0-deprecated.112', {
    "requires": [
        "transition",
        "aui-selector",
        "aui-event-base",
        "aui-node",
        "aui-component",
        "event-tap"
    ],
    "skinnable": true
});
