/*! Gallery Viewer - v0.1.0 - 2012-10-22
* https://github.com/bgcom/gallery-viewer
* Copyright (c) 2012 BGcom; Licensed MIT */

;(function ( $, Handlebars, History, window, document, undefined ) {
    var _defaultTemplate = "";
    var that = {};
    $.widget( "bgcom.galleryViewer" , {
        version: "1.0",
        data: {},
        _handlers: {},
        nbElem: 0,
        _indexes: [],
        _curIdx: NaN,
        options: {
            template: _defaultTemplate,
            someValue: null,
            zoomFactor: 1.2,
            selectors: {
                'thumbList': ".galleryViewer-ThumbnailList", // Where are the thumbnails
                'thumbElement': ".galleryViewer-ThumbnailContainer", // What contains the thumbnail
                'thumbLink': ".galleryViewer-ThumbnailLink", // Where is the data and what will make the slideshow open
                button: {
                    next: ".galleryViewer-next",
                    previous: ".galleryViewer-previous",
                    zoomIn: ".galleryViewer-zoomIn",
                    zoomOut: ".galleryViewer-zoomOut",
                    close: ".galleryViewer-close"
                },
                'main': ".galleryViewer-main",
                'mainContainer': ".galleryViewer-main-container",
                'photoContainer': ".galleryViewer-photo-container",
                'sideContainer': ".galleryViewer-side-container" // the sidebar if you have one
            }
        },

        /** Change the image currently displayed according to the data attached to the event
         * triggers imageChanged
         * data.type === 'next' we go to the next image
         * data.type === 'previous' we go to the previous image
         * data.type === 'none' we clicked on an image
         * The image go in circle (If we arrive to the last one we go to the first one)
         */
        changeImage: function(e) {
            var idx = 0;
            switch (e.data.type) {
                case 'previous':
                    idx = (that._curIdx === 0) ?
                           that._indexes.length - 1 :
                           that._curIdx - 1;
                    break;
                case 'next':
                    idx = (that._curIdx === that._indexes.length - 1) ?
                            0 : that._curIdx + 1;
                    break;
                case 'none':
                    idx = that.data[$(e.currentTarget).attr('key')].index - 1;
                    break;
            }
            // We change the current link that will trigger the rerender of the slideshow
            // The random number is to ensure that the object is unique
            var hash = that._indexes[idx];
            History.pushState({"state": hash, "rand": Math.random()},
                              that._getTitle(hash), hash);
            that._trigger('imageChanged', e);
        },

        /**
         * Close the window and triggers viewerClosed
         */
        close : function(e) {
            // We go to the root url
            that._goRootUrl();

            //We adapt the style concequently
            that.element.html("");
            $('html').css("overflow", "auto");
            $('body').off("keydown.galleryViewer", that._handler.keyboard);
            that._trigger('viewerClosed', e);
        },

        /**
         * Zoom on the image according to the option.zoomFactor and the event data
         * triggers zoom
         * data.type === 'in' zoom in
         * data.type === 'out' zoom out
         */
        zoom: function (e) {
            var $img = $("img", that.options.selectors.photoContainer);
            var width = $img.width();
            var height = $img.height();
            var factor = 0;
            if (e.data.type === 'in') {
                factor = that.options.zoomFactor;
            } else if (e.data.type === 'out') {
                factor = 1.0 / that.options.zoomFactor;
            }

            $img.width(width * factor);
            $img.height(height * factor);
        },

        /**
         * Destroy an instantiated plugin and clean up
         * modifications the widget has made to the DOM
         */
        destroy: function () {
            that.close();

            that._unbindAll();

            $.Widget.prototype.destroy.call(this);
            // For UI 1.9, define _destroy instead and don't
            // worry about
            // calling the base widget
        },

        /**
         * Unbinds all events so that the viewer will never open
         */
        suspend: function () {
            that._unbindAll();
        },

        /**
         * Binds the event and reread the current DOM to get new elements or a change in the order
         */
        restart: function () {
            that.data = {};
            that.nbElem = 0;
            that._indexes = [];
            that._curIdx = NaN;
            $(that.options.selectors.thumbLink, that.options.selectors.thumbList)
                .each(that._appendElement);
            // We bind every thumbnail to opening the viewer
            $(that.options.selectors.thumbLink, that.options.selectors.thumbList)
                .on("click.galleryViewer",
                      {type: 'none'}, that._handler.openViewer);
        },

        /**
         * Initialize the widget and bind all the events
         */
        _create: function () {
            that = this;
            // Create all the event handlers and store them in an object to be able to unbind easily
            that._initWidget();
            that._bindAll();
        },

        /**
         * We bind all events
         */
        _bindAll: function () {
            // We bind History.js
            // Everytime the url changes we change image
            History.Adapter.bind(window, 'statechange', function() {
                that._changeImageFromUrl();
            });
            // We bind every thumbnail to opening the viewer
            $(that.options.selectors.thumbLink, that.options.selectors.thumbList)
                .on("click.galleryViewer",
                      {type: 'none'}, that._handler.openViewer);
        },

        _unbindAll: function () {
            $(that.options.selectors.thumbLink, that.options.selectors.thumbList)
                .off("click.galleryViewer");
        },

        /**
         * Compiles the Template and extracts all the data from the thumbnails
         * NOTICE: _init is used by jQuery-ui widget factory and I'm not sur that this could go in it
         */
        _initWidget: function () {
            that.template = Handlebars.compile(that.options.template);
            // Extract the data from the thumbnails
            $(that.options.selectors.thumbLink, that.options.selectors.thumbList)
                .each(that._appendElement);
            // In case there's already an anchor for an image when loading the page
            that._changeImageFromUrl();
        },

        _appendElement: function (idx, elt) {
            var key = $(elt).attr('key');
            var data = $.parseJSON($(elt).attr('data'));
            that._indexes[idx] = key;

            that.data[key] = data;
            // Description can be rich text so we don't escape
            that.data[key].description =
                new Handlebars.SafeString(data.description);
            // If we want to display number from 1 rather than from 0
            that.data[key].index = idx + 1;
            //This preloads the images and will be reused for resizing
            that.data[key].imageObj = new Image();
            that.data[key].imageObj.src = that.data[key].src;
        },

        /**
         * If a hash is defined in the url we show the right Image
         * TODO: deal with the loading to make it nicer
         */
        _changeImageFromUrl: function() {
            var path = window.location.pathname.split('/');
            var data = path[path.length - 1];
            if (typeof(that.data[data]) !== "undefined") {
                that._displayImage(data);
                that.data[data].imageObj.onload = function(e) {
                    that._sizeImage();
                };
            } else if (typeof(that.data[data]) === "undefined" && data !== "") {
                that._goRootUrl();
            }
        },

        /**
         * Make us go to the root url (in case the image doesn't exist or we close the gallery)
         */
        _goRootUrl: function () {
            var locationTab = location.href.split('/');
            locationTab.pop();
            var root = locationTab.join('/');
            // We push in our history the root
            History.pushState({"state": root, "rand": Math.random()},
                              that._getTitle(), root + '/');
        },

        /**
         * Renders the box and binds all the events to it
         * @param idx string the index(key or hash) of the photo
         */
        _displayImage : function(idx) {
            that._curIdx = that.data[idx].index - 1;
            // We renew keyboard binding to be sure not to bind twice
            $('body').off("keydown.galleryViewer", that._handler.keyboard);
            $('body').on("keydown.galleryViewer", that._handler.keyboard);
            // Render the box
            that._render(idx);

            // Bond all the buttons
            $(that.options.selectors.button.next, that.element)
                .on("click.galleryViewer",
                      {type: 'next'}, that._handler.changeImage);
            $(that.options.selectors.button.previous, that.element)
                .on("click.galleryViewer",
                      {type: 'previous'}, that._handler.changeImage);
            $(that.options.selectors.button.close, that.element)
                .one("click.galleryViewer", that._handler.close);

            $(that.options.selectors.button.zoomIn, that.element)
                .on("click.galleryViewer", {type: "in"}, that._handler.zoom);
            $(that.options.selectors.button.zoomOut, that.element)
                .on("click.galleryViewer", {type: "out"}, that._handler.zoom);

            $(that.options.selectors.mainContainer).on("click.galleryViewer", function (e) {
                e.stopPropagation();
            });

            $(that.options.selectors.main).one("click.galleryViewer", that._handler.close);

            $(window).on("resize.galleryViewer", that._handler.resize);
            that.element.focus();
        },

        /**
         * Renders the box and trigger imageDisplayed
         */
        _render : function(id) {
            $('html').css("overflow-x", "hidden");
            $('html').css("overflow-y", "hidden");
            var element = that.data[id];
            // To get rich text description
            that.element.html(that.template({
                elem: element,
                totalElements: that._indexes.length
            }));
            $('img', that.element).draggable({});
            that._trigger('imageDisplayed');
            that._resize();
        },

        /**
         * Resize the image according to the window size
         */
        _resize: function () {
            var selectors = that.options.selectors;
            var $main = $(selectors.main);
            // Fix the size of the main box
            $main.height($(window).height());
            $main.width($(window).width());

            // Fix the size of the photo container according to what is left from the sideContainer
            $(selectors.photoContainer)
                    .width($main.width() - $(selectors.sideContainer).outerWidth(true));
            // This is because the image is the stick to the top left corner of our document and it height
            // is the size of the window so to be sure to make it everywhere we just stick it to the top
            window.scrollTo(0, 0);

            that._sizeImage();
        },

        /**
         * Sets the inside img element to the right size respecting proportions
         */
        _sizeImage: function () {
            var $container = $(that.options.selectors.photoContainer, that.element);
            var $img = $("img", $container);
            var curImg = that.data[that._indexes[that._curIdx]].imageObj;
            // We get the proportions of the image compared to its container
            var dif = { height: $container.height() / curImg.height,
                        width: $container.width() / curImg.width};
            // We obtain the scaling factor
            var factor;
            if (dif.width < 1 && dif.height < 1) {
                // both side overflow from the container
                factor = Math.min(dif.width, dif.height);
            } else if (dif.width > 1 && dif.height > 1) {
                // both side are too small for the container
                factor = Math.max(dif.width, dif.height);
            } else if (dif.width < 1) {
                // only the width is too big
                factor = dif.width;
            } else if (dif.height < 1) {
                // only the height is too big
                factor = dif.height;
            }
            // Set the final size of the image
            $img.width(curImg.width * factor);
            $img.height(curImg.height * factor);
        },

        /**
         * Creates the title for the page of the photo
         */
        _getTitle: function(idx) {
            var title = document.title.split('|');
            if (typeof(idx) === "undefined" && title.length > 3) {
                title.splice(0,1);
            } else if (typeof(that.data[idx]) !== "undefined") {
                title[0] = ' ' + title[0];
                if (title.length > 3) {
                    title.splice(0,1, that.data[idx].title + " ");
                } else {
                    title = [that.data[idx].title + " "].concat(title);
                }
            } else if (typeof(idx) !== "undefined" && typeof(that.data[idx]) === "undefined") {
                title.splice(0,1);
                title[0] = title[0].substr(1);
            }
            return title.join('|');
        },

        /**
         * Respond to any changes the user makes to the
         * DUMMY method for the moment
         */
        _setOption: function ( key, value ) {
            /*switch (key) {
                case "someValue":
                    //this.options.someValue = doSomethingWith( value );
                    break;
                default:
                    //this.options[ key ] = value;
                    break;
            }

            // For UI 1.8, _setOption must be manually invoked
            // from the base widget
            $.Widget.prototype._setOption.apply( this, arguments );
            // For UI 1.9 the _super method can be used instead
            // this._super( "_setOption", key, value );
            */
        },
        /**
         * All the handlers of our events
         */
        _handler: {
            openViewer: function(e) {
                e.preventDefault();
                that.changeImage(e);
                that._trigger('viewerOpened');
            },
            changeImage: function(e) {
                e.preventDefault();
                that.changeImage(e);
            },
            keyboard: function(e) {
                switch (e.keyCode ? e.keyCode : e.which) {
                    case 37: // left arrow
                        e.preventDefault();
                        e.data = {'type': 'previous'};
                        that.changeImage(e);
                        break;
                    case 39: // right arrow
                        e.preventDefault();
                        e.data = {'type' : 'next'};
                        that.changeImage(e);
                        break;
                    case 27: // left esc
                        e.preventDefault();
                        that.close(e);
                        break;
                    case 38: // up arrow
                        e.preventDefault();
                        e.data = { type: 'in'};
                        that.zoom(e);
                        break;
                    case 40: // down arrow
                        e.preventDefault();
                        e.data = { type: 'out'};
                        that.zoom(e);
                        break;
                }
            },
            zoom: function (e) {
                that.zoom(e);
            },
            resize: function(e) {
                that._resize();
            },
            close: function(e) {
                that.close(e);
            }
        }
    });

})( jQuery, Handlebars, History, window, document );