/**
 * Utility library.
 */
var UTIL = function(){
  
  
  
  /**
   * Tests if a given object is an integer.
   *
   * @param num : An object.
   * @return : True if the object is an integer, false otherwise.
   */
  function isInteger( num ){
    if( typeof(num) !== 'number' ){
      return false;
    }
    
    if( num % 1 !== 0 ){
      return false;
    }
    
    return true;
  }
  
  
  
  /**
   * Returns a copy of the given array.
   *
   * @param arr : The array.
   * @throw ERR.IllegalArgumentException : If the parameter is not an array.
   * @return : A copy of the given array.
   */
   function copyArray( arr ){
     if( arr == null || ( typeof(arr) !== 'array' && arr.constructor !== Array ) ){
       throw new ERR.IllegalArgumentException("Argument was not an array.");
     }
     return arr.slice(0);
   }
   
   
   
  /**
   * Converts seconds to a Time object.
   *
   * @param secs : A non-negative integer representing a time in seconds.
   * @throw ERR.IllegalArgumentException : If the given seconds is not an integer or is negative.
   * @return : The seconds as a time object.
   */
  function convertSecondsToTime( secs ){
    if( !isInteger( secs ) || secs < 0 ){
      throw new ERR.IllegalArgumentException("Seconds must be a non-negative integer.");
    }
    
    var hours = Math.floor( secs/(60*60) );
    var minutes = Math.floor( secs%(60*60)/(60) );
    var seconds = Math.floor( secs%60 )
    
    return new Time( hours, minutes, seconds );
  }
  
  
  
  /**
   * Converts a Time object in to seconds.
   *
   * @param time : A time object.
   * @throw ERR.IllegalArgumentException : If the given parameter is not a Time object.
   * @return : The time in seconds.
   */
  function convertTimeToSeconds( time ){
    if( !isInstanceOf( time, Time ) ){
      throw new ERR.IllegalArgumentException("Give argument was not a Time object.");
    }
    
    var seconds = ( time.getHours()*60*60 ) + ( time.getMinutes()*60 ) + time.getSeconds();
    
    return seconds;
  }
  
  
  
  /**
   * Tests if a given object is an instance of a given class.
   *
   * @param obj : The object.
   * @param constr : The class (a constructor function).
   * @throw ERR.IllegalArgumentException : If the given class is not a function.
   * @return : True if the given objects constructor matches the given class.
   */
  function isInstanceOf( obj, constr ){
    if( typeof(constr) !== 'function' ){
      throw new ERR.IllegalArgumentException("Given class is not a function.");
    }
    
    if( obj === null ){
      return false;
    }
    
    if( typeof(obj) === 'object' && obj.constructor === constr ){
      return true;
    }
    
    return false;
  }
  
  
  
  /**
   * Tests if a given array contains only instances of a given class or primitive type.
   *
   * @param arr : The array.
   * @param type : The class (a constructor function) or a string naming the type of the object (i.e 'string').
   * @throw ERR.IllegalArgumentException : If the type is not a function or string OR if the array is empty.
   * @return : True if every object in the array has a constructor of the given class, or is the given type.
   */
   function isArrayOfType( arr, type ){
     if( arr == null || ( typeof(arr) !== 'array' && arr.constructor !== Array ) ){
       throw new ERR.IllegalArgumentException("Expected an array but received: " + typeof(arr) );
     }
     if( arr.length <= 0 ){
       throw new ERR.IllegalArgumentException("Array is empty.");
     }
     if( type == null || ( typeof(type) !== 'string' && typeof(type) !== 'function' )){
       throw new ERR.IllegalArgumentException(" Type is not a function or string.");
     }
     
     for(var i = 0; i < arr.length; i++){
       if( arr[i] !== null && arr[i].constructor !== type && typeof(arr[i]) !== type ){
         return false;
       }
     }
     
     return true;
   }
   
   
   
  /**
   * Creates a time object to hold a time defined in hours, minutes and seconds.
   */
  function Time( h, m, s ){

    if( !UTIL.isInteger(h) || h < 0 ){
      throw new ERR.IllegalArgumentException("Hours must be a non-negative integer.");
    }
    if( h >= 24 ){
      throw new ERR.IllegalArgumentException("Hours must be within range [0,23]");
    }
    if( !UTIL.isInteger(m) || m < 0 ){
      throw new ERR.IllegalArgumentException("Minutes must be a non-negative integer.");
    }
    if( m >= 60 ){
      throw new ERR.IllegalArgumentException("Minutes must be within range [0,59]");
    }
    if( !UTIL.isInteger(s) || s < 0 ){
      throw new ERR.IllegalArgumentException("Seconds must be a non-negative integer.");
    }
    if( s >= 60 ){
      throw new ERR.IllegalArgumentException("Seconds must be within range [0,59]");
    }

    var hours = h;
    var minutes = m;
    var seconds = s;

    /**
     * Gets the hours component of the time.
     *
     * @return : The hours as a number.
     */
    this.getHours = function(){
      return hours;
    }

    /**
     * Gets the minutes component of the time.
     *
     * @return : The minutes as a number.
     */
    this.getMinutes = function(){
      return minutes;
    }

    /**
     * Gets the seconds component of the time.
     *
     * @return : The seconds as a number.
     */
    this.getSeconds = function(){
      return seconds;
    }

    /**
     * Gets the time in string format.
     *
     * @return : The time as a string.
     */
     this.toString = function(){

       var hs = hours;
       if( hours < 10){
         hs = "" + "0" + hours;
       }

       var ms = minutes;
       if(minutes < 10){
         ms = "" + "0" + minutes;
       }

       var ss = seconds;
       if(seconds < 10){
         ss = "" + "0" + seconds;
       }

       return hs + ":" + ms + ":" + ss;
     }
     
     /**
      * Gets the time as a string formatted in hh:mm:ss format.
      *
      * @return : A string representing the time in hh:mm:ss format.
      */
      this.formatHHMMSS = function(){
        return toString();
      }
      
    /**
     * Gets the time as a string formatted in h:mm:ss format.
     *
     * @return : A string representing the time in h:mm:ss format.
     */
     this.formatHMMSS= function(){

       var hs = hours;

       var ms = minutes;
       if(minutes < 10){
         ms = "" + "0" + minutes;
       }

       var ss = seconds;
       if(seconds < 10){
         ss = "" + "0" + seconds;
       }

       return hs + ":" + ms + ":" + ss;
     }
      
      /**
       * Gets the time as a string formated in mm:ss format.
       *
       * @return : A string representing the time in mm:ss format.
       */
       this.formatMMSS = function(){
         
         var ms = (hours*60) + minutes;
         if(minutes < 10){
           ms = "" + "0" + minutes;
         }

         var ss = seconds;
         if(seconds < 10){
           ss = "" + "0" + seconds;
         }

         return ms + ":" + ss;
       }
       
       /**
        * Gets the time as a string formated in m:ss format.
        *
        * @return : A string representing the time in m:ss format.
        */
        this.formatMSS = function(){

          var ms = (hours*60) + minutes;

          var ss = seconds;
          if(seconds < 10){
            ss = "" + "0" + seconds;
          }

          return ms + ":" + ss;
        }
       
       /**
        * Gets the time in seconds as a string.
        *
        * @return : A string representing the time in ss format.
        */
        this.formatSS = function(){

          var ss = (hours*60*60) + (minutes*60) + seconds;
          if(seconds < 10){
            ss = "" + "0" + seconds;
          }

          return ss;
        }
  }
  
  
  
  return{ isInteger:isInteger,
          convertSecondsToTime:convertSecondsToTime,
          convertTimeToSeconds:convertTimeToSeconds,
          copyArray:copyArray,
          isInstanceOf:isInstanceOf,
          isArrayOfType:isArrayOfType,
          Time:Time};
  
}();

