function Carousel(somePreferences) {
    this.preferences = somePreferences;
    this.currentView = 0;
    this.views = $(this.preferences.platformID).immediateDescendants().size();
    this.viewWidth = $(this.preferences.platformID).immediateDescendants()[this.currentView].getWidth();
    this.leftTweener = new Tweener();
    this.rightTweener = new Tweener();
}

Carousel.prototype.nextView = function() {
    if (this.currentView == this.views - 1) {
        return 0;
    } else {
        return this.currentView + 1;
    }
}

Carousel.prototype.previousView = function() {
    if (this.currentView == 0) {
        return this.views - 1;
    } else {
        return this.currentView - 1;
    }
}

Carousel.prototype.incrementCurrentView = function() {
    if (this.currentView == this.views - 1) {
        this.currentView = 0;
    } else {
        ++this.currentView;
    }
}

Carousel.prototype.decrementCurrentView = function() {
    if (this.currentView == 0) {
        this.currentView = this.views - 1;
    } else {
        --this.currentView;
    }
}

Carousel.prototype.next = function() {
    if (this.rightTweener.isTweening) {
        return;
    }
    
    $(this.preferences.platformID).immediateDescendants()[this.nextView()].setStyle({ left: this.viewWidth + 'px', visibility: 'visible' });
    this.leftTweener.onTweenEnd = function() { $(this.preferences.platformID).immediateDescendants()[this.previousView()].setStyle({ visibility: 'hidden' }); }.bind(this);
    this.leftTweener.tween($(this.preferences.platformID).immediateDescendants()[this.currentView], 'left', -this.viewWidth, this.preferences.durationInSeconds, this.preferences.framesPerSecond, this.preferences.ease);
    this.rightTweener.tween($(this.preferences.platformID).immediateDescendants()[this.nextView()], 'left', 0, this.preferences.durationInSeconds, this.preferences.framesPerSecond, this.preferences.ease);
    this.incrementCurrentView();
    
    if (this.preferences.indicatorsID) {
        $(this.preferences.indicatorsID).insertBefore($(this.preferences.indicatorsID).descendants().last(), $(this.preferences.indicatorsID).descendants().first());
    }
}

Carousel.prototype.previous = function() {
    if (this.leftTweener.isTweening) {
        return;
    }
    
    $(this.preferences.platformID).immediateDescendants()[this.previousView()].setStyle({ left: -this.viewWidth + 'px', visibility: 'visible' });
    this.rightTweener.onTweenEnd = function() { $(this.preferences.platformID).immediateDescendants()[this.nextView()].setStyle({ visibility: 'hidden' }); }.bind(this);
    this.rightTweener.tween($(this.preferences.platformID).immediateDescendants()[this.currentView], 'left', this.viewWidth, this.preferences.durationInSeconds, this.preferences.framesPerSecond, this.preferences.ease);
    this.leftTweener.tween($(this.preferences.platformID).immediateDescendants()[this.previousView()], 'left', 0, this.preferences.durationInSeconds, this.preferences.framesPerSecond, this.preferences.ease);
    this.decrementCurrentView();
    
    if (this.preferences.indicatorsID) {
        $(this.preferences.indicatorsID).insertBefore($(this.preferences.indicatorsID).descendants().first(), null);
    }
}

function Tweener() {
    this.isTweening = false;
}

Tweener.prototype.tween = function(anElement, aProperty, aTargetValue, aDurationInSeconds, aFramesPerSecond, anEase) {
    // alert('anElement: ' + anElement);
    // alert('aProperty: ' + aProperty);
    // alert('aTargetValue: ' + aTargetValue);
    // alert('aDurationInSeconds: ' + aDurationInSeconds);
    // alert('aFramesPerSecond: ' + aFramesPerSecond);
    // alert('anEase: ' + anEase);
    
    this.isTweening = true;
    
    this.element = anElement;
    this.property = aProperty;
    this.targetValue = aTargetValue;
    // alert(this.targetValue);
    this.durationInSeconds = aDurationInSeconds;
    this.framesPerSecond = aFramesPerSecond;
    this.ease = anEase;
    
    this.tweenedFrames = 0;
    this.tweenFrames = this.durationInSeconds * this.framesPerSecond;
    this.frameIntervalInMilliseconds = Math.floor(1000 / (this.framesPerSecond - 1));
    
    this.sourceValue = parseInt(this.element.getStyle(this.property), 10);
    // alert(this.sourceValue);
    this.valueDifference = this.targetValue - this.sourceValue;
    // alert(this.valueDifference);

    setTimeout(function() { this.intervalFrameTween(); }.bind(this), this.frameIntervalInMilliseconds);
}

Tweener.prototype.intervalFrameTween = function() {
    ++this.tweenedFrames;
    if (this.tweenedFrames < this.tweenFrames) {
        var t = this.tweenedFrames / this.tweenFrames;
        switch (this.ease) {
            case 'none':
                var d = t;
            case 'out':
                var d = this.easeOutCurve(t);
            case 'in':
                var d = this.easeInCurve(t);
            case 'inOut':
                var d = this.easeInOutCurve(t);
        }
    } else {
        var d = 1;
    }
    // alert(d);

    var intervalValue = this.sourceValue + Math.ceil(d * this.valueDifference);
    // alert(intervalValue);
    var styles = $H();
    styles[this.property] = intervalValue + 'px';
    this.element.setStyle(styles);

    if (this.tweenedFrames < this.tweenFrames) {
        setTimeout(function() { this.intervalFrameTween(); }.bind(this), this.frameIntervalInMilliseconds);
    } else {
        this.isTweening = false;
        if (this.onTweenEnd) {
            this.onTweenEnd();
        }
    }
}

Tweener.prototype.easeOutCurve = function(x) {
    return Math.sin(Math.PI * (x - 1) / 2) + 1;
}

Tweener.prototype.easeInCurve = function(x) {
    return Math.sin(Math.PI * x / 2);
}

Tweener.prototype.easeInOutCurve = function(x) {
    return (Math.sin(Math.PI / 2 * (2 * x - 1)) + 1) / 2;
}
