/*
 * Async Javascript Library
 * "For making stuff happen when you want it to."
 * 
 * http://cavaliere.org/async
 *
 * Copyright (c) 2009 Mike Cavaliere
 * Licensed under the GNU Public License (GPL).
 */

/*jslint browser: true, forin: true*/

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

Async.Util = {
  /*
   * Determine equality of two objects
   */
  same: function(a, b)
  {
    if (typeof a != typeof b) { return false; }
    
    switch (typeof a)
    {
        case "object":
            // Compare each property recursively
            var ok = true;
            
            for (var prop in a)
            {
                ok = ok && Async.Util.same(a[prop], b[prop]);
            }

            return ok;
            
        case "function":
            return a === b;
            
        case "string":
            return a === b;
    }
  }  
};

/*
 * Subscriber class
 * 
 * Reflects a piece of code that must run after a certain event
 */
Async.Subscriber = function(fn, name)
{
	this._fn = fn;
	this._name = name;
};

Async.Subscriber.prototype = {
    is: function(criterion)
    {
        switch (typeof criterion)
        {
            // Is criterion this subscriber?
            case "object":
                return Async.Util.same(criterion, this);
            
            // Is criterion this subscriber's function?
            case "function":
                return Async.Util.same(criterion, this._fn);
            
            // Is criterion this subscriber's event name?
            case "string":
                return Async.Util.same(criterion, this._name);
        }
        
        return false;
    },
    fire: function()
    {
        return this._fn();
    }
};

Async.Observer = function()
{
    this._log = [];
    this._notifications = []; // Keeps a list of fired events
    this._subscribers = []; // Queues code that fires after a specified event
};

Async.Observer.prototype = {

	// Push fn into subscriber queue; set it to fire on the indicated event
    subscribe: function()
    {
    	var oe = arguments[0];
    
    	if (arguments.length > 1)
    	{
    		var fn        = arguments[0];
    		var eventName = arguments[1];
    		oe = new Async.Subscriber(fn, eventName);
    	}
    	        
        // If the event happened already - fire the code now
        if (this.notified(oe._name)) 
        {
            this._subscribers.push(oe);
            oe.fire();    
            return;
        }
    
    	this._subscribers.push(oe);
    },

	// Run all queued code waiting on a given event
    notify: function(eventName)
    {
    	var list = this.subscribersTo(eventName);
        
        this._log.push(new Date() + ': ' + eventName);
    	this._notifications.push(eventName);
    
    	for (var i=0, l=list.length; i<l; i++)
    	{
    		this._subscribers[list[i]].fire();
    	}
    	
    	
    }, 

	// Return true if the event has subscribers
	contains: function(event)
	{
		return this.subscribersTo(event).length > 0;
	},
	
	// Return true if the event has occurred/fired 	
	notified: function(eventName)
	{
		for (var i=0, l=this._notifications.length; i<l; i++)
		{
			if (this._notifications[i] == eventName)
			{
				return true;
			}
		}
		
		return false;
	},

	// Return array of subscriber indices
	subscribersTo: function(event)
	{
		var matches = [];

		for (var i=0, l=this._subscribers.length; i<l; i++)
		{
			if (this._subscribers[i].is(event)) {
		  	matches.push(i);
		  }
		}

		return matches;
	},

  // Return log
  log: function()
  {
    return this._log;
  }
};



/*



  ----------------------------------

  -create a function, assign it an event name
  -other functions push to the event
  -run the event manually, when it completes, notifypushrs()
    (i.e., for every pushr to that event, run queued code in proper scope)


  q = new Async.Observer();

  function A()
  {
    ...

    // Notify all pushers that this event has occurred
    q.notify("A-completed");
  }


  // Execute function
  A();

  ...

  // Set B() to run after "A-completed" is in queue
  q.subscribe(B, "A-completed");

  // Set C() to run after "A-completed" is in queue
  q.subscribe(C, "A-completed");

  // As above, but only run if the passed function returns true
  q.subscribe(C, "A-completed", function() {

    var someCondition = ....

    return someCondition;
  });





  // Also: different notifiers based on diff outcomes


	function doAjax()
	{
		$.ajax({
		   url: ...

		   success: function() { q.notify('doAjax-success'); }
		   error: function() { q.notify('doAjax-failure'); }
		   complete:  function() { q.notify('doAjax-completed'); }
		});
	}


  *** REQUIREMENTS ***

  - Event names have to all be unique
  - All queued functions have to manually send the notification when complete


 */