// Requires Prototype 1.6+

if ('undefined' == typeof UI)
  UI = {};

UI.Carousel3D = (function() {
  var DefaultOptions = {
    autoStart: false,
    step:      -4,
    zIndex:    100,
    fps:       40
  };
  
  function draw(stepAhead) {
    var count = this.items.length, baseZIndex = this.element.style.zIndex, that = this;
    if (stepAhead == 'auto')
      this._baseAngle = (this._baseAngle + this.options.step) % 360;
    else if (stepAhead == 'roll') {
      this._baseAngle = (this._baseAngle + this._rollStep);
      if (Math.abs(this._baseAngle - this._rollTargetAngle) < Math.abs(this._rollStep)) {
        this._baseAngle = this._rollTargetAngle;
        this._stopRoll();
      }
    }
    var baseAngle = this._baseAngle * 2 * Math.PI / 360;
    var angle, posX, posY
    this.items.each(function(item, index) {
      angle = index * 2 * Math.PI / count;
      posX = that._centerX + Math.sin(baseAngle + angle) * that._centerX;
      posY = that._centerY + Math.cos(baseAngle + angle) * that._centerY;
      item.setStyle({ left: posX + 'px', top: posY + 'px', zIndex: Math.round(posY / 3) + baseZIndex });
    });
  }
  
  function handleItemClick(event) {
    if (event.findElement('a')) return;
    var item = this.items.find(function(item) { return event.element().descendantOf(item); });
    if (item)
      this.rollTo(item);
  }
  
  function rollTo(item) {
    item = $(item);
    var index = this.items.indexOf(item);
    if (-1 == index) return;
    var itemPosX = parseInt(item.getStyle('left'), 10);
    this.stop();
    this._rollTargetAngle = (360 - index * 360 / this.items.length).round();
    if (this._rollTargetAngle == this._baseAngle || this._rollTargetAngle == 360 && this._baseAngle == 0) return;
    // Roll in the intuitive direction
    this._rollStep = Math.abs(this.options.step) * (itemPosX < this._centerX ? 1 : -1);
    if (this._baseAngle < this._rollTargetAngle && this._rollStep < 0)
      this._baseAngle += 360;
    if (this._baseAngle > this._rollTargetAngle && this._rollStep > 0)
      this._rollTargetAngle += 360;
    // Let's roll!
    this._rollTimer = setInterval(this._roll, this._interval);
  }
  
  function setup() {
    this.element.makePositioned();
    if ((this.element.style.zIndex || 0) < this.options.zIndex)
      this.element.setStyle('z-index: ' + this.options.zIndex);
    this.items.invoke('setStyle', 'float: none; position: absolute;');
    var width = this.options.width || this.items.invoke('getWidth').inject(0, function(acc, value) {
      return acc + value * 0.9;
    }).ceil();
    var height = this.options.height || (this.items.invoke('getHeight').max() * 0.9).ceil();
    this._baseAngle = this.options.baseAngle || 0;
    this._centerX = (width / 2).round();
    this._centerY = (height / 2).round();
    this._draw();
    this._interval = (1000 / (this.options.fps || DefaultOptions.fps)).floor();
    if (this.options.clickToRoll)
      this.element.observe('click', this._handleItemClick);
  }
  
  function start() {
    if (this._timer) return;
    this._stopRoll();
    this._timer = setInterval(this._redraw, this._interval);
  }
  
  function stop() {
    this._stopRoll();
    if (!this._timer) return;
    this._baseAngle = this._baseAngle % 360;
    clearInterval(this._timer);
    delete this._timer;
  }
  
  function stopRoll() {
    if (!this._rollTimer) return;
    this._baseAngle = this._baseAngle % 360;
    clearInterval(this._rollTimer);
    delete this._rollTimer;
  }
  
  function Carousel3D(element, options) {
    this.element = $(element);
    this.items   = this.element.childElements();
    this.options = Object.extend(DefaultOptions, options || {})

    this._draw            = draw;
    this._handleItemClick = handleItemClick.bind(this);
    this._redraw          = draw.bind(this, 'auto');
    this._roll            = draw.bind(this, 'roll');
    this.rollTo           = rollTo;
    this._setup           = setup;
    this.start            = start;
    this.stop             = stop;
    this._stopRoll        = stopRoll;
    this._setup();

    if (this.options.autoStart)
      this.start();
  }
  
  Carousel3D.DefaultOptions = DefaultOptions;
  return Carousel3D;
})();

