function SlideManager(photoViewer, album){
    this.slide = [];
    this.album = album;
    this.photoViewer = photoViewer;
    this.createSlide("current");
    this.createSlide("right");
    this.createSlide("left");
    this.repositionSlides();
    this.animatingPhoto = false;
    this.finalCallback = null;

    this.slideAnimator = new SlideAnimator(this);
}

SlideManager.prototype.createSlide = function(position){
    var slide = new Slide(this);
    this.slide[position] = slide;
    slide.createAndAppend();
    slide.repositionSlideElement();
}

SlideManager.prototype.setSlide = function (position, photo){
    if(photo && this.slide[position].photo != photo){
        this.slide[position].setPhoto(photo);
        this.slide[position].reposition();
    }
}

SlideManager.prototype.repositionSlides = function(){
    $("#photo").css({"top": View.area.slide.top + "px", "height": View.area.slide.height + "px", "width": View.area.slide.width + "px"});

    this.slide.current.reposition();
    this.slide.left.reposition();
    this.slide.right.reposition();
}

SlideManager.prototype.getSlideWithPhoto = function (photo){
    if (this.slide.current.photo == photo) return this.slide.current;
    if (this.slide.left.photo == photo) return this.slide.left;
    if (this.slide.right.photo == photo) return this.slide.right;
    
    var position = this.photoViewer.getPhotoPosition(photo);
    this.setSlide(position, photo);
    return this.slide[position];
}

SlideManager.prototype.slideReadyCallback = function (slide){
    slide.loadingFinished = true;
    if(slide.photo == this.photoToShowWhenLoaded){
        this.showSlide(slide);
    }
}

SlideManager.prototype.slideErrorCallback = function (slide){
    //TODO: manage this situation
    this.slideReadyCallback(slide);
}

SlideManager.prototype.incomingSlideAnimationFinishedCallback = function (slide){
    this.photoToShowWhenLoaded = null;
    this.photoViewer.setCurrentPhoto(slide.photo);

    if(this.getSlidePosition(slide) == "right"){
        this.changeSlidePosition(this.slide.right, "current");
        this.createSlide("right");
        this.setSlide("right", this.album.getNextPhoto(this.photoViewer.currentPhoto));
    } else {
        this.changeSlidePosition(this.slide.left, "current");
        this.createSlide("left");
        this.setSlide("left", this.album.getPreviousPhoto(this.photoViewer.currentPhoto));
    }

    this.finalCallback.registerFinishedCallback();
}

SlideManager.prototype.outgoingSlideAnimationFinishedCallback = function (slide, incomingSlidePosition){
    if(incomingSlidePosition == "right"){
        this.slide.left.removeElement();
        this.changeSlidePosition(slide, "left");
    } else {
        this.slide.right.removeElement();
        this.changeSlidePosition(slide, "right");
    }

    this.finalCallback.registerFinishedCallback();
}

SlideManager.prototype.animationFinishedCallback = function (){
    this.animatingPhoto = false;
    this.finalCallback = null;
}

SlideManager.prototype.showPhoto = function (photo){
    if (this.animatingPhoto) return;

    var slide = this.getSlideWithPhoto(photo);
    slide.setPhoto(photo);
    this.showSlideWhenPhotoIsReady(slide, photo);
}

SlideManager.prototype.showSlideWhenPhotoIsReady = function (slide, photo){
    this.photoToShowWhenLoaded = photo;
    this.photoViewer.showLoadingIndicator();
    if(slide.loadingFinished){
        this.showSlide(slide);
    }
}

SlideManager.prototype.showSlide = function (slide){
    this.photoViewer.hideLoadingIndicator();
    this.animatingPhoto = true;
    this.finalCallback = new FinalCallback(2, jQuery.proxy(this.animationFinishedCallback, this));

    var incomingSlide = slide;
    var outgoingSlide = this.slide.current;
    
    this.slideAnimator.exchangeSlides(incomingSlide, outgoingSlide, this.getSlidePosition(incomingSlide));
}

SlideManager.prototype.getSlidePosition = function (slide){
    if (slide == this.slide.current) return "current";
    if (slide == this.slide.right) return "right";
    if (slide == this.slide.left) return "left";
}

SlideManager.prototype.changeSlidePosition = function(slide, position){
    this.slide[position] = slide;
    slide.$element.attr("class", "slide " + position + "Slide");
}

function Slide(slideManager){
    this.photo = null;
    this.loadingFinished = false;
    this.slideManager = slideManager;
}

Slide.prototype.reposition = function(){
    this.repositionSlideElement();
    this.repositionPhotoElement();
}

