/*--------------------------------------------------------------------------*/
/*	MVC, Events, etc.
/*--------------------------------------------------------------------------*/

// Crad Namespace
if ( !window.Crad )
	var Crad = new Object();

// Observable
//
// Allows objects to register (and unregister) to receive notification of change.
//
// Observable is intended to be used by other objects in a has-a relationship:
//		var ob = new Observable();
//		this.register = ob.register;
//		this.unregister = ob.unregister;
//		... (an observable event occurs)
//		ob.notify();
//
// Observers register by providing a function to call when change occurs:
//		function notifyMe()
// ...and, of course, observers can use bind() to be called in whatever context
// suits them.
Crad.Observable = function()
{
	var _observers = new Array();
	
	this.length = _observers.length;
	
	this.dispose = function()
	{
		var len = _observers.length;
		for ( var i=0; i<len; i++ )
		{
			_observers[i] = null;
		}
		_observers = null;
	}
	
	this.register = function (fctn)
	{
		_observers.push(fctn);
	}
	
	this.unregister = function(fctn)
	{
		var len = _observers.length;
		for ( var i=0; i<len; i++ )
		{
			if ( _observers[i] == fctn )
			{
				_observers[i] = null;
				_observers.splice(i,1);
				break;
			}
		}
	}
	
	this.notify = function(e)
	{
		var len = _observers.length;
		for ( var i=0; i<len; i++ )
		{
			var fctn = _observers[i];
			fctn.apply(this,arguments);
		}
	}
}

// Create a Delegator object for each element that can fire events.
// The Delgator object handles registering and unregistering event handlers for the given element, 
// and calling all event handlers for a given event when that event is fired.
// TODO: potential problem with namespace conflict by adding delegator as field of element. could be solved by using a 
// global delegator mgr with quick lookup by element.
Crad.EventDelegator = function(elem)
{
	// handlers is an collection of observables -- one for each eventType
	// example
	// {
	//	'click':[fctn1, fctn2, fctn3],
	//	'mouseover':[fctn4]
	//	'mouseout':[fctn5, fctn6]
	// }
	var _handlers = new Object();
	var _elem = elem;
	_elem.delegator = this;
	
	// register
	// register an event handler for a given event type	
	this.register = function(eventType, handler)
	{
		// retrieve the array of handlers for the given event; create if necessary
		var obsrv = _handlers[eventType];
		if ( obsrv == null )
		{
			// create the observable for this eventType
			obsrv = new Crad.Observable();
			_handlers[eventType] = obsrv;
			
			// set our static callback as the element's event handler for this eventType
			_elem['on'+eventType] = this.notify;
		}
		// add the new handler
		obsrv.register(handler);
	}
	
	// unregister
	// remove an event handler for a given event type
	this.unregister = function(eventType,handler)
	{
		// remove the handler from the array for the event type
		var obsrv = _handlers[eventType];
		obsrv.unregister(handler);

		// if there are no more handlers for the event type, then delete the observable entirely
		// from the handlers list and remove the event handler from the element
		if ( obsrv.length == 0 )
		{
			delete _handlers[eventType];
			_elem['on'+eventType] = null;	// remove the event handler from the element
		}
	}

	// notify -- gets called with the context set to the element that fired the event;
	// we pass that context along to the notify routine of our observable
	this.notify = function(e)
	{
		var e = e || window.event; // cross-browser
		var obsrv = _handlers[e.type];
		obsrv.notify.call(this,e);
	}	
}

// Model
//
// Provides a simple Model object for controlling access to an object.
//
// The model:
//	- is Observable
//	- provides access to an object
//	- notifies observers when the object is changed through the setObject method
Crad.Model = function(obj)
{
	var _obj = obj;
	var _obsrv = new Crad.Observable();
	this.register = _obsrv.register;
	this.unregister = _obsrv.unregister;
	
	this.dispose = function()
	{
		_obsrv.dispose();
		_obsrv = null;
		_obj = null;
	}
	
	this.getObject = function()
	{
		return _obj;
	}
	
	this.setObject = function(obj)
	{
		_obj = obj;
		_obsrv.notify(_obj);
	}
}

// Cursor
//
// Provides a cursor object to be used with an Array in an underlying Model.
// The cursor is itself a Model (since it can change, and will notify observers),
// but it also has a model (of the underlying Array). The underlying data model
// can change indepently from the Cursor, and the Cursor will respond
// accordintly.
//
// Observers to the Cursor need not Observe the underlying data Model; the Cursor
// will notify of changes to the underlying data.
//
// Cursor accepts two optional arguments: copy() and compare() routines.
// copy() will be called to make a stored copy of the underlying data object (or any part of the object),
// and Compare() will be called to compare the stored copy with a row in the dataArray.
// The default implementations of copy() and compare() perform a shallow copy and shallow compare.
Crad.Cursor = function(dataModel, copy, compare)
{
	var _index = 0;
	var _dataModel = dataModel;
	var _copy = copy;
	var _compare = compare;
	if ( _copy == null )
	{
		_copy = shallowCopy;
		_compare = shallowCompare;
	}
	
	var _currentRow = _copy(_dataModel.getObject()[_index]);	// get the current row
	var _cursorObsrv = new Crad.Observable();

	
	// dispose
	this.dispose = function()
	{
		_dataModel = null;
		_copy = null;
		_compare = null;
		_currentRow = null;
		_cursorObsrv.dispose();
		_cursorObsrv = null;
	}
	
	// register/unregister is passed through to the cursor observable
	this.register = _cursorObsrv.register;
	this.unregister = _cursorObsrv.unregister;

	// getCursor, setCursor allow for changes to the cursor, and
	// they are simply passed through to the cursor Model
	this.getIndex = function()
	{
		return _index;
	}
	this.setIndex = function(index)
	{
		_index = index;
		_currentRow = _copy(_dataModel.getObject()[_index]);
		_cursorObsrv.notify(_index);
	}
	
	// return the property corresponding to the given key at the current row
	this.getData = function (key)
	{
		if ( key == null )
			return _dataModel.getObject()[_index];
		else
			return _dataModel.getObject()[_index][key];
	}
	
	// length returns length of underlying data
	this.length = function () { return _dataModel.getObject().length; }

	// we register ourselves with the underlying data model, and notify
	// our observers if it changes ONLY if the underlying cursor changed.
	dataModel.register(observeData);
	
	// called when the underlying data Model changes.
	// we go through the data looking for data that corresponds to
	// our current cursor; if found, we simply update our cursor without
	// notifying observers. If not found, we adjust our cursor and notify.
	function observeData()
	{
		var dataArray = _dataModel.getObject();
		var row;
		// if the underlying data changed, but our cursor was unaffected, just return
		// this is an optimization: it handles cases where data in a different row
		// changes, but neither the size of the array nor the data at our cursor change.
		if ( _index <= dataArray.length )
		{
			row = dataArray[_index];
			if (_compare(_currentRow, row)) return;
		}
		
		// look for a row that matches our cursor. if found, adjust the cursor and return
		// this handles inserts and deletes into the dataArray that cause no change to the
		// data at our cursor.
		for ( var i in dataArray )
		{
			row = dataArray[i];
			if (_compare(_currentRow, row))
			{
				// update the cursor WITHOUT notifying observers
				_index = i;
				return;
			}
		}
		
		// if we get here, the row was not found, or was changed, so 
		// we update the cursor and nofify observers
		if ( _index >= dataArray.length )	// dataArray may have gotten smaller, e.g. deletes
		{
			_index = dataArray.length - 1;
		}
		_currentRow = _copy(dataArray[_index]);
		_cursorObsrv.notify(_index);
	}
}


