/**
 * -----------------------------------------------------------------------------
 * ImageCarousel component
 *
 * @author     Ronald Drenth <ronald@vdlp.nl>
 * @copyright  Copyright (c) 2010, Van der Let & Partners Identity
 * @version    1.1
 * @dependency Prototype 1.6.1, Scriptaculous 1.8.3
 * @browsers   IE7, FF3, Safari 5, Chrome 5
 * -----------------------------------------------------------------------------
 *
 * Constructor:
 * var horizontal = new ImageCarousel(
 *   $$('#image-carousel-horizontal .view-wrapper'), // wrapper div for the large images
 *   $$('#image-carousel-horizontal .view .image'),  // all the large images
 *   $$('#image-carousel-horizontal .view-control'), // controls
 *   {
 *     mode            : 'horizontal',   // horizontal or vertical
 *     effect          : false,          // true or false
 *     effectDuration  : 0.4,            // the duration of the effect in seconds
 *     amountVisible   : 1,              // amount of visible images, important!
 *     activeImageClass : 'active',      // className for the active image
 *     disabledControlClass : 'disabled' // className for the disabled controls
 *   },
 *   $$('#image-carousel-horizontal .list-wrapper'), // wrapper for the thumbs
 *   $$('#image-carousel-horizontal .list .thumb'),  // all the thumbs
 *   $$('#image-carousel-horizontal .list-control'), // controls
 *   {
 *     mode            : 'horizontal',   // horizontal or vertical
 *     effect          : true,          // true or false
 *     effectDuration  : 0.4,            // the duration of the effect in seconds
 *     amountVisible   : 1,              // amount of visible images, important!
 *     activeImageClass : 'active',      // className for the active image
 *     disabledControlClass : 'disabled' // className for the disabled controls
 *   }
 * );
 *
 * -----------------------------------------------------------------------------
 */