Slide.prototype.getPosition = function (){
    return this.slideManager.getSlidePosition(this);
}

Slide.prototype.repositionSlideElement = function (){
    var slideLeftPosition = View.area.slide.left;
    
    if(this.getPosition() === "right"){
        slideLeftPosition += View.area.slide.width;
    }
    
    if(this.getPosition() === "left"){
        slideLeftPosition -= View.area.slide.width;
    }
    
    this.$element.css({
        "top": 0,
        "left": slideLeftPosition,
        "height": View.area.slide.height,
        "width": View.area.slide.width
    });
}

Slide.prototype.repositionPhotoElement = function (){
    if(this.photo){
        var photoPosition = this.getPhotoPosition(this.photo);
        $("img.photo", this.$element).css({
            "height": (photoPosition.height) + "px",
            "width": (photoPosition.width) + "px",
            "left": (photoPosition.left) + "px",
            "top": (photoPosition.top) + "px"
        });
    }
}

Slide.prototype.createAndAppend = function (){
    var $slide = $('<div class="slide"></div>');
    if(debug){
        $slide.addClass(this.getPosition() + "Slide");
    }
    $("#photo").append($slide);
    this.$element = $slide;
}

Slide.prototype.removeElement = function (){
    this.$element.remove();
}

Slide.prototype.setPhoto = function(photo){
    if(this.photo != photo){
        this.photo = photo;
        this.loadingFinished = false;

        var $img = $('<img class="photo"/>');
        $("img", this.$element).remove();
        this.$element.append($img);

        var self = this;
        $img.bind("load", function(){
            self.slideManager.slideReadyCallback(self)
        });

        $img.bind("error", function(){
            self.slideManager.slideReadyCallback(self)
        });

        $img.attr("src", photo.photoUrl);
        
        this.repositionPhotoElement()
    } 
}

Slide.prototype.getPhotoPosition = function(photo){
    var lPhotoSize = View.getPhotoSize(photo);
    var lWidth = lPhotoSize.width;
    var lHeight = lPhotoSize.height;

    var lLeft = Math.round((View.area.slide.width - lWidth - (View.area.frame.width * 2)) / 2);
    var lTop = Math.round((View.area.slide.height - lHeight - (View.area.frame.width * 2)) / 2);

    return {
        "width": lWidth,
        "height": lHeight,
        "left": lLeft,
        "top": lTop
    };
}

function FinalCallback(callbackCount, finalCallback){
    this.remainingCallbacks = callbackCount;
    this.finalCallback = finalCallback;
}

FinalCallback.prototype.registerFinishedCallback = function (){
    this.remainingCallbacks -= 1;
    if (this.remainingCallbacks == 0){
        this.finalCallback();
    }
}

SlideAnimator = function(slideManager){
    this.slideManager = slideManager;
}

SlideAnimator.prototype.exchangeSlides = function (incomingSlide, outgoingSlide, fromPosition){
    var moveCurrentSlideTo;

    this.prepareForIncomingAnimation(incomingSlide);

    if (fromPosition === "right"){
        moveCurrentSlideTo = View.area.slide.left - Math.floor(View.area.slide.width/3);
    } else {
        moveCurrentSlideTo = View.area.slide.left + Math.floor(View.area.slide.width/3);
    }

    var self = this;

    this.animateSlideWithCallback(incomingSlide, {
        "left": View.area.slide.left + "px",
        "opacity": 1
    }, function(){
        self.slideManager.incomingSlideAnimationFinishedCallback(incomingSlide);
    });

    this.animateSlideWithCallback(outgoingSlide, {
        "left": moveCurrentSlideTo + "px",
        "opacity": 0
    }, function(){
        self.slideManager.outgoingSlideAnimationFinishedCallback(outgoingSlide, fromPosition);
        self.updateAfterOutgoingAnimation(outgoingSlide);
    });
}

SlideAnimator.prototype.prepareForIncomingAnimation = function (slide){
    if(slide.getPosition() === "right"){
        slide.$element.css({
            "opacity": 0,
            "left": View.area.slide.left + Math.floor(View.area.slide.width/3) + "px"
        });
    } else {
        slide.$element.css({
            "opacity": 0,
            "left": View.area.slide.left - Math.floor(View.area.slide.width/3) + "px"
        });
    }
}

SlideAnimator.prototype.updateAfterOutgoingAnimation = function (slide){
    slide.$element.css("opacity", 1);
    slide.reposition();
}

SlideAnimator.prototype.animateSlideWithCallback = function (slide, css, callback){
    slide.$element.animate(css, ANIMATION_SPEED, callback);
}
