﻿/**
* @author: PLY Interactive
* @dependency: jQuery 1.6.2
*/

// Wrap the ply object definition in a simple self-calling function to
// guarantee the definition of window, '$' and undefined as well as a cached
// jQuery document element for more efficient finds (see:
// http://www.codenothing.com/archives/2010/8-jquery-micro-optimization-tips/).
// Also creates a convenient closure to prevent DOM bloat.
(function(window, $, $document, undefined) {

    /**
    * setSafeDefault
    *
    * Set a safe default value when the previous value/existence is unknown (see:
    * http://webreflection.blogspot.com/2011/08/please-stop-reassigning-for-no-reason.html)
    *
    * @param {object} obj   REQUIRED - Object to check
    * @param {string} key   REQUIRED - Property/Method name
    * @param {object} value REQUIRED - Property/Method value
    */
    var setSafeDefault = function(obj, key, value) {
        key in obj || (obj[key] = value);
        // Return function to enable chaining
        return setSafeDefault;
    };


    /*---------------------------------------------------------------------------
    Native type extensions
    ---------------------------------------------------------------------------*/

    setSafeDefault(
    // Object create
        Object, 'create', function(o) {
            function f() {
            }
            f.prototype = o;
            return new f();
        })(
    // Object keys
        Object, 'keys', function(o) {
            var 
            result = [],
            key;
            for (key in o) {
                if (o.hasOwnProperty(key))
                    result.push(key);
            }
            return result;
        })(
    // Object count
        Object, 'count', function(o) {
            var 
            result = 0,
            key;
            for (key in o) {
                if (o.hasOwnProperty(key))
                    result++;
            }
            return result > 0 ? result : 0;
        });

    /*---------------------------------------------------------------------------
    Ply
    ---------------------------------------------------------------------------*/

    // Setup namespace
    setSafeDefault(window, 'ply', {});


    /*---------------------------------------------------------------------------
    Ply utils
    ---------------------------------------------------------------------------*/

    // Setup namespace
    setSafeDefault(ply, 'utils', {});

    /**
    * ply.utils.Enum
    *
    * @param {Array} keys REQUIRED - Enum keys
    */
    ply.utils.Enum = function(keys) {
        var i;
        for (i in keys) {
            this[keys[i]] = i;
        }
    };

    /**
    * ply.utils.getPropertyValueOrDefault
    *
    * @param {object} obj          REQUIRED - Object that contains the property
    * @param {string} propertyName REQUIRED - Property name
    * @param {object} defaultValue REQUIRED - Default if value is null or empty
    */
    ply.utils.getPropertyValueOrDefault = function(obj, propertyName, defaultValue) {
        return (obj.hasOwnProperty(propertyName) && obj[propertyName] != undefined) ? obj[propertyName] : defaultValue;
    };

    /**
    * ply.utils.pluginBridge
    *
    * Helps keep us DRY (see: http://en.wikipedia.org/wiki/Don't_repeat_yourself)
    * Leverages jQuery's data method to either create or return a given
    * plugin's constructor. We make use of previous work in jQuery UI (see:
    * https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js)
    * and David DeSandro's Isotope (see: https://github.com/desandro/isotope)
    *
    * @param {string}   name   REQUiRED - Plugin name.
    * @param {Function} object REQUIRED - Function to instantiate.
    *
    * @return {function} - Returns a generic plugin constructor
    */

    ply.utils.pluginBridge = function(name, object) {
        // return plugin constructor
        return function(options) {
            // Get args
            var args;

            if (typeof (options) === 'string') {
                args = Array.prototype.slice.call(arguments, 1);
                this.each(function() {
                    var instance = $.data(this, name);

                    if (!instance)
                        throw new Error('cannot call methods on ' + name + ' prior to initialization; attempted to call method "' + options + '"');
                    if (!$.isFunction(instance[options]) || options.charAt(0) === '_')
                        throw new Error('no such method "' + options + '" for ' + name + ' instance');

                    // apply method (see: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/function/apply)
                    instance[options].apply(instance, args);
                });
            } else {
                this.each(function() {
                    var instance = $.data(this, name);

                    if (instance) {
                        // Update option(s) and reinitialize
                        instance.option(options || {})._init();
                    } else {
                        $(this).addClass('ply-plugin');
                        object.prototype.option = object.prototype.option || function(key, value) {
                            var 
                            context = this,
                            options = key;

                            if (arguments.length === 0) {
                                // don't return a reference to the internal hash
                                return $.extend({}, this.options);
                            }

                            if (typeof (key) === 'string') {
                                if (value === undefined)
                                    return this.options[key] === undefined ? null : this.options[key];

                                options = {};
                                options[key] = value;
                            }

                            $.each(options, function(key, value) {
                                context.options[key] = value;
                            });

                            return this;
                        };

                        $.data(this, name, new object(this, options));
                    }
                });
            }
            // Maintain chain-ability
            return this;
        };
    };


    /*---------------------------------------------------------------------------
    Ply UI
    ---------------------------------------------------------------------------*/

    // Setup namespace
    setSafeDefault(ply, 'ui', {});

    /**
    * ply.ui.mediaGallery
    *
    * @param {jQuery} element REQUIRED - JQuery element(s)
    * @param {object} options REQUIRED - Options object
    */

    var mediaGallery = ply.ui.MediaGallery = function(element, options) {
        this.element = $(element);
        this._build(options);
        this._init();
    };

    //
    ply.ui.MediaGallery.type = new ply.utils.Enum(['image', 'video']);
    ply.ui.MediaGallery.position = new ply.utils.Enum(['top', 'right', 'bottom', 'left']);
    ply.ui.MediaGallery.revealAnimation = {
        fade: function(instance, mediaList, mediaItemWidth, currentIndex, index) {
            mediaList
                .find('.' + galleryCssClasses.mediaItem + '.' + galleryCssClasses.current)
                .animate({ opacity: 0 }, 200, 'swing', function() {
                    $(this).removeClass(galleryCssClasses.current).addClass(galleryCssClasses.invisible)
                        .closest('ul').css("left", -(mediaItemWidth * index))
                        .find('li:eq(' + index + ')')
                            .removeClass(galleryCssClasses.invisible)
                            .animate({ opacity: 1 }, 200, function() {
                                $(this).addClass(galleryCssClasses.current);
                            });
                });
        },
        scroll: function(instance, mediaList, mediaItemWidth, currentIndex, index) {
             mediaList
                .animate({ left: -(mediaItemWidth * index) }, 400, 'swing', function() {
                    $(this)
                        .find('li').removeClass(galleryCssClasses.current)
                        .eq(index).addClass(galleryCssClasses.current);
                });
        },
        immediateScroll: function(instance, mediaList, mediaItemWidth, currentIndex, index) {
            var 
            isBeforeCurrent = currentIndex > index,
            mediaItems = mediaList.find('.' + galleryCssClasses.mediaItem);
            
            if (isBeforeCurrent)
                mediaList.css('left', -mediaItemWidth);
                
            mediaItems.eq(index).show();
            mediaList
                .animate({ left: isBeforeCurrent ? '+=' + mediaItemWidth : '-=' + mediaItemWidth }, 400, 'swing', function() {
                    var left = parseInt(mediaList.css('left'));
                    
                    mediaList
                        .find('li').removeClass(galleryCssClasses.current)
                            .eq(index).addClass(galleryCssClasses.current).end()
                            .not('.' + galleryCssClasses.current).hide().end()
                        .end()
                        .css('left', 0);
                });
        }
    };

    // Gallery config
    ply.ui.MediaGallery._config = {
        cssClasses: {
            typeList: 'mediagallery-type-list',
            typeItemPrefix: 'mediagallery-type-',
            galleryContainer: 'mediagallery-container',
            mediaContainer: 'mediagallery-media-container',
            mediaViewport: 'mediagallery-media-viewport',
            mediaItem: 'mediagallery-media-item',
            mediaTitle: 'mediagallery-media-title',
            mediaElement: 'mediagallery-media-element',
            mediaDescription: 'mediagallery-media-description',
            thumbViewport: 'mediagallery-thumb-viewport',
            thumbItem: 'mediagallery-thumb-item',
            prevNextContainer: 'mediagallery-prev-next-container',
            prevLink: 'mediagallery-prev-link',
            nextLink: 'mediagallery-next-link',
            current: 'mediagallery-current',
            disabled: 'mediagallery-disabled',
            clearfix: 'clearfix',
            invisible: 'invisible'
        },
        // IMPORTANT:
        // these values need to match the data attributes in the raw HTML
        dataAttributes: {
            type: 'data-mediagallery-type',
            title: 'data-mediagallery-title',
            thumb: 'data-mediagallery-thumb',
            description: 'data-mediagallery-description'
        }
    };

    // Gallery default options
    ply.ui.MediaGallery._defaults = {
        alwaysShowMediaList: false,
        autoPlay: false,
        autoPlayInterval: 5000,
        autoPlayLoop: undefined,
        initVideoPlayer: $.noop,
        onPrevNextLinkDisabled: $.noop,
        onTypeLinkClick: function(e, instance, gallery) {
            gallery.siblings().hide().end().show();
        },
        revealType: 'fade',
        setVideoPlayerSize: $.noop,
        thumbListPosition: mediaGallery.position.bottom,
        useAnimatedRibbon: true,
        usePrevNextButtons: true
    };

    // Convenience
    var 
    galleryCssClasses = mediaGallery._config.cssClasses,
    galleryDataAttributes = mediaGallery._config.dataAttributes;

    // Gallery prototype
    ply.ui.MediaGallery.prototype = {

        // Build gallery
        _build: function(options) {
            var 
            context = this,
            rawElements, jsonData, galleryContainer, gallerySections, typeList;

            // don't continue if there are no media elements
            if (this.element.children().length < 1)
                return;

            // set instance options
            this.options = $.extend({}, mediaGallery._defaults, options);

            // set raw elements
            rawElements = $('<div/>').append(this.element.children());
            this.element.empty();

            //
            galleryContainer = $('<div/>').addClass(galleryCssClasses.galleryContainer);
            gallerySections = rawElements.children().filter(function() { return $(this).children().length > 0; });
            gallerySections.each(function(i) {
                var 
                rawElement = $(this),
                mediaItems = rawElement.children(),
                type = rawElement.attr(galleryDataAttributes.type),
                title = rawElement.attr(galleryDataAttributes.title),
                isFirst = i == 0;

                if (mediaItems.length < 1)
                    return;

                // build type item, but only if there is more than one section or the the plugin is
                // explicitly set always show a type list
                if (context.options.alwaysShowMediaList === true || gallerySections.length > 1) {
                    if (isFirst)
                        typeList = $('<ul/>').addClass(galleryCssClasses.typeList + ' ' + galleryCssClasses.clearfix);

                    typeList.append(context._buildTypeItem(type, title).toggleClass(galleryCssClasses.current, isFirst));
                }

                // build gallery
                galleryContainer.append(context._buildGallery(type, mediaItems).toggle(isFirst));
            });

            // add type navigation to containing element
            if (typeList !== undefined && typeList.children().length > 0)
                this.element.append(typeList);
            // add gallery to containing element
            if (galleryContainer.children().length > 0)
                this.element.append(galleryContainer);
        },

        // Build type navigation
        _buildTypeItem: function(type, title) {
            return $([
                '<li ', galleryDataAttributes.type, '=', type, ' class="', galleryCssClasses.typeItemPrefix, type, '">',
                    '<a href="#">', title, '</a>',
                '</li>'].join(''));
        },

        // Build gallery
        _buildGallery: function(type, rawElements) {
            var 
            context = this,
            viewportTemplate = $('<div><ul class="' + galleryCssClasses.clearfix + '"/></div>'),
            mediaContainer = $('<div/>').addClass(galleryCssClasses.mediaContainer),
            mediaViewport = viewportTemplate.clone().addClass(galleryCssClasses.mediaViewport),
            mediaList = mediaViewport.find('ul'),
            thumbViewport = viewportTemplate.clone().addClass(galleryCssClasses.thumbViewport),
            thumbList = thumbViewport.find('ul');

            mediaContainer
            // add type data attribute
                .attr(mediaGallery._config.dataAttributes.type, type)
            // add CSS clear fix if the layout requires it
                .toggleClass(galleryCssClasses.clearfix, $.inArray(context.options.thumbListPosition, [mediaGallery.position.right, mediaGallery.position.left]))

            rawElements.each(function(i) {
                var 
                rawElement = $(this),
                isFirst = i == 0;

                // add media item to list
                mediaList.append(context._buildMediaItem(rawElement, i, isFirst));
                // add thumb item to list
                thumbList.append(context._buildThumbItem(rawElement, i, isFirst));
            });

            // add viewport to media container
            mediaContainer.append(mediaViewport);

            // add thumb list to media container
            if ($.inArray(this.options.thumbListPosition, [mediaGallery.position.top, mediaGallery.position.left]) >= 0)
                mediaContainer.prepend(thumbViewport);
            else
                mediaContainer.append(thumbViewport);

            // add prev/next nav to media container
            if (this.options.usePrevNextButtons) {
                mediaContainer.append($([
                    '<div class="', galleryCssClasses.prevNextContainer, '">',
                        '<a href="#" class="', galleryCssClasses.prevLink, '">previous</a>',
                        '<a href="#" class="', galleryCssClasses.nextLink, '">next</a>',
                    '</div>'
                ].join('')));
            }

            //
            return mediaContainer;
        },

        // Build media item
        _buildMediaItem: function(rawElement, index, isFirst) {
            var 
            mediaItem = $([
                '<li class="', galleryCssClasses.mediaItem, '">',
                    '<div class="', galleryCssClasses.mediaElement, '"/>',
                '</li>'].join('')),
            title = rawElement.attr(galleryDataAttributes.title),
            description = rawElement.attr(galleryDataAttributes.description);

            if (title !== undefined && title.length > 0) {
                mediaItem.append([
                    '<div class="', galleryCssClasses.mediaTitle, '">',
                        title,
                    '</div>'].join(''));
            }

            mediaItem.find('.' + galleryCssClasses.mediaElement).append(rawElement.clone());

            if (description !== undefined && description.length > 0) {
                mediaItem.append([
                    '<div class="', galleryCssClasses.mediaDescription, '">',
                        description,
                    '</div>'].join(''));
            }

            return mediaItem.toggleClass(galleryCssClasses.current, isFirst);
        },

        // Build thumb item
        _buildThumbItem: function(rawElement, index, isFirst) {
            var 
            thumbItem = $('<li class="' + galleryCssClasses.thumbItem + '"><a href="#"/></li>'),
            thumbLink = thumbItem.find('a'),
            thumbImageSrc = rawElement.attr(galleryDataAttributes.thumb),
            thumbTitle = rawElement.attr(galleryDataAttributes.title);

            if (thumbImageSrc !== undefined && thumbImageSrc.length > 0) {
                thumbLink.empty().append(
                    $('<img/>').attr({
                        src: rawElement.attr(thumbImageSrc),
                        alt: rawElement.attr(thumbTitle)
                    }));
            } else
                thumbLink.text(index + 1);

            return thumbItem.toggleClass(galleryCssClasses.current, isFirst);
        },

        // Get current index
        _getCurrentIndex: function(listItems) {
            return listItems.index(listItems.filter('.' + mediaGallery._config.cssClasses.current));
        },

        // Set position
        _setPosition: function(list, index, centered) {
            var 
            viewportWidth = list.parent().width(),
            itemWidth = list.children('li:first').outerWidth(true),
            leftPosition = centered === true
                ? Math.floor((viewportWidth / 2) - itemWidth / 2) - (itemWidth * index)
                : -(viewportWidth * index);

            list.css({
                left: leftPosition,
                width: (itemWidth * list.children('li').length)
            });
        },

        // Set state of prev/next links
        _setPrevNextState: function(gallery) {
            var 
            context = this,
            thumbList = gallery.find('.' + galleryCssClasses.thumbViewport + ' ul'),
            currentIndex = this._getCurrentIndex(thumbList.find('li'));

            gallery
                .find('.' + galleryCssClasses.prevNextContainer).children()
                    .filter('.' + galleryCssClasses.prevLink)
                        .toggleClass(galleryCssClasses.disabled, currentIndex < 1)
                    .end()
                    .filter('.' + galleryCssClasses.nextLink)
                        .toggleClass(galleryCssClasses.disabled, currentIndex >= (thumbList.find('li').length - 1))
                    .end()
                    .each(function() {
                        var link = $(this);
                        if (link.hasClass(galleryCssClasses.disabled))
                            context.options.onPrevNextLinkDisabled(link);
                    });
        },

        // Bind events
        _bindEvents: function() {
            var 
            context = this,
            typeList = this.element.find('.' + galleryCssClasses.typeList + ' li');

            // type link click event
            if (typeList.length > 0) {
                typeList.delegate('a', 'click', function(e) {
                    var 
                    link = $(this),
                    listItem = link.closest('li'),
                    currentClass = galleryCssClasses.current,
                    typeAttribute = galleryDataAttributes.type,
                    gallery = context.element.find('.' + mediaGallery._config.cssClasses.mediaContainer + '[' + typeAttribute + '=' + listItem.attr(typeAttribute) + ']');

                    e.preventDefault();

                    if (listItem.hasClass(currentClass) || typeof (context.options.onTypeLinkClick) != 'function') {
                        e.stopPropagation();
                        return;
                    }

                    listItem
                        .siblings().removeClass(currentClass).end()
                        .addClass(currentClass).trigger('blur');

                    context.options.onTypeLinkClick(e, context, gallery);
                });
            }
            //
            this.element.find('.' + galleryCssClasses.galleryContainer)
            // thumb link click event
                .find('.' + galleryCssClasses.thumbViewport + ' ul')
                    .find('li[' + galleryDataAttributes.type + '=video] a')
                        .bind('click', function() {
                            // init video player(s)
                            context.element.find('video').each(function() {
                                var video = $(this);
                                if (video.parent().hasClass(galleryCssClasses.mediaElement))
                                    context.options.initVideoPlayer(video);
                            });
                        })
                    .end()
                    .delegate('a', 'click', function(e) {
                        var 
                        link = $(this),
                        listItem = link.closest('li'),
                        list = link.closest('ul'),
                        gallery = link.closest('.' + galleryCssClasses.mediaContainer);

                        e.preventDefault();

                        context.autoPlayTimeout = undefined;

                        if (listItem.is('.' + galleryCssClasses.current)) {
                            e.stopPropagation();
                            return;
                        }

                        context.showMedia(gallery, list.find('li').index(listItem));
                    });

            // prev/next link click event
            if (this.options.usePrevNextButtons) {
                this.element.find('.' + galleryCssClasses.prevNextContainer)
                    .delegate('a', 'click', function(e) {
                        var 
                        link = $(this),
                        gallery = link.closest('.' + galleryCssClasses.mediaContainer),
                        list = gallery.find('.' + galleryCssClasses.thumbViewport + ' ul'),
                        index = context._getCurrentIndex(list.find('li'));

                        e.preventDefault();

                        if (link.is('.' + galleryCssClasses.disabled)) {
                            e.stopPropagation();
                            return;
                        }

                        if (link.hasClass(galleryCssClasses.prevLink))
                            index--;
                        if (link.hasClass(galleryCssClasses.nextLink))
                            index++;

                        context.showMedia(gallery, index);
                    });
            }
        },

        // Initialize gallery
        _init: function() {
            var 
            context = this,
            mediaContainers = this.element.find('.' + galleryCssClasses.mediaContainer),
            videos = this.element.find('video');

            // bind DOM events
            this._bindEvents();

            //
            mediaContainers.each(function(i) {
                var 
                container = $(this),
                mediaList = container.find('.' + galleryCssClasses.mediaViewport + ' ul'),
                mediaItems = mediaList.find('li'),
                thumbList = container.find('.' + galleryCssClasses.thumbViewport + ' ul'),
                thumbItems = thumbList.find('li');

                // set state of prev/next links
                if (context.options.usePrevNextButtons)
                    context._setPrevNextState(container);

                // set position of media list
                if (mediaItems.length > 0)
                    context._setPosition(mediaList, 0)

                // set position of thumb list
                if (context.options.useAnimatedRibbon && thumbItems.length > 0)
                    context._setPosition(thumbList, 0, true)
            });
            // init videos
            if (videos.length > 0) {
                videos.each(function() {
                    var video = $(this);
                    context.options.initVideoPlayer(video);
                });
            }
            // ta-da
            this.element.removeClass('invisible');

            if (this.options.autoPlay) {
                this.autoPlayLoopCount = 0;
                this._autoPlay(mediaContainers.filter(':visible').first());
            }
        },

        _autoPlay: function(gallery) {
            var context = this;

            this.autoPlayTimeout = setTimeout(function() {
                var 
                listItems = gallery.find('.' + galleryCssClasses.thumbViewport + ' ul > li'),
                currentIndex = context._getCurrentIndex(listItems),
                targetIndex = (currentIndex + 1) % (listItems.length);
                                
                if (context.options.autoPlayLoop !== undefined) {
                    if (currentIndex > 0 && targetIndex == 0)
                        context.autoPlayLoopCount++;
                        
                    if (context.autoPlayLoopCount > context.options.autoPlayLoop)
                        return;
                }

                if (context.autoPlayTimeout !== undefined) {
                    context.showMedia(gallery, targetIndex);
                    context._autoPlay(gallery);
                }

            }, this.options.autoPlayInterval);
        },

        /**
        * Show media
        *
        * @param {jQuery} gallery REQUIRED - Gallery jQuery object
        * @param {number} index   REQUIRED - index of media item in list
        */
        showMedia: function(gallery, index) {
            var 
            mediaList = gallery.find('.' + galleryCssClasses.mediaViewport + ' ul'),
            mediaItemWidth = mediaList.children('li:first').outerWidth(true),
            thumbList = gallery.find('.' + galleryCssClasses.thumbViewport + ' ul'),
            thumbViewPortWidth = thumbList.parent().outerWidth(false),
            thumbItemWidth = thumbList.children('li:first').outerWidth(true),
            currentIndex = this._getCurrentIndex(thumbList.find('li')),
            thumbsToTraverse = currentIndex > index ? currentIndex - index : index - currentIndex;

            // revealMedia
            if (typeof(ply.ui.MediaGallery.revealAnimation[this.options.revealType]) == 'function') {
                ply.ui.MediaGallery.revealAnimation[this.options.revealType](this, mediaList, mediaItemWidth, currentIndex, index);

                if (this.options.useAnimatedRibbon)
                    thumbList.animate({ left: Math.floor((thumbViewPortWidth / 2) - thumbItemWidth / 2) - (thumbItemWidth * index) }, 100 * thumbsToTraverse, 'swing');

                thumbList
                    .children('li')
                        .removeClass(galleryCssClasses.current)
                        .eq(index)
                            .addClass(galleryCssClasses.current);

                // set state of prev/next links
                if (this.options.usePrevNextButtons)
                    this._setPrevNextState(gallery);
            }
        },

        /**
        * Destroy instance
        */
        destroy: function() {
            // TODO: destroy MediaGallery instance
        }
    };

    // Create plugin
    $.fn.plyMediaGallery = ply.utils.pluginBridge('plyMediaGallery', ply.ui.MediaGallery);

})(window, jQuery, jQuery(document));

