var JSGallery2 = new Class({
    Implements: Options,
    options: {
        'borderWidthDeselected': 0,	//the width of the border of an deselected image (should be the same as in css)
        'borderWidthSelected': 3,	//border width of the selected image
        'borderColor': '#83b36b',		//the color of the borders
        'loadingMask': '#000',		//color of the mask overlay during loading of the big images
        'loadingOpacity': 0.6,		//opacity of the border div during loading (including the mask)
        'loadingImage': 'loading.gif',		//you may specify a loading image which is displayed while the big images are being loaded
        'selectSpeed': 200,			//the duration of the select effect in ms (high values will make it look ugly)
        'fadeSpeed': 250,			//the duration of the image fading effect in ms
        'pageSpeed': 1000,			//the duration of the change page effect in ms
        'prevHandle': null,			//if you pass a previous page handle in here, it will be hidden if it's not needed
        'nextHandle': null,			//like above, but for next page
        'titleTarget': null,		//target HTML element where image texts are copied into
        'initialIndex': -1,			//which thumb to select after init. you could create deep links with it.
        'maxOpacity': 0.8			//maximum opacity before cursor reaches prev/next control, then it will be set to 1 instantly.
    },
    /**
     *	Constructor. Starts up the whole thing :-)
     *
     *	This script is free to use. It has been created by http://www.aplusmedia.de and
     *	can be downloaded on http://www.esteak.net.
     *	License: GNU GPL 2.0: http://creativecommons.org/licenses/GPL/2.0/
     *	Example on: http://blog.aplusmedia.de/moo-gallery2
     *	Known issues:
     *	- preloading does not care about initialIndex param
     *	- hovering to a control over the border of the big image will make the other one flickering
     *	- if you enter and leave the control area very quickly, the control flickers sometimes
     *	- does not work in IE6
     *
     * 	@param {Array} thumbs, An array of HTML elements
     *	@param {HTMLelement} bigImageContainer, the full size image
     *	@param {HTMLelement} pageContainer, If you have several pages, put them in this container
     *	@param {Object} options, You have to pass imagesPerPage if you have more than one!
     */
    initialize: function(thumbs, bigImageContainer, pageContainer, options) {
        this.currentPageNumber = 0;
        this.loadedImages = 0;
        this.blockKeys = false;
        this.imagesPerPage = pageContainer.getFirst().getChildren().length;
        this.setOptions(options);
        this.thumbs = thumbs;
        this.bigImage = bigImageContainer;
        this.pageContainer = pageContainer;
        this.convertThumbs();
        if(this.options.initialIndex != -1) {
            this.selectByIndex(this.options.initialIndex);
        } else {
            this.gotoPage(0);
        }
        this.createControls();
        if($defined(this.options.loadingImage)) {
            new Asset.image(this.options.loadingImage);
        }
        this.loadNextImage();
    },
    /**
     *	Creates the previous and next links over the big image.
     */
    createControls: function() {
        this.prevLink = new Element('a', {
            events: {
                'click': this.prevImage.bindWithEvent(this),
                'mouseleave': this.mouseLeaveHandler.bindWithEvent(this)
            },
            styles: {
                'width': '46px',
                'display': 'block',
                'position': 'absolute',
                'top': 0,
                'height' : '98%',
                'background': 'url(prev_image.png) no-repeat 0 50%',
                'outline': 'none'
            },
            'href': '#'
        }).injectInside(this.bigImage.getParent());
        this.prevLink.addEvent('mouseover', this.focusControl.bindWithEvent(this, this.prevLink));
        this.nextLink = this.prevLink.clone().injectAfter(this.prevLink).set({
            'events': {
                'click': this.nextImage.bindWithEvent(this),
                'mouseleave': this.mouseLeaveHandler.bindWithEvent(this)
            },
            'styles': {
                'right': 0,
                'background-image': 'url(next_image.png)'
            }
        });
        this.prevLink.setStyle('left', 0);
        this.nextLink.addEvent('mouseover', this.focusControl.bindWithEvent(this, this.nextLink));
        this.bigImage.addEvents({
            'mousemove': this.mouseOverHandler.bindWithEvent(this),
            'mouseleave': this.mouseLeaveHandler.bindWithEvent(this)
        });
        document.addEvent('keydown', this.keyboardHandler.bindWithEvent(this));
        this.mouseLeaveHandler();
    },
    /**
     * Focuses one control
     *
     * @param {Event} event
     * @param {HTMLElement} control
     */
    focusControl: function(event, control) {
        control.set('opacity', 1);
    },
    /**
     *	Handles mouse movement over the big image.
     * @param {Event} event
     */
    mouseOverHandler: function(event) {
        var currentIndex = this.thumbs.indexOf(this.selectedContainer);
        //this makes the control on the other side fade out in just the moment when you reach one
        var activeRange = this.bigImage.getSize().x;
        var op = 0;
        if(currentIndex < this.thumbs.length - 1) {
            op = this.options.maxOpacity - this.getDistanceToMouse(this.nextLink, event) / activeRange;
        }
        this.nextLink.set('opacity', op);
        op = 0;
        if(currentIndex > 0) {
            op = this.options.maxOpacity - this.getDistanceToMouse(this.prevLink, event) / activeRange;
        }
        this.prevLink.set('opacity', op);
    },
    /**
     * Hides the controls.
     */
    mouseLeaveHandler: function() {
        this.nextLink.set('opacity', 0);
        this.prevLink.set('opacity', 0);
    },
    /**
     * Handles keyboard interactions.
     * @param {Event} event
     */
    keyboardHandler: function(event){
        if(!this.blockKeys) {
            if(event.code >= 49 && event.code <= 57) {
                this.gotoPage(event.key - 1);
            } else if (event.key == "left") {
                this.prevImage(event);
            } else if (event.key == "right") {
                this.nextImage(event);
            }
        }
    },
    /**
     *	Returns the distance to the mouse from the middle of a given element.
     *	@param {HTMLelement} element
     *	@param {Event} event
     * 	@return integer
     */
    getDistanceToMouse: function(element, event) {
        var s = element.getSize();
        var xDiff = Math.abs(event.client.x - (element.getLeft() + s.x / 2));
        var yDiff = Math.abs(event.client.y - (element.getTop() + s.y / 2));
        return Math.sqrt( Math.pow(xDiff, 2) + Math.pow(yDiff, 2) );
    },
    /**
     * 	Adds the border to the thumbs and so on. (conversion of static thumbs)
     */
    convertThumbs: function() {
        this.thumbs.each(function(thumbContainer, count) {
            this.convertThumb(thumbContainer, count);
        }, this);
    },
    /**
     * Converts one single thumb.
     * @param {HTMLelement} thumbContainer
     * @param {Integer} count
     */
    convertThumb: function(thumbContainer, count) {
        if(!$defined(thumbContainer)) {
            return;
        }
        thumbContainer.addEvent('click', this.select.bind(this, thumbContainer)).setStyle('position', 'relative').set('counter', count);
        var bigImage = thumbContainer.getFirst().set('href', 'javascript: void(0);').get('rel');
        var border = new Element('div', {
            styles: {
                'border': this.options.borderWidthDeselected + 'px solid ' + this.options.borderColor,
                'width': thumbContainer.getSize().x,
                'height': thumbContainer.getSize().y,
                'position': 'absolute',
                'background-color': this.options.loadingMask,
                'top': 0,
                'left': 0
            },
            'rel': bigImage,
            'description': thumbContainer.getElements('img')[0].get('title')
        }).injectTop(thumbContainer).set('opacity', this.options.loadingOpacity);
        thumbContainer.getElements('img')[0].set('title', '');
    },
    /**
     * 	Removes key blocking.
     */
    unBlockKeys: function() {
        this.blockKeys = false;
    },
    /**
     *	Selects a certain image. (You have to pass the outer container of the image)
     *	@param {HTMLelement} container
     */
    select: function(container) {
        if(this.blockKeys || !$defined(container)) {
            return false;
        }
        this.blockKeys = true;
        if($defined(this.selectedContainer)) {
            //this prevents an ugly effect if you click on the currently selected item
            if(container == this.selectedContainer) {
                this.unBlockKeys();
                return false;
            }
            this.deselect(this.selectedContainer);
        }
        //if target image is not on current page, we have to go there first
        var targetPage = Math.floor(container.get('counter') / this.imagesPerPage);
        if(this.currentPageNumber != targetPage) {
            this.gotoPage(targetPage, container);
        }
        this.selectedContainer = container;
        //make calculations a bit more handy
        var s = container.getSize();
        var des = this.options.borderWidthDeselected;
        var sel = this.options.borderWidthSelected;
        new Fx.Morph(container.getFirst(), {
            duration: this.options.selectSpeed,
            transition: Fx.Transitions.Quad.easeOut
        }).start({
            'border-width': [des, sel + 'px'],
            'width': [s.x, s.x - 2 * (sel - des) ],
            'height': [s.y, s.y - 2 * (sel - des)]
        });
        var source = container.getFirst();
        this.setImage(source.get('rel'), source.get('description'));
    },
    /**
     * Preloads one big image
     */
    loadNextImage: function() {
        var thumbContainer = this.thumbs[this.loadedImages].getFirst();
        if($defined(this.options.loadingImage)) {
            new Element('img', {'src': this.options.loadingImage}).injectTop(thumbContainer);;
        }
        var imageToLoad = thumbContainer.get('rel');
        new Asset.image(imageToLoad, {
            onload: this.imageLoaded.bind(this, this.thumbs[this.loadedImages])
        });
    },
    /**
     * Callback after an image has been successfully preloaded.
     * Removes the loading effects from the border div.
     * @param {HTMLElement} thumbContainer the thumb wrapper div
     */
    imageLoaded: function(thumbContainer) {
        this.loadedImages++;
        if($defined(this.options.loadingImage)) {
            //remove loading gif
            thumbContainer.getFirst().getFirst().destroy();
        }
        //remove loading styles
        thumbContainer.getFirst().setStyle('background-color', 'transparent').setOpacity(1);
        if(this.loadedImages < this.thumbs.length) {
            this.loadNextImage();
        }
    },
    /**
     * Selects an image by its thumbnail index.
     * @param {integer} index of the thumbnail, starting with 0
     */
    selectByIndex: function(index) {
        this.mouseLeaveHandler();
        if(index < 0 || this.thumbs.length <= index) {
            index = 0;
        }
        this.select(this.thumbs[index]);
    },
    /**
     *	Opposite to method above.
     *	@param {HTMLelement} container
     */
    deselect: function(container) {
        new Fx.Morph(container.getFirst(), {duration: this.options.selectSpeed, transition: Fx.Transitions.Quad.easeOut}).start({
            'border-width': this.options.borderWidthDeselected + 'px',
            'width': container.getSize().x,
            'height': container.getSize().y
        });
    },
    /**
     *	Changes the full size image to given one.
     *	@param {String} newSrc, new target of the full size image
     *	@param {String} newText, new text for the info element
     */
    setImage: function(newSrc, newText) {
        var effect = new Fx.Tween(this.bigImage, {duration: this.options.fadeSpeed, transition: Fx.Transitions.Quad.easeOut});
        effect.start('opacity', 0).chain(function(){
            this.bigImage.set('src', newSrc);
            if($defined($(this.options.titleTarget))) {
                $(this.options.titleTarget).set('html', newText);
            }
            this.mouseLeaveHandler();
            effect.start('opacity', 1).chain(this.unBlockKeys.bind(this));
        }.bind(this));
    },
    /**
     *	Navigates to previous page.
     */
    prevPage: function() {
        this.gotoPage(this.currentPageNumber - 1);
    },
    /**
     *	Navigates to next page.
     */
    nextPage: function() {
        this.gotoPage(this.currentPageNumber + 1);
    },
    /**
     *	Selects the previous image.
     */
    prevImage: function(e) {
        e = new Event(e).stop();
        this.selectByIndex(this.thumbs.indexOf(this.selectedContainer) - 1);
    },
    /**
     *	Selects the next image.
     */
    nextImage: function(e) {
        e = new Event(e).stop();
        this.selectByIndex(this.thumbs.indexOf(this.selectedContainer) + 1);
    },
    /**
     *	Navigates to given page and selects the first image of it.
     *	Also hides the handles (if set).
     *	@param {Integer} pageNumber, index of the target page (0-n)
     *  @param {HTMLElement} selectImage, optionally receives a particular image to select
     */
    gotoPage: function(pageNumber, selectImage) {
        //if we like to select another image on that page than the first one
        selectImage = $pick(selectImage, this.thumbs[pageNumber * this.imagesPerPage]);
        var lastPage = Math.ceil(this.thumbs.length / this.imagesPerPage);
        if(pageNumber >= 0 && pageNumber < lastPage) {
            this.pageContainer.set('tween', {
                duration: this.options.pageSpeed,
                transition: Fx.Transitions.Quint.easeInOut
            });
            this.pageContainer.tween('margin-left', this.pageContainer.getFirst().getSize().x * pageNumber * -1);
            this.currentPageNumber = pageNumber;
            this.select(selectImage);

            //update handles
            if($defined(this.options.prevHandle)) {
                new Fx.Tween(this.options.prevHandle, {duration:this.options.fadeSpeed * 2}).start('opacity', pageNumber == 0 ? 0 : 1);
            }
            if($defined(this.options.nextHandle)) {
                new Fx.Tween(this.options.nextHandle, {duration:this.options.fadeSpeed * 2}).start('opacity', pageNumber == lastPage - 1 ? 0 : 1);
            }
        }
    }
});