/**
 * Sheep.  Sheep can be herded and do what they are told.  Sheeps are awesome.
 * All Sheep listen to the Shepherd.  The Shepherd keeps track of the states of certain variables.
 * When a variable changes state, the Shepherd notifies each Sheep listening to that state of the change.
 * Each Sheep will then run a corresponding JS based on what state changed.
 *
 * @package agdistis
 * @version $Id$
 */

 
/**
 * Shepherd Object
 *
 * @return Shepherd
 */
function Shepherd(interval)
{
	/** 
	 * How often to check state changes for all the sheep
	 *
	 * @var int - milliseconds
	 */
	 this.check_sheep_interval = (interval ? interval : 250);
		
	/** 
	 * Stores a list of variable names and their values.  This is where all state information is stored.
	 * Sheep then check their own states against this master state table.
	 *
	 * @var array
	 */
	this.master_states = new Object();
	
	
	/** 
	 * Stores a list of states that should be ignored for one pass.
 	 *
	 * @var array
	 */
	this.ignore_states = new Object();
	
	
	/** 
	 * This is where we store all our sheep objects so we can keep track of them and feed them states changes.
	 *
	 * @var array
	 */
	this.sheep_pen = new Object();

	/** 
	 * Stores the timer object used by setInterval so we can stop it if we want.
	 *
	 * @var object
	 */
	this.watch_timer = null;
}


/** 
 * Object identity function.  Look for me to figure out what this is.
 */ 
Shepherd.prototype.is_a_shepherd = function()
{
	return true;
}
  

/** 
 * Store a key value pair in the master state array.
 *
 * @param string state_key -  Name of the state to store value in
 * @param mixed value - What we want to store in the state
 */ 
Shepherd.prototype.set_state = function(state_key, value, ignore)
{
	if (state_key)
	{
		this.master_states[state_key] = (value != undefined) ? value : null;
		this.ignore_states[state_key] = ignore ? true : false;
	}
		
}







/** 
 * Retrieve a master state value.
 *
 * @param string state_key - The state key we want a value from
 * @return mixed value - The value in that state
 */ 
Shepherd.prototype.get_state = function(state_key)
{
	return (this.master_states[state_key] != undefined) ? this.master_states[state_key] : null;
}


/** 
 * Retrieves a list of ignored states.
 *
 * @param string state_key - The state key we want a value from
 * @return bool - Whether or not to ignore the state change
 */ 
Shepherd.prototype.get_ignore_state = function(state_key)
{
	return (this.ignore_states[state_key] != undefined) ? this.ignore_states[state_key] : false;
}


/** 
 * Resets the ignored states.
 *
 * @return void
 */ 
Shepherd.prototype.reset_ignore_states = function()
{
	this.ignore_states = new Object;
}




/**
 * Puts a sheep object in to the pen.  Does an insert unless the sheep is a clone.
 * If the sheep is a clone, it updates the old sheep with the new sheep.
 *
 * @param object sheep - The Sheep object we wish to store.
 */
Shepherd.prototype.pen_sheep = function(sheep)
{
	var sheep_name = sheep.get_name();
	if (sheep_name && sheep.is_a_sheep)
	{
		// If sheep already exists, merge in the old states
		if (this.sheep_pen[sheep_name])
		{
			sheep.set_last_states(this.sheep_pen[sheep_name].get_last_states());
		}
			
		this.sheep_pen[sheep_name] = sheep;
	}
}	


/**
 * Removes a sheep from the pen.  Stops all processing on that sheep.
 *
 * @param string sheep_name - The name of the sheep object to release.
 */
Shepherd.prototype.free_sheep = function(sheep_name)
{
	if (this.sheep_pen[sheep_name])
	{
		delete this.sheep_pen[sheep_name];
	}
}	


/**
 * Call me from a setInterval, sets up a listener.  Checks states Sheep wants to listen to.
 * When a state changes, call the function attached to the state.
 * This lets objects on the page deal with themselves when global states change since we cant keep track of them.
 * Static function, doesn't get put in the Sheep object since it will waste space.
 *
 * @param object sheep - The sheep object we want to check on.
 */
Shepherd.prototype.check_on_sheep = function(sheep)
{
	for (var state_key in sheep.get_state_keys())
	{
		// We ignore null master states.
		if (this.get_state(state_key) == null)
		{
			// Clear all old states on a new null entry.
			if (sheep.get_last_state(state_key) != null)
			{
				sheep.set_last_state(state_key, null);
			}

			continue;
		}

		// Check if state has changed, and make sure we're not ignoring it
		if (this.get_state(state_key) != sheep.get_last_state(state_key))
		{
			if (!this.get_ignore_state(state_key))
			{
				// Call the function associated with this state change
				eval(sheep.get_state_key_functions(state_key));
			}
			
			// Update the last state value AFTER calling the function
			sheep.set_last_state(state_key, this.get_state(state_key));
		}
	}
}


