function Slideshow(jqSelector, duration) {
    this.init(jqSelector, duration);
};

Slideshow.prototype = {

    init: function(jqSelector, duration) {
        this.duration = duration;
        //
        this.findSlides(jqSelector);
        this.prepareSlides();
        this.initStateVars();
        this.start();
    },

    findSlides: function(selector) {
        var elements = $(selector);
        this.slides = [];
        for(var i=0; i < elements.length; i++)
            this.slides.push($(elements[i]));
    },

    prepareSlides: function() {
        this.alignSlides();
        this.hideSlidesExceptFirstSlide();
    },

    alignSlides: function() {
        // Задаём нулевую высоту всем слайдам, тогда они наложатся друг на
        // друга. Последний слайд не трогаем -- ведь если мы зададим ему
        // нулевую высоту, то и у всего слайдшоу получится нулевая высота, и на
        // него налезут следующие за ним HTML элементы.
        for(var i=0; i < this.slidesCount() - 1; i++) {
            this.slides[i].css({
                height: 0,
                overflow: 'visible',
                display: 'block',
            });
        }
    },
    
    slidesCount: function() {
        return this.slides.length;
    },

    hideSlidesExceptFirstSlide: function() {
        for(var i=1; i < this.slidesCount(); i++) {
            this.slides[i].css({
                opacity: 0
            });
        }
    },

    initStateVars: function() {
        this.visibleSlideIndex = 0;
        this.lockFlag = false;
    },

    start: function() {
        this.timer = setInterval(function(me) {
            return function() {
                me.tic();
            };
        }(this), this.duration);
    },

    tic: function() {
        if(!this.isLocked())
            this.showNextSlide();
    },

    isLocked: function() {
        return this.lockFlag;
    },
    lock: function() {
        this.lockFlag = true;
    },
    unlock: function() {
        this.lockFlag = false;
    },

    showNextSlide: function() {
        var nextSlideIndex = this.getNextSlideIndex();
        this.showSlideByIndex(nextSlideIndex);
    },

    getNextSlideIndex: function() {
        var nextSlideIndex = this.visibleSlideIndex + 1;
        if(nextSlideIndex >= this.slidesCount())
            return 0;
        else
            return nextSlideIndex;
    },

    showSlideByIndex: function(slideIndex) {
        if(slideIndex < 0 || slideIndex >= this.slidesCount()) {
            throw {
                name: 'InvalidArgument',
                message: 'Slide index is out of bounds'
            };
        }
        var slideToHide = this.slides[this.visibleSlideIndex];
        var slideToShow = this.slides[slideIndex];
        this.visibleSlideIndex = slideIndex;
        this.switchFromOneSlideToAnother(slideToHide, slideToShow);
    },

    switchFromOneSlideToAnother: function(slideToHide, slideToShow) {
        var animationDuration = this.duration / 5;
        this.lock();
        slideToHide.animate({opacity: 0}, animationDuration);
        slideToShow.animate({opacity: 1}, animationDuration, function(me) {
            return function() {
                me.unlock();
            };
        }(this));
    },

    stop: function() {
        this.clearInterval(this.timer);
    }
};

