/****************************************************************************/

/**
 * Creates an Exception with a given error message.
 *
 * @param msg : The error message.
 */
function Exception ( msg ){

  this.errorMsg = msg;

}

/**
 * Gets the error message describing this Exception.
 *
 * @return : The error message.
 */
Exception.prototype.getMessage = function(){

  return this.errorMsg;

}

/****************************************************************************/

/**
 * Creates an IllegalArgumentException with a given error message.
 *
 * @param msg : The error message.
 */
function IllegalArgumentException( msg ){
  this.errorMsg = msg;
}

IllegalArgumentException.prototype = new Exception();

/****************************************************************************/

/**
 * Creates a RunTimeException with a given error message.
 *
 * @param msg : The error message.
 */
function RunTimeException( msg ){
  this.errorMsg = msg;
}

RunTimeException.prototype = new Exception();

/****************************************************************************/

/**
 * Creates a barrier synchroniser for 'count' many threads.
 *
 * @param number count : The number of threads to wait for.
 * @throw : IllegalArgumentException if count is not a positive integer.
 */
function BarrierSync( count ){

  /* Throw IllegalArgumentException if count is not an integer */
  if(typeof(count) != 'number'){
    throw new IllegalArgumentException("BarrierSync:constructor( count ) : count is not an integer.");
  }else if(count%1 != 0){
    throw new IllegalArgumentException("BarrierSync:constructor( count ) : count is a float, not an integer.");
  }

  /* Throw IllegalArgumentException if count <= 0  */
  if(count <= 0){
    throw new IllegalArgumentException("BarrierSync:constructor( count ) : count must be positive.");
  }

  this.waitCount = count;
  this.thisthreadIDs = new Array( count );

  /* Init threads as incomplete */
  for(i = 0; i < count; i++){
    this.thisthreadIDs[i] = false;
  }

}

/**
 * Gets the number of threads being waited on by this barrier.
 *
 * @return number : The number of threads this barrier is waiting for.
 */
BarrierSync.prototype.getWaitCount = function(){

  return this.waitCount;

}

/**
 * Gets a callback function used by the thread of a given ID to
 * notify the barrier that it has finished.
 *
 * @return : The number of threads this barrier is waiting for.
 * @throw : IllegalArgumentException if threadID is not a positive integer
 *          OR if threadID >= the number of threads the barrier is waiting for.
 */
BarrierSync.prototype.getNotifier = function( threadID ){

  /* Throw IllegalArgumentException if threadID is not a positive integer */
  if(typeof(threadID) != 'number'){
    throw new IllegalArgumentException("BarrierSync:getNotifier( threadID ) : threadID is not an integer.");
  }else if(threadID%1 != 0){
    throw new IllegalArgumentException("BarrierSync:getNotifier( threadID ) : threadID is a float, not an integer.");
  }else if( threadID < 0 ){
    throw new IllegalArgumentException("BarrierSync:getNotifier( threadID ) : threadID is not a non-negative integer.");
  }

  /* Is the thread in monitored by this barrier? */
  if( threadID >= this.waitCount ){
    /* No */
    throw new IllegalArgumentException("BarrierSync:getNotifier( threadID ) : Thread with ID " + threadID + " is not monitored by this barrier.");
  }
  
  var tIDs = this.thisthreadIDs;

  /* Return callback function that sets the thread as finished */
  return function(){ 
    tIDs[threadID] = true;
  };
}

/**
 * Checks if all threads have reached the barrier (completed
 * their execution).
 *
 * @return : True if all threads have finished.
 */
BarrierSync.prototype.hasFinished = function(){

  var finished = true;

  /* Check if all threads have finished */
  for(i = 0; i < this.waitCount; i++){
    if( this.thisthreadIDs[i] == false){
      finished = false;
    }
  }

  return finished;
}

/**
 * Waits for all threads to reach the barrier.
 *
 * @param interval : Time interval between checks (ms).
 * @param callback : Callback function called when all threads
 * have reached the barrier.
 * @throw : IllegalArgumentException if interval is a negative 
 *          number OR interval was not a number OR callback was 
 *          not a function.
 */
BarrierSync.prototype.wait = function( interval, callback ){
  
  var barrier = this;

  /* Ensure interval is valid */
  if(interval == null){
    throw new IllegalArgumentException("BarrierSync:wait( interval, callback ) : interval is undefined.");
  }else if( typeof(interval) != 'number' ){
    throw new IllegalArgumentException("BarrierSync:wait( interval, callback ) : interval is not a number.");
  }else if( interval < 0 ){
    throw new IllegalArgumentException("BarrierSync:wait( interval, callback ) : interval is not a non-negative number.");
  }

  /* Ensure callback is a function */
  if(callback == null){
    throw new IllegalArgumentException("BarrierSync:wait( interval, callback ) : callback is undefined.");
  }else if( typeof(callback) != 'function'){
    throw new IllegalArgumentException("BarrierSync:wait( interval, callback ) : callback is not a function.");
  }

  /* Execute callback when all threads have arrived at the barrier */
  var wait = setInterval(function() {
    
    if(barrier.hasFinished() == true){
      clearInterval(wait);
      callback();

      return;
    }
  }, interval);

}