//##########################################################################################################################
//##########################################################################################################################

/**
 * Error handling library.
 */
 var ERR = function(){
   
   /**
    * Creates a generic exception.
    *
    * @param message : The error message.
    */
   function Exception( message ){

     if( message === null ){
       message = "Error";
     }

     /**
      * Gets the error message.
      *
      * @return : An error message as a string.
      */
     this.getMessage = function(){
       return message;
     };
   }

   /**
    * Creates an illegal argument exception.
    *
    * @param message : The error message.
    */
   function IllegalArgumentException( message ){

     Exception.call(this, message);

   }

   /**
    * Creates a file not found exception.
    *
    * @param message : The error message.
    */
   function FileNotFoundException( message ){
     
     Exception.call(this, message);
     
   }
   
   return { Exception:Exception,
            IllegalArgumentException:IllegalArgumentException,
            FileNotFoundException:FileNotFoundException }
   
 }();
 
//##########################################################################################################################
//##########################################################################################################################

/**
 * Synchronization library.
 */
var SYNC = function(){
  
  
  
  /**
   * Helper method that waits for a given 'condition' function to return true before executing
   * the given callback.
   *
   * @param condition : A function that represents the condition. Must return true for wait to complete.
   * @param callbk : A callback function to be executed after the 'condition' function evaluates true.
   */
  var wait = function( condition, callbk ){
    
    var wait = setInterval(function() {

      if( condition() ){
        clearInterval(wait);
        callbk();

        return;
      }
    }, 200);
  }
  
  
  
  /**
   * Creates a barrier that waits for a number of threads to complete.
   *
   * @param threads : The number of threads to wait for.
   * @throw IllegalArgumentException : If the number of threads is not a positive integer.
   */
  var Barrier = function( threads ){
    
    /* Throw IllegalArgumentException if count is not an integer */
    if( !UTIL.isInteger( threads ) || threads < 1){
      throw new ERR.IllegalArgumentException("SYNC.Barrier:constructor : Number of threads must be a positive integer.");
    }

    var thisBarrier = this;
    var waitCount = threads;
    var thisthreadIDs = new Array( threads );

    /* Init threads as incomplete */
    for(i = 0; i < threads; i++){
      thisthreadIDs[i] = false;
    }
    
    /**
     * Gets the number of threads being waited on by this barrier.
     *
     * @return : The number of threads this barrier is waiting for.
     */
    this.getWaitCount = function(){
      return waitCount;
    }
    
    /**
     * Gets a callback function used by the thread of a given ID to
     * notify the barrier that it has finished.
     *
     * @return : A function used to notify this barrier that a thread has completed.
     * @throw : IllegalArgumentException if threadID is not a positive integer
     *          OR if threadID >= the number of threads the barrier is waiting for.
     */
    this.getNotifier = function( threadID ){

      /* Throw IllegalArgumentException if threadID is not a non-negative integer */
      if( !UTIL.isInteger( threadID ) || threadID < 0){
        throw new ERR.IllegalArgumentException("SYNC.Barrier:getNotifier( threadID ) : threadID must be a non-negative integer.");
      }

      /* Is the thread id monitored by this barrier? */
      if( threadID >= waitCount ){
        throw new ERR.IllegalArgumentException("SYNC.Barrier:getNotifier( threadID ) : Thread with ID " + threadID + " is not monitored by this barrier.");
      }

      /* Return callback function that sets the thread as finished */
      return function(){ 
        thisthreadIDs[threadID] = true;
      };
    }
    
    /**
     * Checks if all threads have reached the barrier (completed
     * their execution).
     *
     * @return : True if all threads have finished.
     */
    this.hasFinished = function(){

      var finished = true;

      /* Check if all threads have finished */
      for(i = 0; i < waitCount; i++){
        if( 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.
     */
    this.wait = function( interval, callback ){

      /* Ensure interval is valid */
      if(interval == null){
        throw new IllegalArgumentException("SYNC.Barrier:wait( interval, callback ) : interval is undefined.");
      }else if( typeof(interval) != 'number' ){
        throw new IllegalArgumentException("SYNC.Barrier:wait( interval, callback ) : interval is not a number.");
      }else if( interval < 0 ){
        throw new IllegalArgumentException("SYNC.Barrier:wait( interval, callback ) : interval is not a non-negative number.");
      }

      /* Ensure callback is a function */
      if(callback == null){
        throw new IllegalArgumentException("SYNC.Barrier:wait( interval, callback ) : callback is undefined.");
      }else if( typeof(callback) != 'function'){
        throw new IllegalArgumentException("SYNC.Barrier:wait( interval, callback ) : callback is not a function.");
      }

      /* Execute callback when all threads have arrived at the barrier */
      var wait = setInterval(function() {

        if( thisBarrier.hasFinished() == true){
          clearInterval(wait);
          callback();

          return;
        }
      }, interval);

    }
    
  }
  
  return {Barrier:Barrier,
          wait:wait};
  
}();

//##########################################################################################################################
//##########################################################################################################################

/**
 * Event handling library.
 */
var EVENT = function(){
  
  
  
  /**
   * Generic event handler which executes callbacks to interested parties.
   */
  function EventHandler(){

    var eventListeners = new Array();

    /**
     * Adds a function that will listen for an event and then execute.
     * The given callback function can expect one parameter according to the implementation of the EventHandler.
     *
     * @param listener : A callback function that will be executed every time the event occurs.
     * @throw ERR.IllegalArgumentException : If the given listener is not a function.
     */
    this.addEventListener = function( listener ){
      if( listener == null || typeof(listener) !== 'function' ){
        throw ERR.IllegalArgumentException("Given listener is not a function.");
      }
      eventListeners.push( listener );
    }

    /**
     * PROTECTED:
     * Informs all attached listeners that an event occured. The listener functions will be executed and passed
     * the given parameter object if provided.
     *
     * @param params : The optional parameter object (optional).
     */
    var alertEventListeners = function( params ){ 
      // Do change state callbacks
      for( var i = 0; i < eventListeners.length; i++ ){
        eventListeners[i]( params );
      }
    }

    //Return protected 'alertEventListeners' method
    return { alertEventListeners:alertEventListeners };

  }
  
  
  
  /**
   * Creates a time change handler that executes calbacks when time changes.
   */
  function TimeChangeHandler(){

    var protected = EVENT.EventHandler.call();

    /**
     * Adds a function that will listen for a change in time and then execute.
     * The given callback function can expect one parameter; An integer representing the new time in seconds.
     *
     * @param listener : A callback function that will be executed every time the time changes.
     * @throw ERR.IllegalArgumentException : If the given listener is not a function.
     */
    this.addTimeChangeListener = addEventListener;
    this.addEventListener = undefined;

    /**
     * PROTECTED:
     * Informs all attached listeners that a time change has occurred, passing them the new time in seconds.
     *
     * @param secs : An integer representing the new time in seconds.
     * @throw ERR.IllegalArgumentException : If the given seconds are negative or not an integer.
     */
    var alertTimeChangeListeners = function( secs ){ 
      if( secs < 0 || !UTIL.isInteger( secs ) ){
        throw new ERR.IllegalArgumentException("Seconds must be a non-negative integer");
      }

      protected.alertEventListeners( secs );
    }

    //Return protected methods
    return { alertTimeChangeListeners:alertTimeChangeListeners };
  }
  
  
  
  /**
   * Creates a state change handler that responds to the given states.
   *
   * @param : An array of strings describing the states this handler will respond to.
   * @throw ERR.IllegalArgumentException : If the given states are not an array of strings.
   */
  function StateChangeHandler( states ){
    
    if( !UTIL.isArrayOfType( states, 'string' ) ){
      throw new ERR.IllegalArgumentException("Available states must be an array of strings.");
    }
    
    var handledStates = states;
    
    // Inherit from EVENT.EventHandler
    var protected = EVENT.EventHandler.call();
    
    /**
     * Returns a string description of the states handled by this handler. These strings match those that
     * will be passed to any attached listening functions on state change.
     *
     * @return : An array of strings describing the events that are handled.
     */
     this.getHandledStates = function(){
       return UTIL.copyArray( handledStates );
     }
     
     /**
      * Adds a function that will listen for a change in the state of an object and then execute.
      * The given callback function can expect one parameter; A string representing the state change.
      *
      * @param listener : A callback function that will be executed every time the object's state changes.
      * @throw ERR.IllegalArgumentException : If the given listener is not a function.
      */
      this.addStateChangeListener = addEventListener;
      this.addEventListener = undefined;
      
      /**
       * PROTECTED:
       * Informs all attached listeners that a state change has occurred, passing them the string representation of the new state.
       *
       * @param stateIx : An integer representing the new state. This index must map to the appropriate string in the handled events array.
       */
      var alertStateChangeListeners = function( stateIx ){
        if( !UTIL.isInteger( stateIx ) ){
          throw new ERR.IllegalArgumentException("State index is not an integer.");
        }
        if( stateIx < 0 || stateIx >= handledStates.length ){
          throw new ERR.IllegalArgumentException("State with index " + stateIx + "doesn't exist.");
        }
        
        // Do change state callbacks
        protected.alertEventListeners( handledStates[ stateIx ] );
      }
      
      //Return protected methods
      return { alertStateChangeListeners:alertStateChangeListeners };
  }
  
  return { EventHandler:EventHandler,
           StateChangeHandler:StateChangeHandler,
           TimeChangeHandler:TimeChangeHandler }
}();