ImageCarousel = Class.create({
  initialize: function(
    viewElement, viewImages, viewControls, viewOptions,
    listElement, listImages, listControls, listOptions
  ) {
    viewOptions = Object.extend({
      beforeMove : this.beforeMoveView.bind(this),
      afterMove  : this.afterMoveView.bind(this)
    }, viewOptions || {});

    listOptions = Object.extend({
      beforeMove : this.beforeMoveList.bind(this),
      afterMove  : this.afterMoveList.bind(this),
      onClick    : this.onClickList.bind(this)
    }, listOptions || {});

    this.view = new ImageCarouselAbstract(viewElement, viewImages, viewOptions);
    this.list = new ImageCarouselAbstract(listElement, listImages, listOptions);

    this._initControlsView(this.view, viewControls);
    this._initControlsList(this.list, listControls);
  },
  _initControlsView: function(object, controls)
  {
    this.viewControls = controls;
    this._initDefaultControls(object, controls);

    if (
      !Object.isUndefined(controls) &&
      !Object.isString(controls) &&
      controls != null &&
      controls.length > 0
    ) {
      this.initViewControls();
      this.positionViewControls();
      this.initViewContainers();
      this.positionViewContainers();
      this.toggleControls([this.imageNextContainer, this.imagePrevContainer], object);
    }
  },
  _initControlsList: function(object, controls)
  {
    this.listControls = controls;
    this._initDefaultControls(object, controls);
  },
  _initDefaultControls: function(object, controls)
  {
    if (
      Object.isUndefined(controls) ||
      Object.isString(controls) ||
      controls == null
    ) {
      return;
    }


    if (controls.length == 0) {
      return;
    }

    controls.each(function(control, index) {
      var rel = control.readAttribute('rel');

      if (rel == 'next') {
        control.observe('click', this.next.bindAsEventListener(this));
      }
      else if (rel == 'prev') {
        control.observe('click', this.prev.bindAsEventListener(this));
      }
      else if (rel == 'first') {
        control.observe('click', this.first.bindAsEventListener(this));
      }
      else if (rel == 'last') {
        control.observe('click', this.last.bindAsEventListener(this));
      }
    }.bind(this));

    this.toggleControls(controls, object);
  },
  toggleControls: function(controls, object, enable)
  {
    if (controls == null || controls.length == 0) {
      return;
    }

    if (Object.isUndefined(enable)) {
      enable = true;
    }

    controls.each(function(control, index) {
      if (!Object.isUndefined(control)) {
        var rel = control.readAttribute('rel');
        var className = object.options.disabledControlClass;

        if (enable) {
          control.removeClassName(className);
        }
        else {
          control.addClassName(className);
        }

        if (rel == 'next' || rel == 'last') {
          if (this.view.getLastIndex() == this.view.getIndex()) {
            control.addClassName(className);
          }
        }
        else if (rel == 'prev' || rel == 'first') {
          if (this.view.getFirstIndex() == this.view.getIndex()) {
            control.addClassName(className);
          }
        }
      }
    }.bind(this));
  },
  initViewControls: function()
  {
    this.viewNext = new Array();
    this.viewPrev = new Array();

    this.viewControls.each(function(control, index) {
      var rel = control.readAttribute('rel');

      if (rel == 'next') {
        // next
        this.viewNext.push(control);
      }
      else if (rel == 'prev') {
        // prev
        this.viewPrev.push(control);
      }
    }.bind(this));
  },
  positionViewControls: function()
  {
    offset = this.view.element.cumulativeOffset();

    // next
    this.viewNext.each(function(next, index) {
      var dimensions = next.getDimensions();
      next.setStyle({
        position : 'absolute',
        display  : 'none',
        left     : (offset.left
                 + this.view.options.width
                 - dimensions.width)
                 + 'px',
        top      : (offset.top
                 + (this.view.options.height / 2)
                 - (dimensions.height / 2))
                 + 'px'
      });
    }.bind(this));

    // prev
    this.viewPrev.each(function(prev, index) {
      var dimensions = prev.getDimensions();
      prev.setStyle({
          position : 'absolute',
          display  : 'none',
          left     : (offset.left)
                   + 'px',
          top      : (offset.top
                   + (this.view.options.height / 2)
                   - (prev.getHeight() / 2))
                   + 'px'
      });
    }.bind(this));
  },
  initViewContainers: function()
  {
    this.imageNextContainer = new Element('div', {
      rel : 'next'
    }).addClassName('image-carousel-overlay-control');
    this.imagePrevContainer = new Element('div', {
      rel : 'prev'
    }).addClassName('image-carousel-overlay-control');

    document.body.appendChild(this.imageNextContainer);
    document.body.appendChild(this.imagePrevContainer);

    this.imageNextContainer.observe('mouseover', this.onNextContainerMouseOver.bindAsEventListener(this));
    this.imageNextContainer.observe('mouseout', this.onNextContainerMouseOut.bindAsEventListener(this));
    this.imageNextContainer.observe('click', this.onNextContainerClick.bindAsEventListener(this));

    this.imagePrevContainer.observe('mouseover', this.onPrevContainerMouseOver.bind(this));
    this.imagePrevContainer.observe('mouseout',this.onPrevContainerMouseOut.bind(this));
    this.imagePrevContainer.observe('click',  this.onPrevContainerClick.bind(this));
  },
  positionViewContainers: function()
  {
    offset = this.view.element.cumulativeOffset();

    this.imageNextContainer.setStyle({
      position : 'absolute',
      left     : (offset.left)
               + (this.view.options.width)
               - (this.view.options.width / 4)
               + 'px',
      top      : offset.top + 'px',
      width    : (this.view.options.width / 4) + 'px',
      height   : this.view.options.height  + 'px'
    });

    this.imagePrevContainer.setStyle({
      position : 'absolute',
      left     : offset.left + 'px',
      top      : offset.top + 'px',
      width    : (this.view.options.width / 4) + 'px',
      height   : this.view.options.height + 'px'
    });

    this.imageNextContainer.show();
    this.imagePrevContainer.show();
  },
  /*** MOVE FUNCTIONS ***/
  moveToIndex: function(index)
  {
    this.beforeMoveList();
    this.beforeMoveView();

    var origListEffect = this.list.options.effect;
    var origViewEffect = this.view.options.effect;

    this.list.options.effect = false;
    this.view.options.effect = false;

    this.list.moveToIndex(index);
    this.view.moveToIndex(index);

    this.list.options.effect = origListEffect;
    this.view.options.effect = origViewEffect;

    this.afterMoveList();
    this.afterMoveView();
  },
  next: function(event)
  {
    var element = Event.element(event);
    var viewHasClass = element.hasClassName(this.view.options.disabledControlClass);
    var listHasClass = element.hasClassName(this.list.options.disabledControlClass)

    if (!viewHasClass) {
      this.view.next();
    }

    if (!listHasClass) {
      this.list.next();
    }
  },
  prev: function(event)
  {
    var element = Event.element(event);
    var viewHasClass = element.hasClassName(this.view.options.disabledControlClass);
    var listHasClass = element.hasClassName(this.list.options.disabledControlClass)

    if (!viewHasClass) {
      this.view.prev();
    }

    if (!listHasClass) {
      this.list.prev();
    }
  },
  first: function(event)
  {
    var element = Event.element(event);
    var viewHasClass = element.hasClassName(this.view.options.disabledControlClass);
    var listHasClass = element.hasClassName(this.list.options.disabledControlClass)

    if (!viewHasClass) {
      this.view.first();
    }

    if (!listHasClass) {
      this.list.first();
    }
  },
  last: function(event)
  {
    var element = Event.element(event);
    var viewHasClass = element.hasClassName(this.view.options.disabledControlClass);
    var listHasClass = element.hasClassName(this.list.options.disabledControlClass)

    if (!viewHasClass) {
      this.view.last();
    }

    if (!listHasClass) {
      this.list.last();
    }
  },
  /*** EVENTS ***/
  beforeMoveView: function()
  {
    this.toggleControls(this.viewControls, this.view, false);
    this.toggleControls([this.imageNextContainer, this.imagePrevContainer], this.view, false);
    this.view.getImage(this.view.getPreviousIndex()).removeClassName(this.view.options.activeImageClass);
  },
  beforeMoveList: function()
  {
    this.toggleControls(this.listControls, this.list, false);
    this.list.getImage(this.view.getPreviousIndex()).removeClassName(this.list.options.activeImageClass);
  },
  afterMoveView: function()
  {
    this.toggleControls(this.viewControls, this.view, true);
    this.toggleControls([this.imageNextContainer, this.imagePrevContainer], this.view, true);
    this.view.getImage(this.view.getIndex()).addClassName(this.view.options.activeImageClass);
  },
  afterMoveList: function()
  {
    this.toggleControls(this.listControls, this.list, true);
    this.list.getImage(this.view.getIndex()).addClassName(this.list.options.activeImageClass);
  },
  onClickList: function(event, element)
  {
    var clickIndex = element.retrieve('index');
    var viewIndex  = this.view.getIndex();
    var toggle     = true;

    if (clickIndex == viewIndex) {
      return;
    }

    var listIndex = this.list.getIndex();
    this.view.moveToIndex(clickIndex);
    this.list.setClassNameAtIndex(clickIndex);

    if (clickIndex == listIndex) {
      this.list.prev();
      toggle = false;
    }

    if (((this.list.options.amountVisible - 1) + listIndex) == clickIndex) {
      this.list.next();
      toggle = false;
    }

    if (toggle) {
      this.toggleControls(this.listControls, this.list, true);
      this.toggleControls(this.viewControls, this.view, true);
    }
  },
  onNextContainerMouseOver: function(event)
  {
    var element = Event.element(event);

    if (!element.hasClassName(this.view.options.disabledControlClass)) {
      this.viewNext.each(function(next, index) {
        next.show();
        new Effect.Opacity(next, { from: 0.0, to: 1.0, duration: 0 });
      });
    }
  },
  onNextContainerMouseOut: function(event)
  {
    this.viewNext.each(function(next, index) {
      new Effect.Opacity(next, { from: 1.0, to: 0.0, duration: 0 });
    });
  },
  onNextContainerClick: function(event)
  {
    var element = Event.element(event);

    if (!element.hasClassName(this.view.options.disabledControlClass)) {
      this.next(event);
    }
  },
  onPrevContainerMouseOver: function(event)
  {
    var element = Event.element(event);

    if (!element.hasClassName(this.view.options.disabledControlClass)) {
      this.viewPrev.invoke('show');
    }
  },
  onPrevContainerMouseOut: function(event)
  {
    this.viewPrev.invoke('hide');
  },
  onPrevContainerClick: function(event)
  {
    var element = Event.element(event);

    if (!element.hasClassName(this.view.options.disabledControlClass)) {
      this.prev(event);
    }
  }
});


