(function (factory) {
    if(typeof module === "object" && typeof module.exports === "object") {
        module.exports = factory(require("jquery"), window, document);
    } else {
        factory(jQuery, window, document);
    }
}(function($, window, document, undefined) {
    var calls = 0;

    function Chocolat(element, settings) {
        var that = this;

        this.settings  = settings;
        this.elems     = {};
        this.element   = element;

        this._cssClasses = [
            'chocolat-open',
            'chocolat-in-container',
            'chocolat-cover',
            'chocolat-zoomable',
            'chocolat-zoomed'
        ];

        if (!this.settings.setTitle && element.data('chocolat-title')) {
            this.settings.setTitle = element.data('chocolat-title');
        }

        this.element.find(this.settings.imageSelector).each(function () {
            that.settings.images.push({
                title  : $(this).attr('title'),
                src    : $(this).attr(that.settings.imageSource),
                height : false,
                width  : false
            });
        });

        this.element.find(this.settings.imageSelector).each(function (i) {
            $(this).off('click.chocolat').on('click.chocolat', function(e){
                that.init(i);
                e.preventDefault();
            });
        });

        return this;
    }

    $.extend(Chocolat.prototype, {

        init : function(i) {
            if (!this.settings.initialized) {
                this.setDomContainer();
                this.markup();
                this.events();
                this.settings.lastImage   = this.settings.images.length - 1;
                this.settings.initialized = true;
            }

            this.settings.afterInitialize.call(this);

            return this.load(i);
        },

        preload : function(i) {
            var def = $.Deferred();

            if (typeof this.settings.images[i] === 'undefined') {
                return;
            }
            var imgLoader    = new Image();
            imgLoader.onload = function() { def.resolve(imgLoader); };
            imgLoader.src    = this.settings.images[i].src;

            return def;
        },

        load : function(i) {
            var that = this;
            if (this.settings.fullScreen) {
                this.openFullScreen();
            }

            if (this.settings.currentImage === i) {
                return;
            }

            this.elems.overlay.fadeIn(this.settings.duration);
            this.elems.wrapper.fadeIn(this.settings.duration);
            this.elems.domContainer.addClass('chocolat-open');

            this.settings.timer = setTimeout(function(){
                if (typeof that.elems != 'undefined') {
                    $.proxy(that.elems.loader.fadeIn(), that);
                }
            }, this.settings.duration);

            var deferred = this.preload(i)
                .then(function (imgLoader) {
                    return that.place(i, imgLoader);
                })
                .then(function (imgLoader) {
                    return that.appear(i);
                })
                .then(function (imgLoader) {
                    that.zoomable();
                    that.settings.afterImageLoad.call(that);
                });

            var nextIndex = i + 1;
            if (typeof this.settings.images[nextIndex] != 'undefined') {
                this.preload(nextIndex);
            }

            return deferred;
        },

        place : function(i, imgLoader) {
            var that = this;
            var fitting;

            this.settings.currentImage = i;
            this.description();
            this.pagination();
            this.arrows();

            this.storeImgSize(imgLoader, i);
            fitting = this.fit(i, that.elems.wrapper);

            return this.center(
                fitting.width,
                fitting.height,
                fitting.left,
                fitting.top,
                0
            );
        },

        center : function(width, height, left, top, duration) {

            return this.elems.content
                .css('overflow', 'visible')
                .animate({
                    'width'  :width,
                    'height' :height,
                    'left'   :left,
                    'top'    :top
                }, duration)
                .promise();
        },

        appear : function(i) {
            var that = this;
            clearTimeout(this.settings.timer);

            this.elems.loader.stop().fadeOut(300, function() {
                that.elems.img
                    .attr('src', that.settings.images[i].src);
            });
        },

        fit : function(i, container) {
            var height;
            var width;

            var imgHeight        = this.settings.images[i].height;
            var imgWidth         = this.settings.images[i].width;
            var holderHeight     = $(container).height();
            var holderWidth      = $(container).width();
            var holderOutMarginH = this.getOutMarginH();
            var holderOutMarginW = this.getOutMarginW();

            var holderGlobalWidth  = holderWidth-holderOutMarginW;
            var holderGlobalHeight = holderHeight-holderOutMarginH;
            var holderGlobalRatio  = (holderGlobalHeight / holderGlobalWidth);
            var holderRatio        = (holderHeight / holderWidth);
            var imgRatio           = (imgHeight / imgWidth);

            if (this.settings.imageSize == 'cover') {
                if (imgRatio < holderRatio) {
                    height = holderHeight;
                    width = height / imgRatio;
                }
                else {
                    width = holderWidth;
                    height = width * imgRatio;
                }
            }
            else if (this.settings.imageSize == 'native') {
                height = imgHeight;
                width = imgWidth;
            }
            else {
                if (imgRatio > holderGlobalRatio) {
                    height = holderGlobalHeight;
                    width = height / imgRatio;
                }
                else {
                    width = holderGlobalWidth;
                    height = width * imgRatio;
                }
                if (this.settings.imageSize === 'default' && (width >= imgWidth || height >= imgHeight)) {
                    width = imgWidth;
                    height = imgHeight;
                }
            }

            return {
                'height' : height,
                'width'  : width,
                'top'    : (holderHeight - height)/2,
                'left'   : (holderWidth - width)/2
            };
        },

        change : function(signe) {
            this.zoomOut(0);
            this.zoomable();

            var requestedImage = this.settings.currentImage + parseInt(signe);
            if (requestedImage > this.settings.lastImage) {
                if (this.settings.loop) {
                    return this.load(0);
                }
            }
            else if (requestedImage < 0) {
                if (this.settings.loop) {
                    return this.load(this.settings.lastImage);
                }
            }
            else {
                return this.load(requestedImage);
            }
        },

        arrows: function() {
            if (this.settings.loop) {
                $([this.elems.left[0],this.elems.right[0]])
                    .addClass('active');
            }
            else if (this.settings.linkImages) {
                // right
                if (this.settings.currentImage == this.settings.lastImage) {
                    this.elems.right.removeClass('active');
                }
                else {
                    this.elems.right.addClass('active');
                }
                // left
                if (this.settings.currentImage === 0) {
                    this.elems.left.removeClass('active');
                }
                else {
                    this.elems.left.addClass('active');
                }
            }
            else {
                $([this.elems.left[0],this.elems.right[0]])
                    .removeClass('active');
            }
        },

        description : function() {
            var that = this;
            this.elems.description
                .html(that.settings.images[that.settings.currentImage].title);
        },

        pagination : function() {
            var that      = this;
            var last      = this.settings.lastImage + 1;
            var position  = this.settings.currentImage + 1;

            this.elems.pagination
                .html(position + ' ' + that.settings.separator2 + last);
        },

        storeImgSize : function(img, i) {
            if (typeof img === 'undefined') {
                return;
            }
            if (!this.settings.images[i].height || !this.settings.images[i].width) {
                this.settings.images[i].height = img.height;
                this.settings.images[i].width  = img.width;
            }
        },

        close : function() {

            if (this.settings.fullscreenOpen) {
                this.exitFullScreen();
                return;
            }

            var els = [
                this.elems.overlay[0],
                this.elems.loader[0],
                this.elems.wrapper[0]
            ];
            var that = this;
            var def = $.when($(els).fadeOut(200)).done(function () {
                that.elems.domContainer.removeClass('chocolat-open');
            });
            this.settings.currentImage = false;

            return def;
        },

        destroy : function() {
            this.element.removeData();
            this.element.find(this.settings.imageSelector).off('click.chocolat');

            if (!this.settings.initialized) {
                return;
            }
            if (this.settings.fullscreenOpen) {
                this.exitFullScreen();
            }
            this.settings.currentImage = false;
            this.settings.initialized = false;
            this.elems.domContainer.removeClass(this._cssClasses.join(' '));
            this.elems.wrapper.remove();
        },

        getOutMarginW : function() {
            var left  = this.elems.left.outerWidth(true);
            var right = this.elems.right.outerWidth(true);
            return left + right;
        },

        getOutMarginH : function() {
            return this.elems.top.outerHeight(true) + this.elems.bottom.outerHeight(true);
        },

        markup : function() {
            this.elems.domContainer.addClass('chocolat-open ' + this.settings.className);
            if (this.settings.imageSize == 'cover') {
                this.elems.domContainer.addClass('chocolat-cover');
            }
            if (this.settings.container !== window) {
                this.elems.domContainer.addClass('chocolat-in-container');
            }

            this.elems.wrapper = $('<div/>', {
                'class' : 'chocolat-wrapper',
                'id' : 'chocolat-content-' + this.settings.setIndex
            }).appendTo(this.elems.domContainer);

            this.elems.overlay = $('<div/>', {
                'class' : 'chocolat-overlay'
            }).appendTo(this.elems.wrapper);

            this.elems.loader = $('<div/>', {
                'class' : 'chocolat-loader'
            }).appendTo(this.elems.wrapper);

            this.elems.content = $('<div/>', {
                'class' : 'chocolat-content',
            }).appendTo(this.elems.wrapper);

            this.elems.img = $('<img/>', {
                'class' : 'chocolat-img',
                'src' : ''
            }).appendTo(this.elems.content);

            this.elems.top = $('<div/>', {
                'class' : 'chocolat-top'
            }).appendTo(this.elems.wrapper);

            this.elems.left = $('<div/>', {
                'class' : 'chocolat-left'
            }).appendTo(this.elems.wrapper);

            this.elems.right = $('<div/>', {
                'class' : 'chocolat-right'
            }).appendTo(this.elems.wrapper);

            this.elems.bottom = $('<div/>', {
                'class' : 'chocolat-bottom'
            }).appendTo(this.elems.wrapper);

            this.elems.close = $('<span/>', {
                'class' : 'chocolat-close'
            }).appendTo(this.elems.top);

            this.elems.fullscreen = $('<span/>', {
                'class' : 'chocolat-fullscreen'
            }).appendTo(this.elems.bottom);

            this.elems.description = $('<span/>', {
                'class' : 'chocolat-description'
            }).appendTo(this.elems.bottom);

            this.elems.pagination = $('<span/>', {
                'class' : 'chocolat-pagination'
            }).appendTo(this.elems.bottom);

            this.elems.setTitle = $('<span/>', {
                'class' : 'chocolat-set-title',
                'html' : this.settings.setTitle
            }).appendTo(this.elems.bottom);

            this.settings.afterMarkup.call(this);
        },

        openFullScreen : function() {
            var wrapper = this.elems.wrapper[0];

            if (wrapper.requestFullscreen) {
                this.settings.fullscreenOpen = true;
                wrapper.requestFullscreen();
            }
            else if (wrapper.mozRequestFullScreen) {
                this.settings.fullscreenOpen = true;
                wrapper.mozRequestFullScreen();
            }
            else if (wrapper.webkitRequestFullscreen) {
                this.settings.fullscreenOpen = true;
                wrapper.webkitRequestFullscreen();
            }
            else if (wrapper.msRequestFullscreen) {
                wrapper.msRequestFullscreen();
                this.settings.fullscreenOpen = true;
            }
            else {
                this.settings.fullscreenOpen = false;
            }
        },

        exitFullScreen : function() {
            if (document.exitFullscreen) {
                document.exitFullscreen();
                this.settings.fullscreenOpen = false;
            }
            else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
                this.settings.fullscreenOpen = false;
            }
            else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
                this.settings.fullscreenOpen = false;
            }
            else if (document.msExitFullscreen) {
                document.msExitFullscreen();
                this.settings.fullscreenOpen = false;
            }
            else {
                this.settings.fullscreenOpen = true;
            }
        },

        events : function() {
            var that = this;

            $(document).off('keydown.chocolat').on('keydown.chocolat', function(e) {
                if (that.settings.initialized) {
                    if (e.keyCode == 37) {
                        that.change(-1);
                    }
                    else if (e.keyCode == 39) {
                        that.change(1);
                    }
                    else if (e.keyCode == 27) {
                        that.close();
                    }
                }
            });
            // this.elems.wrapper.find('.chocolat-img')
            //     .off('click.chocolat')
            //     .on('click.chocolat', function(e) {
            //         var currentImage = that.settings.images[that.settings.currentImage];
            //         if(currentImage.width > $(that.elems.wrapper).width() || currentImage.height > $(that.elems.wrapper).height() ){
            //             that.toggleZoom(e);
            //         }
            // });

            this.elems.wrapper.find('.chocolat-right')
                .off('click.chocolat')
                .on('click.chocolat', function() {
                    that.change(+1);
            });

            this.elems.wrapper.find('.chocolat-left')
                .off('click.chocolat')
                .on('click.chocolat', function() {
                    return that.change(-1);
            });

            $([this.elems.overlay[0], this.elems.close[0]])
                .off('click.chocolat')
                .on('click.chocolat', function() {
                    return that.close();
            });

            this.elems.fullscreen
                .off('click.chocolat')
                .on('click.chocolat', function() {
                    if (that.settings.fullscreenOpen) {
                        that.exitFullScreen();
                        return;
                    }

                    that.openFullScreen();
            });

            if (that.settings.backgroundClose) {
                this.elems.overlay
                    .off('click.chocolat')
                    .on('click.chocolat', function() {
                        return that.close();
                });
            }
            this.elems.wrapper
                .off('click.chocolat')
                .on('click.chocolat', function(e) {
                    return that.zoomOut(e);
            });

            this.elems.wrapper.find('.chocolat-img')
                .off('click.chocolat')
                .on('click.chocolat', function(e) {
                    if (that.settings.initialZoomState === null && that.elems.domContainer.hasClass('chocolat-zoomable')) {
                        e.stopPropagation();
                        return that.zoomIn(e);
                    }
            });

            this.elems.wrapper.mousemove(function( e ) {
                if (that.settings.initialZoomState === null) {
                    return;
                }
                if (that.elems.img.is(':animated')) {
                    return;
                }

                var pos = $(this).offset();
                var height = $(this).height();
                var width = $(this).width();

                var currentImage = that.settings.images[that.settings.currentImage];
                var imgWidth = currentImage.width;
                var imgHeight = currentImage.height;

                var coord = [e.pageX - width/2 - pos.left, e.pageY - height/2 - pos.top];

                var mvtX = 0;
                if (imgWidth > width) {
                    var paddingX = that.settings.zoomedPaddingX(imgWidth, width);
                    mvtX = coord[0] / (width / 2);
                    mvtX = ((imgWidth - width)/ 2  + paddingX) * mvtX;
                }

                var mvtY = 0;
                if (imgHeight > height) {
                    var paddingY = that.settings.zoomedPaddingY(imgHeight, height);
                    mvtY = coord[1] / (height / 2);
                    mvtY = ((imgHeight - height) / 2  + paddingY) * mvtY;
                }

                var animation = {
                    'margin-left': - mvtX + 'px',
                    'margin-top': - mvtY + 'px'
                };
                if (typeof e.duration !== 'undefined') {
                    $(that.elems.img).stop(false, true).animate(animation, e.duration);
                }
                else {
                    $(that.elems.img).stop(false, true).css(animation);
                }

            });
            $(window).on('resize', function() {
                if (!that.settings.initialized || that.settings.currentImage === false)  {
                    return;
                }
                that.debounce(50, function() {
                    var fitting = that.fit(that.settings.currentImage, that.elems.wrapper);
                    that.center(fitting.width, fitting.height, fitting.left, fitting.top, 0);
                    that.zoomable();
                });
            });
        },

        zoomable : function () {
            var currentImage = this.settings.images[this.settings.currentImage];
            var wrapperWidth = this.elems.wrapper.width();
            var wrapperHeight = this.elems.wrapper.height();

            var isImageZoomable = ( this.settings.enableZoom && ( currentImage.width > wrapperWidth || currentImage.height > wrapperHeight)) ? true : false;
            var isImageStretched = this.elems.img.width() > currentImage.width || this.elems.img.height() > currentImage.height;


            if (isImageZoomable && !isImageStretched) {
                this.elems.domContainer.addClass('chocolat-zoomable');
            }
            else {
                this.elems.domContainer.removeClass('chocolat-zoomable');
            }
        },

        zoomIn : function (e) {
            this.settings.initialZoomState = this.settings.imageSize;
            this.settings.imageSize = 'native';

            var event = $.Event('mousemove');
            event.pageX = e.pageX;
            event.pageY = e.pageY;
            event.duration = this.settings.duration;
            this.elems.wrapper.trigger(event);

            this.elems.domContainer.addClass('chocolat-zoomed');
            var fitting = this.fit(this.settings.currentImage, this.elems.wrapper);
            return this.center(fitting.width, fitting.height, fitting.left, fitting.top, this.settings.duration);
        },

        zoomOut : function (e, duration) {
            if (this.settings.initialZoomState === null || this.settings.currentImage === false) {
                return;
            }
            duration = duration || this.settings.duration;

            this.settings.imageSize = this.settings.initialZoomState;
            this.settings.initialZoomState = null;
            this.elems.img.animate({'margin': 0}, duration);

            this.elems.domContainer.removeClass('chocolat-zoomed');
            var fitting = this.fit(this.settings.currentImage, this.elems.wrapper);
            return this.center(fitting.width, fitting.height, fitting.left, fitting.top, duration);
        },

        setDomContainer : function() {
            // if container == window
            // domContainer = body
            if ( this.settings.container === window) {
                this.elems.domContainer = $('body');
            }
            else {
                this.elems.domContainer = $(this.settings.container);
            }
        },

        debounce: function(duration, callback) {
            clearTimeout(this.settings.timerDebounce);
            this.settings.timerDebounce = setTimeout(function() {
                callback();
            }, duration);
        },

        api: function() {
            var that = this;
            return {
                open : function(i){
                    i = parseInt(i) || 0;
                    return that.init(i);
                },

                close : function(){
                    return that.close();
                },

                next : function(){
                    return that.change(1);
                },

                prev : function(){
                    return that.change(-1);
                },

                goto : function(i){ // open alias
                    return that.open(i);
                },
                current : function(){
                    return that.settings.currentImage;
                },

                place : function(){
                    return that.place(that.settings.currentImage, that.settings.duration);
                },

                destroy : function(){
                    return that.destroy();
                },

                set : function(property, value){
                    that.settings[property] = value;
                    return value;
                },

                get : function(property){
                    return that.settings[property];
                },

                getElem : function(name){
                    return that.elems[name];
                },
            };
        }
    });

    var defaults = {
        container         : window, // window or jquery object or jquery selector, or element
        imageSelector     : '.chocolat-image',
        className         : '',
        imageSize         : 'default', // 'default', 'contain', 'cover' or 'native'
        initialZoomState  : null,
        fullScreen        : false,
        loop              : false,
        linkImages        : true,
        duration          : 300,
        setTitle          : '',
        separator2        : '/',
        setIndex          : 0,
        firstImage        : 0,
        lastImage         : false,
        currentImage      : false,
        initialized       : false,
        timer             : false,
        timerDebounce     : false,
        images            : [],
        enableZoom        : true,
        imageSource       : "href",
        afterInitialize   : function () {},
        afterMarkup       : function () {},
        afterImageLoad    : function () {},
        zoomedPaddingX    : function (canvasWidth, imgWidth) { return 0; },
        zoomedPaddingY    : function (canvasHeight, imgHeight) { return 0; },
    };

    $.fn.Chocolat = function (options) {
        return this.each(function() {

            calls++;
            var settings = $.extend(true, {}, defaults, options, {setIndex:calls} );

            if (!$.data(this, 'chocolat')) {
                $.data(this, 'chocolat',
                    new Chocolat($(this), settings)
                );
            }
        });
    };
}));
