/**
 *  Zebra_Accordion
 *
 *  A tiny (2KB minified) accordion plugin for jQuery.
 *
 *  It transforms a basic definition list, without requiring any other specific markup, into a small-footprint, easily
 *  configurable, fully customizable, cross-browser accordion widget, useful for better organizing larger groups of content.
 *
 *  Features:
 *
 *  -   no additional markup required other than a basic definition list;
 *  -   easily customizable through CSS;
 *  -   can be configured to work so that only a single can be expanded at a time, or so that all tabs may be
 *      expanded/collapsed;
 *  -   can be configured to work so tabs expand on mouse over;
 *  -   when, after expanding a tab, part of its content is outside the viewport, it automatically scrolls the browser's
 *      window so that the tab's content is visible;
 *  -   callback functions can be used for further customizations;
 *  -   works in all major browsers (Firefox, Opera, Safari, Chrome, Internet Explorer 6, 7, 8, 9)
 *
 *  Visit {@link http://stefangabos.ro/jquery/zebra-accordion/} for more information.
 *
 *  For more resources visit {@link http://stefangabos.ro/}
 *
 *  @author     Stefan Gabos <contact@stefangabos.ro>
 *  @version    1.0 (last revision: September 25, 2011)
 *  @copyright  (c) 2011 Stefan Gabos
 *  @license    http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE
 *  @package    Zebra_Accordion
 */