ImageCarouselAbstract = Class.create({
  /*** CONSTRUCTOR ***/
  initialize: function(element, images, options)
  {
    this.options = Object.extend({
      mode            : 'horizontal',
      effect          : true,
      effectDuration  : 0.4,
      amountVisible   : 1,
      beforeMove      : null,
      afterMove       : null,
      onClick         : null,
      activeImageClass : 'active',
      disabledControlClass : 'disabled'
    }, options || {});

    if (images.length == 0) {
      return;
    }

    if (Object.isArray(element)) {
      element = element.first();
    }

    this.element = $(element);
    this.images   = images;
    this.active   = false;
    this.index    = 0;
    this.current  = this.images.first();
    this.previous = null;
    this.previousIndex = 0;

    // prepare images
    this.images.each(function(image, index) {
      image.store('index', index);

      if (this.options.onClick && Object.isFunction(this.options.onClick)) {
        image.observe('click', this.options.onClick.bindAsEventListener(this, image));
      }
    }.bind(this));

    // initialize element
    this._initElement();
  },
  _initElement: function()
  {
    if (Object.isUndefined(this.options.width)) {
      this.options.width = this.getImage().getWidth();
    }

    if (Object.isUndefined(this.options.height)) {
      this.options.height = this.getImage().getHeight();
    }

    if (this.options.mode == 'horizontal') {
      this.element.down().setStyle({
        width  : ((this.images.length + 1) * this.options.width) + 'px',
        height : this.options.height + 'px'
      });
    }
    else {
      this.element.down().setStyle({
        width  : this.options.width + 'px',
        height : ((this.images.length + 1) * this.options.height) + 'px'
      });
    }

    this.getCurrentImage().addClassName(this.options.activeImageClass);
  },
  setClassNameAtIndex: function(current)
  {
    this.images.invoke('removeClassName', this.options.activeImageClass);
    this.getImage(current).addClassName(this.options.activeImageClass);
  },
  /*** GETTERS ***/
  getNextIndex: function()
  {
    var nextIndex = 1;

    if ((this.images.length - 1) == this.index) {
      nextIndex = this.index;
    }
    else {
      nextIndex = this.index + 1;
    }

		if (nextIndex > this.images.length - (this.options.amountVisible + 1)) {
			nextIndex = this.images.length - this.options.amountVisible;
		}

		return nextIndex;
  },
  getPrevIndex: function()
  {
    var prevIndex = 0;

    if (this.index > 0) {
      prevIndex = this.index - 1;
    }

    return prevIndex;
  },
  getFirstIndex: function()
  {
    return 0;
  },
  getLastIndex: function()
  {
    var lastIndex = this.images.length - 1;

		if (lastIndex > this.images.length - (this.options.amountVisible + 1)) {
			lastIndex = this.images.length - this.options.amountVisible;
		}

		return lastIndex;
  },
  getImage: function(index)
  {
    if (!Object.isNumber(index)) {
      index = this.index;
    }

    return this.images[index];
  },
  getCurrentImage: function()
  {
    return this.getImage(this.index);
  },
  getIndex: function()
  {
    return this.index;
  },
  getPreviousIndex: function()
  {
    return this.previousIndex;
  },
  getLength: function()
  {
    return this.images.length;
  },
  /*** MOVE FUNCTIONS ***/
  next: function()
  {
    this.moveToIndex(this.getNextIndex());
  },
  prev: function()
  {
    var prevIndex = this.getPrevIndex();
    this.moveToIndex(this.getPrevIndex());
  },
  first: function()
  {
    this.moveToIndex(this.getFirstIndex());
  },
  last: function()
  {
    this.moveToIndex(this.getLastIndex());
  },
  moveToIndex: function(index)
  {
    this.previous      = this.current;
    this.previousIndex = this.index;
    this.current       = this.getImage(index);
    this.index         = index;

    var elementOffset = this.element.cumulativeOffset();
    var currentOffset = this.current.cumulativeOffset();

    if (this.active) {
      this.active.cancel();
    }

    this.beforeMove();

    if (this.options.effect) {
  		this.active = new Effect.SmoothScroll(this.element, {
  		  x           : (currentOffset[0] - elementOffset[0]),
  		  y           : (currentOffset[1] - elementOffset[1]),
  		  duration    : this.options.effectDuration,
  		  afterFinish : function() {
  		    this.afterMove();
  		  }.bind(this)
  		});
    }
    else {
      this.element.scrollLeft = (currentOffset[0] - elementOffset[0]);
      this.element.scrollTop  = (currentOffset[1] - elementOffset[1]);
      this.afterMove();
    }
  },
  /*** EVENTS ***/
  beforeMove: function()
  {
    if (this.options.beforeMove && Object.isFunction(this.options.beforeMove)) {
      // overrule
      this.options.beforeMove();
    }
    else {
      // default
      this.getImage(this.previousIndex).removeClassName(this.options.activeImageClass);
    }
  },
  afterMove: function(effect)
  {
    if (this.options.afterMove && Object.isFunction(this.options.afterMove)) {
      // overrule
      this.options.afterMove();
    }
    else {
      // default
      this.getImage(this.index).addClassName(this.options.activeImageClass);
    }
  }
});


Effect.SmoothScroll = Class.create();
Object.extend(Object.extend(Effect.SmoothScroll.prototype, Effect.Base.prototype), {
	initialize: function(element)
	{
		this.element = $(element);
		var options = Object.extend({
  		x    : 0,
  		y    : 0,
  		mode : 'absolute'
		} , arguments[1] || {});

		this.start(options);
  },
	setup: function() {
		if (this.options.continuous && !this.element._ext) {
			this.element.cleanWhitespace();
			this.element._ext = true;
			this.element.appendChild(this.element.firstChild);
    }

		this.originalLeft = this.element.scrollLeft;
		this.originalTop  = this.element.scrollTop;

		if (this.options.mode == 'absolute') {
			this.options.x -= this.originalLeft;
			this.options.y -= this.originalTop;
    }
  },
	update: function(position)
	{
		this.element.scrollLeft = this.options.x * position + this.originalLeft;
		this.element.scrollTop  = this.options.y * position + this.originalTop;
  }
});