/**
 * Go through each sheep in the pen and check its states versus the master states using check_on_sheep
 */
Shepherd.prototype.corral_sheep = function()
{
	for (var sheep_name in this.sheep_pen)
	{
		if (this.sheep_pen[sheep_name].is_a_sheep)
		{
			this.check_on_sheep(this.sheep_pen[sheep_name]);
		}
	}
	
	// Clear all ignore states
	this.reset_ignore_states();
}


/**
 * Set up a timer function to corral the sheep every so often.
 * This essentially starts the event listeners.
 * Note: This is a clever way of making setInterval work.  But it could be fragile.
 */
Shepherd.prototype.watch_sheep = function()
{
	var self = this;
	this.watch_timer = setInterval(function(){ self.corral_sheep(); }, this.check_sheep_interval);
}


/**
 * Disable all the setInterval calling corral_sheep, stops all listeners.
 */
Shepherd.prototype.stop_watching_sheep = function()
{
	clearInterval(this.watch_timer);
}




/**
 * Sheep Object
 *
 * @return Sheep
 */
function Sheep()
{

	this.state_keys_functions = new Object();
	this.last_state = new Object();
	
	/**
	 * Each sheep can have its own private key value pairs
	 * Its just an extra storage mechanism.
	 * @var object
	 */
	this.privates = new Object();
	
	/** 
	 * @var mixed sheep_name - false until name is set, then string
	 */
	this.sheep_name = null;
}

/**
 * JS seemingly has no method of determinging WHAT an object might be.
 * So just check the existance of is_a_sheep to ensure you HAVE a sheep.
 *
 * @return true
 */
Sheep.prototype.is_a_sheep = function()
{
	return true;
}

/**
 * Name this sheep object.  This prevents duplicate sheep spawning per object
 * when dynamically reloading the object (AJAX).  Sheep cloning is baaad.
 *
 * @param string name - Just a unique identifier for this sheep object.
 */
Sheep.prototype.set_name = function(name)
{
	if (name)
	{
		this.sheep_name = name;
	}
}

/**
 * Returns the sheep name stored.
 * 
 * @return mixed - false if no name set, string containing the name otherwise.
 */
Sheep.prototype.get_name = function(name)
{
	return this.sheep_name;
}

/**
 * Call me after birthing a sheep to set up state keys to listen for, and JS to run when they change
 *
 * @param string state_key - State key to listen for
 * @param string jsfunc - Eval() this when a state changes.
 */
Sheep.prototype.on_state = function(state_key, jsfunc)
{
	// Set a state to keep tabs on and assign a function to it
	if (state_key && jsfunc)
	{
		this.state_keys_functions[state_key] = jsfunc;
	}
}

/**
 * Returns the state_keys_function array
 *
 * @return array - state_keys to js functions
 */
Sheep.prototype.get_state_keys = function()
{
	return this.state_keys_functions;
}


/**
 * Returns the js function stored for any given state key
 *
 * @param string state_key - State key to listen for
 * @return string - JS Function stored for this state key
 */
Sheep.prototype.get_state_key_functions = function(state_key)
{
	return (this.state_keys_functions[state_key]) ? this.state_keys_functions[state_key] : null;
}


/**
 * Sets the last known state value for any given state key
 *
 * @param string state_key - State key to listen for
 * @param mixed value - What new value to store for this state
 */
Sheep.prototype.set_last_state = function(state_key, value)
{
	if (state_key)
	{
		this.last_state[state_key] = (value != undefined) ? value : null;
	}
}


/**
 * Returns the last state value for any given state key
 *
 * @param string state_key - State key to listen for
 * @return mixed - last known value for this key
 */
Sheep.prototype.get_last_state = function(state_key)
{
	return (this.last_state[state_key] != undefined) ? this.last_state[state_key] : null;
}



/**
 * Retrieve the last states object.
 *
 * @return array
 */
Sheep.prototype.get_last_states = function()
{
	return this.last_state;
}



/**
 * Sets the last states object
 *
 * @param array last_states
 * @return void
 */
Sheep.prototype.set_last_states = function(last_states)
{
	this.last_state = last_states;
}