;(function($) {

    $.Zebra_Accordion = function(el, options) {

        var defaults = {

            animate_opacity:        true,                       //  Should the content block's opacity be also animated?
                                                                //
                                                                //  Default is TRUE

            collapsible:            false,                      //  If set to TRUE, an open block can also be collapsed
                                                                //  - in this scenario, all blocks can be collapsed; if
                                                                //  set to FALSE, an open block can be collapsed only by
                                                                //  opening another block - in this scenario, only a
                                                                //  single block is open at any given moment;
                                                                //
                                                                //  Default is FALSE

            expanded_class:         'Zebra_Accordion_Expanded', //  The name of the class to append to the "title"
                                                                //  element when its associated content block is
                                                                //  expanded.
                                                                //
                                                                //  Default is "Zebra_Accordion_Expanded"

            hide_speed:             400,                        //  The speed (in milliseconds) to use when collapsing
                                                                //  content blocks.
                                                                //
                                                                //  Default is 400

            scroll_speed:           600,                        //  If a content block's content is not entirely visible
                                                                //  after it is expanded, the window will be scrolled so
                                                                //  so that the entire content of the content block is
                                                                //  visible (if is possible).
                                                                //
                                                                //  This value represents the speed (in milliseconds) to
                                                                //  use for scrolling.
                                                                //
                                                                //  Default is 600

            show_speed:             400,                        //  The speed (in milliseconds) to use when expanding
                                                                //  content blocks.
                                                                //
                                                                //  Default is 400

            show:                   0,                          //  The index (0 based) of the content block to be
                                                                //  expanded by default.
                                                                //
                                                                //  If "collapsible" is TRUE, the value of this property
                                                                //  can also be boolean FALSE, indicating that all tabs
                                                                //  should be collapsed by default.
                                                                //
                                                                //  Default is 0

            toggle_on_mouseover:    false,                      //  Set this to TRUE if content blocks should be expanded
                                                                //  when hovering their associated "titles".
                                                                //
                                                                //  If "collapsible" is TRUE then this property will
                                                                //  always be FALSE!
                                                                //
                                                                //  Default is FALSE;

            onClose:                null,                       //  Event fired when a content block is closed.
                                                                //
                                                                //  The callback function (if any) receives as arguments
                                                                //  the closed item's number (0 based), the title element
                                                                //  and the content block element.

            onOpen:                 null                        //  Event fired when a content block is open.
                                                                //
                                                                //  The callback function (if any) receives as arguments
                                                                //  the opened item's number (0 based), the title element
                                                                //  and the content block element.

        }

        // to avoid confusions, we use "plugin" to reference the current instance of the object
         var plugin = this;

        plugin.settings = {}

        /**
         *  Constructor method
         *
         *  @return object  Returns a reference to the plugin
         */
        var init = function() {

            // the plugin's final properties are the merged default and user-provided options (if any)
            plugin.settings = $.extend({}, defaults, options);

            $element = $(el);   // reference to the jQuery version of DOM element the plugin is attached to
            element = el;       // reference to the actual DOM element

            // this will hold references to the title and their heights
            plugin.titles = [];

            // this will hold references to the content blocks and their heights
            plugin.blocks = [];

            // iterate through the content titles
            $element.children('dt').each(function(index) {

                var

                    // reference to the element
                    $title = $(this),

                    // the event that should trigger content blocks expansion/collapse
                    event = !plugin.settings.collapsible && plugin.settings.toggle_on_mouseover ?
                                'mouseover' :
                                'click';

                plugin.titles.push({
                    '$element': $title,                 // reference to the element
                    'height':   $title.outerHeight()    // the title's height, including margins and padding
                });

                // handle the "click" event
                $title.bind(event, function() {

                    // show the associated content block
                    plugin.show(index);

                });

            });

            // iterate through the content blocks
            $element.children('dd').each(function(index) {

                //  reference to the content block object
                var $block = $(this);

                // temporary make the content block visible,
                // in order to get some of the element's properties
                $block.css({
                    'visibility':   'hidden',
                    'display':      'block'
                });

                // get some of the element's properties
                // needed to correctly expand/collapse the block
                plugin.blocks.push({
                    '$element':             $block,
                    'height':               $block.height(),
                    'outerHeight':          $block.outerHeight(),
                    'paddingTop':           _int($block.css('paddingTop')),
                    'paddingBottom':        _int($block.css('paddingBottom')),
                    'marginTop':            _int($block.css('marginTop')),
                    'marginBottom':         _int($block.css('marginBottom')),
                    'borderTopWidth':       _int($block.css('borderTopWidth')),
                    'borderBottomWidth':    _int($block.css('borderBottomWidth'))
                });
                // all blocks are collapsed by default
                plugin.hide(index, true);

            });

            // if
            if (

                // accordion is not collapsible, make sure the index is an integer
                !plugin.settings.collapsible ||
                // accordion is collapsible, but the index is not FALSE
                (plugin.settings.show !== false)

            // make sure the index is an integer
            ) plugin.settings.show = _int(plugin.settings.show);

            // make sure the index of the content block to be expanded by default is valid
            if (plugin.settings.show < 0 && plugin.settings.show > plugin.blocks.length - 1) plugin.settings.show = 0;

            // if a content block is to be shown by default
            if (plugin.settings.show !== false)

                // show the default content block
                plugin.show(plugin.settings.show, true, true);

        }

        /**
         *  Expands a content block.
         *
         *  @param  integer index       The 0-based index of the content block to expand.
         *
         *  @param  boolean noFx        (Optional) If set to TRUE, the content block will be instantly expanded without
         *                              animation.
         *
         *                              Default is FALSE.
         *
         *  @param  boolean noScroll    (Optional) If set to TRUE, the browser window will not be scrolled to the newly
         *                              expanded content block.
         *
         *                              Default is FALSE.
         *  @return void
         */
        plugin.show = function(index, noFx, noScroll) {

            var block = plugin.blocks[index],           //  reference to the entry in the cache array
                $block = block.$element,                //  reference to the content block element
                $title = plugin.titles[index].$element; //  reference to the title element

            // if blocks can be opened and collapsed
            // and current block is expanded, collapse it instead
            if (plugin.settings.collapsible && $block.height() > 0) return plugin.hide(index);

            // if only a single block can be expanded at any given moment
            if (plugin.settings.collapsible)

                // iterate through the content blocks
                $.each(plugin.blocks, function(el_index) {

                    // if not the the block that we are about to expand
                    // collapse the content block
                    if (el_index != index) plugin.hide(el_index);

                });

            // add a class to the title element, to indicate that the element is expanded
            $title.addClass(plugin.settings.expanded_class)

            // set the content block's "display" property to "block"
            $block.css('display', 'block');

            // stop any ongoing animation on the current content block
            $block.stop();

            // expand the indicated content block
            $block.animate({

                'height':               block.height,
                'paddingTop':           block.paddingTop,
                'paddingBottom':        block.paddingBottom,
                'marginTop':            block.marginTop,
                'marginBottom':         block.marginBottom,
                'opacity':              1

            // the animation's speed
            }, (noFx ? 0 : plugin.settings.show_speed),

            // once the animation is complete
            function() {

                // if a callback function exists for when opening a content block
                if (plugin.settings.onOpen && typeof plugin.settings.onOpen == 'function')

                    // execute the callback function
                    plugin.settings.onOpen(index, $title, $block);

                // if scrolling is not explicitly disabled
                if (!noScroll) {

                    var title_top = Math.round($title.offset().top - 80),                //  the title's "top" position
                        title_height = plugin.titles[index].height,                 //  the title's height
                        block_height = block.outerHeight,                           //  the content block's height
                        total_height = title_top + title_height + block_height,     //  item's total height
                        viewport_height = $(window).height(),                       //  visible area in the browser
                        viewport_scroll = $(window).scrollTop(),                    //  how much is the page scrolled down (from the top)
                        offset = null;

                    // if a content block's bottom goes out of the view
                    if (total_height > viewport_height + viewport_scroll) {

                        // this is how much the page needs to be scrolled down (from the top) so that the content is visible
                        offset = total_height - viewport_height;

                        // but, if that would mean that the title would end up above the visible area
                        // better to scroll to the title instead
                        if (offset > title_top) offset = title_top;

                    } 

                    // if title is above the visible area, scroll back up, so that the title becomes visible
                    if (title_top < viewport_scroll) offset = title_top;

                    // if we need to scroll the content
                    if (offset)

                        // smoothly scroll the page
                        $('html, body').animate({

                            scrollTop: offset

                        // the animation's speed
                        }, plugin.settings.scroll_speed);

                } 

            });

        }

        /**
         *  Collapses a content block.
         *
         *  @param  integer index   The 0-based index of the content block to collapse.
         *
         *  @param  boolean noFx    (Optional) If set to TRUE, the content block will be instantly collapsed without
         *                          animation.
         *
         *                          Default is FALSE.
         *
         *  @return void
         */
        plugin.hide = function(index, noFx) {

            var $title = plugin.titles[index].$element, //  reference to the "title" element
                block = plugin.blocks[index]            //  reference to the entry in the cache array
                $block = block.$element;                //  reference to the content block

            // remove the extra class added when the block was expanded
            $title.removeClass(plugin.settings.expanded_class);

            // stop any ongoing animation for the current content block
            $block.stop();

            // supress top and bottom borders
            $block.css({
                'borderTopWidth':       0,
                'borderBottomWidth':    0
            });

            // hide the element
            $block.animate({

                'height':           0,
                'paddingTop':       0,
                'paddingBottom':    0,
                'marginTop':        0,
                'marginBottom':     0,
                'opacity':          (plugin.settings.animate_opacity ? 0 : 1)

            // the animation's speed
            }, (noFx ? 0 : plugin.settings.hide_speed),

            // once the animation is complete
            function() {

                // if a callback function exists for when closing a content block
                if (plugin.settings.onClose && typeof plugin.settings.onClose == 'function')

                    // execute the callback function
                    plugin.settings.onClose(index, $title, $block);

                // set some of element's css properties
                $(this).css({
                    'display':              'none',
                    'visibility':           'visible',
                    'borderTopWidth':       block.borderTopWidth,
                    'borderBottomWidth':    block.borderBottomWidth
                });

            });

        }

        /**
         *  A wrapper to JavaScript's parseInt() function.
         *
         *  @return int     Returns the integer representation of the string given as argument
         *
         *  @access private
         */
        var _int = function(value) {

            var nr = parseInt(value, 10);
            return isNaN(nr) ? 0 : nr;

        }

        // fire it up!
        init();

    }

})(jQuery);
