/*
    Rotator Class - Remon Oldenbeuving (Solid Square)
*/

var Rotator = function($elements, speed) {
  // Store the this object for use in hover
  var that = this;

  // Instantiate private variables
  this.$elements = $elements;

  this.$current = $elements.filter(':first').css('z-index', 20);

  // Keep track of the elements which will stop the rotator from rotating 
  // on focus
  this.addFocusable($elements);

  // Is the cursor on top of the images? (initial)
  this.focus = false;

  // Speed of the animation
  this.speed = speed;

  // Object which holds the bound events
  this.bound_events = {};

  // Event queue, used to make halt/resume work
  this.eventQueue = [];
  this.running = false;
};

// Stop the rotator from rotating
Rotator.prototype.stop = function() {
  // Stop the interval from running
  window.clearInterval(this.interval);

  // Clear the eventQueue
  this.eventQueue = [];
};

// Start the rotator
Rotator.prototype.start = function() {
  // Reference for use within setInterval
  var that = this;
  
  // Start running
  this.run();

  // Store the interval in a private variable (for later use)
  this.interval = setInterval(function() {
    // Only rotate if there is no focus
    if (!that.focus) {
      // Trigger the beforeChange event
      that.trigger('beforeChange');
      
      // Queue the event in the eventQueue
      that.queue(that.internalInterval);
    }
  }, this.speed, this);
};

// Opposite of halt
Rotator.prototype.proceed = function() {
  // Start the animations
  this.run();
};

// Function which will halt all queue operations
Rotator.prototype.halt = function() {
  // Stop the animations by setting boolean to false
  this.running = false;

  // Stop the interval from stacking
  //window.clearInterval(this.interval);
};

// Add a function to the eventQueue
Rotator.prototype.queue = function(func) {
  // Push the event to the bottom of the stack
  this.eventQueue.push(func);

  // If the script hasn't been halted
  if (this.running) {
    // Run the queue
    this.run();
  }
};

// Function which will run all the functions in the queue
// TODO: infinite loop fix
Rotator.prototype.run = function() {
  this.running = true;
  for (var i = 0; this.eventQueue.length > 0;) {
    // Make sure the script hasn't been halted
    if (this.running) {
      // Shift the first object in queue and call
      // TODO: Check if this is a function?
      this.eventQueue.shift().call(this);
    }
  }
};

// The actual function which will be used in the interval
Rotator.prototype.internalInterval = function() {
  // Find the visible image and fade the next image in
  this.showEl(this.nextEl());
};

Rotator.prototype.nextEl = function() {
  var next = this.$current.next();
  
  if ( next.length > 0 ) {
    return next;
  } else {
    return this.$elements.filter(':first');
  }
};

Rotator.prototype.prevEl = function() {
  var prev = this.$current.prev();
  
  if ( prev.length > 0 ) {
    return prev;
  } else {
    return this.$elements.filter(':last');
  }
};

Rotator.prototype.showEl = function($el) {
  var self = this;
  
  $el.show().css('z-index', 10);
  
  // We're using throttle because Chrome & Firefox seem to stack the event callbacks
  // when the page is not visible. So when you return to the page the whole
  // slider would go mental
  var throttle = function() {
    $el.css('z-index', 20);

    self.$current.css('z-index', 1);

    $(this).show();

    self.$current = $el;

    // Trigger the afterChange event
    self.trigger('afterChange');
  };

  this.$current.fadeOut('slow', throttle);
};

// Function which will force the rotator to change to a given element
// TODO: add support for a function argument
Rotator.prototype.forceChange = function(obj) {
  // Trigger the right event
  this.trigger('beforeChange');
    
  var el = obj;
  
  if ( typeof obj === "string" ) {
    el = $('#' + id);
  }

  // Clear the current interval
  this.stop();
  
  if (this.$current === el) {
    this.trigger('afterChange');
    return;
  }

  this.queue(function() {
    // Only proceed if there is a matching element
    if (el.length > 0) {
      // Put the next element on top of all the others
      this.showEl(el);

      // Trigger the afterChange event
      this.trigger('afterChange');
    }
  });

  this.start();
};

// We use a bind structure for eventhandling
Rotator.prototype.bind = function(event, callback) {
  // Has the event not yet been bound?
  if (!this.bound_events.hasOwnProperty(event)) {
    // Create an empty array
    this.bound_events[event] = [];
  }

  // Add the callback to the stack
  this.bound_events[event].push(callback);
};

// Trigger the right events
Rotator.prototype.trigger = function(event) {
  // Only proceed if there are (or were) any events bound
  if (this.bound_events.hasOwnProperty(event)) {
    // Get all the callbacks
    var callbacks = this.bound_events[event];

    for (var i = callbacks.length; i--;) {
      // Parse the arguments (we don't want to send the event identifier)
      var args = Array.prototype.slice.call(arguments, 1);

      // credits voor HJ
      try {
        // Call the function
        // We use apply instead of call so we can pass the arguments
        callbacks[i].apply(this, args);
      } catch (e) {}
    }
  }
};

// Add elements which will stop the rotator from rotating on focus
Rotator.prototype.addFocusable = function($elem) {
  // Check if the $focus variable has been set
  if (typeof this.$focus !== "Array") {
    // If not, create an empty array
    this.$focus = [];
  }
  // Used in the event bindings
  var that = this;

  // Push the elements on top of the array
  this.$focus.push($elem);

  // Bind the hover to make sure the animation stops when the 
  // cursor is on top of the image
  $elem.bind('mouseenter', function() {
    that.focus = true;
  }).bind('mouseleave', function() {
    that.focus = false;
  });
};

function d(str) {
  console.info( "[ROTATOR-DEBUG]", (new Date()),  str);
}

