/* Update messages using the new Messagebox DIV, etc. */
Clockwork.Message  =  Class.create( );

Clockwork.Message.prototype  =  {

	message_appear_duration: 0.3,

	messagebox: null,

	mini_message_duration: 5,

	ms_fudge: 50,

	options:  { },

	initialize:  function ( message, message_type, options ) {

		this.message         =  message;
		this.message_type    =  message_type;
		this.options         =  this.options || { };

		Object.extend( this.options, options || {} );

		this.message_details    = this.options.message_details || [];

		var message_node  =  $( ( this.options.message_target ? this.options.message_target : 'messagebox_target' ) );

		if ( ! message_node ) { Logger.log( 'Cannot find id: ' + ( this.options.message_target ? this.options.message_target : 'messagebox_target' ), CW_LOG_DEBUG ); return; }

		/* Clear out the children */
		if ( ! this.options.append ) {
			$A( message_node.childNodes ).each( ( function ( node ) { 
				node && node.parentNode && Element.remove( node );
			} ).bind( this ) );
		}

		this.messagebox  =  this.get_messagebox_element( );

		/* Hide the message for appear effect */
		if ( window.Effect ) {
			this.messagebox.style.display  =  'none';
		}

		/* Add the message to the DOM */
		this.messagebox  =  message_node.appendChild( this.messagebox );

		if ( window.Effect ) {
			var effect_opts  =  { };
			Object.extend( effect_opts, Element.effect_opts );
			effect_opts.duration  =  this.message_appear_duration;
			Effect.Appear( this.messagebox, effect_opts ) ;
		}

		/* Mini-messages have duration by default. */
		if ( options.mini_message && ! options.duration ) { options.duration  =  this.mini_message_duration; }

		/* If there's a duration on this Message, clean it up! */
		if ( options.duration ) {
			setTimeout( ( function ( ) { 
					this.messagebox && this.messagebox.parentNode && this.remove_message( this.messagebox ); 
				} ).bind( this ), 
				Math.floor( options.duration * 1000 ) 
			);
		}


	},


	remove:  function ( ) {

		if ( ! this.messagebox ) {
			return;
		}

		this.remove_message( this.messagebox );
	},

	
	remove_message:  function ( message ) {

		if ( message.nodeType != 3 && window.Effect ) {
			Element.fade_remove( message );
		}
		else {
			message && message.parentNode && Element.remove( message );
		}

	 },


	get_messagebox_element:  function ( ) {

		var mb         =  ( this.options.mini_message ) ? document.createElement( 'span' ) : document.createElement( 'div' );
		if ( this.options.message_id ) { mb.id  =  this.options.message_id; }
		mb.className   =  this.options.mini_message ? 'mini_messagebox' : 'messagebox';
		mb.className  +=  ( this.message_type ? ( ' ' + this.message_type ) : '' );

		var h2         =  document.createElement( 'h2' );
		h2.className   =  ( this.message_type ? this.message_type : '' );
		h2.appendChild( document.createTextNode( this.message ) );
		mb.appendChild( h2 );

		/* Draw message details unordered list? */
		var draw_details  =  ( ! this.options.mini_message                       &&
								 this.message_details                            && 
								 typeof( this.message_details ) === typeof( [] ) &&
								 this.message_details.length > 0                    );

		if ( draw_details ) {

			var ul = new Element( 'ul', { 'class':this.message_type } );
			mb.appendChild( ul );

			for (var i=0;i<this.message_details.length;i++) {

				var message_text = '';
				if ( typeof( this.message_details[i] ) == typeof( '') ) {
					message_text = this.message_details[i];
				}
				else if ( typeof( this.message_details[i] ) == typeof( {} ) &&
					 this.message_details[i].error_detail ) {
					message_text = this.message_details[i].error_detail;
				} 

				var li = new Element( 'li' ).update( message_text );
				ul.appendChild(li);
			}

			mb.appendChild( ul );

		} 		

		return mb;
	}
}

Clockwork.js_redirect = function( url, method, unique_id ) {

	if ( !method ) {
		method == 'GET';
	}

	if ( method == 'GET' ) {
		window.location = url;
		return;
	}
	else if ( method == 'POST' ) {

		if ( ! unique_id ) {
			unique_id = '0';
		}

		// redirect using POST so that conditional content rules are followed
		var redirect_form_el = new Element( 'form', { 
														'id'      :  'redirect' + unique_id + '_redirect_form',
														'name'    :  'component_' + unique_id + '_redirect_form',
														'method'  :  'POST'
													} );
													
		$(document.body).insert( redirect_form_el );

		/* Add a dummy hidden form element - this allows posting to URLs with http parameters */
		var post_value = new Element( 'input', { name: 'post_value_' + unique_id, type: 'hidden', value: '1' } );
		redirect_form_el.insert( post_value );

		redirect_form_el.action = url;
		redirect_form_el.submit( );
	}
		
}


Clockwork.MiniMessage  =  Class.create( );
Object.extend( Clockwork.MiniMessage.prototype, Clockwork.Message.prototype );
/* The MiniMessage merely sets mini_message: true by default */
Object.extend( Clockwork.MiniMessage.prototype, { options:  { mini_message: true } } );
		

/* Extend prototype.js's Ajax.Updater to handle XML envelope, messaging */
Clockwork.Ajax  =  { }

/* Extend Prototype's Ajax.Request class */
Clockwork.Ajax.Request  =  Class.create(Ajax.Request, {

	default_options:  
		{ 
			asynchronous  :  true,
			evalScripts   :  false,
			method        :  'post',
			contentType   :  'application/x-www-form-urlencoded',
			encoding      :  'UTF-8',
			evalJSON      :  true,
			evalJS        :  true
		},


	initialize: function ( $super, url, options ) {

		var effective_options  =  { };
		
		Object.extend( effective_options, this.default_options );

		Object.extend( effective_options, options || { } );

		/* It's possible we have no parameters! */
		effective_options.parameters  =  effective_options.parameters || { };

		/* Enforce object parameters for extending (to preserve ajax=1) */
		if ( Object.isString( effective_options.parameters ) ) {
			effective_options.parameters  =  effective_options.parameters.toQueryParams( );
		}

		/* Add the default ajax=1 */
		if ( ! effective_options.parameters.ajax ) {
			effective_options.parameters.ajax  =  1;
		}

		$super( url, effective_options );

		this.results  =  { };

		var onComplete  =  this.options.onComplete || Prototype.emptyFunction;
		
		this.options.onComplete  =  ( function ( transport, object ) {
			this.handleResponse( );
			onComplete( transport, object );
		} ).bind( this, this.transport, this );
	},

	handleResponse:  function ( $super ) {
		
		this.processResponse( );
		this.displayMessage( );
		this.setFieldStyles( this.results.fields );

	},


	processResponse:  function ( ) {
		
		if ( ! this.success( ) ) {
			Logger.log( 'Response not a success.', CW_LOG_DEBUG );
            this.results.error = 1;
            this.results.message = 'Request Failed';
            this.results.message_type = 'error';
			return;
		}

		if ( this.transport.status != 200 ) {
			Logger.log( 'Error - response status '+this.transport.status + ' ' + this.transport.statusText, CW_LOG_DEBUG );
            this.results.error = 1;
            this.results.message = 'Request Failed - Response status '+this.transport.status + ' ' + this.transport.statusText;
            this.results.message_type = 'error';
			return;
		}

		var content_type_full  =  this.getHeader('Content-type');
		var content_type       =  content_type_full.replace( /;.*$/, '' );
		
		switch( content_type ) {

		case 'text/xml':
			// if the response contains xml
			if ( this.transport.responseXML != null ) {

				this.results.output  =  this.getTagContent( 'output' );
				var message          =  this.getTag( 'message' );

				/* XML parse error, possibly caused by leading or trailing newline or whitespace in a php file */
				if ( this.transport.responseXML.firstChild.tagName == 'parsererror' ) {

					this.results.message       =  'Error parsing xml response';
					this.results.message_type  =  'error';
					return;
				}

				var message_type  =  $A(message.attributes).find( function ( attribute ) {
										 return attribute.nodeName == 'type';
									 } ).nodeValue;
				
				this.results.message       =  this.getTagContent( message );
				this.results.message_type  =  ( message_type ? message_type : '' );
				this.results.error         =  parseInt( this.getTagContent( 'error' ) );

			} else {
				
				this.results.error = 1;
				this.results.message = 'Error - null responseXML';
				this.results.message_type = 'error';
				return;

			}

			break;

		case 'application/json':
			
			// the 'application-json' content-type is as close as exists to a standard content-type for json:
			// ( see http://tech.groups.yahoo.com/group/json/message/337%3E )

			// prototype does auto-evaluation if the json is supplied in the X-JSON header, but html headers
			// are limited in size by some servers ( see http://www.ruby-forum.com/topic/94728 )
			// so we use application-json and do the evaluating explicitly.

			try {
				var jsondata  =  eval( '(' + this.transport.responseText + ')' );

				this.results.message          =  jsondata.message || '';
				this.results.message_type     =  jsondata.message_type;
				this.results.error            =  jsondata.error;
				this.results.extra_data       =  jsondata.extra_data;
				this.results.message_details  =  jsondata.extra_data.message_details;
				this.results.fields           =  jsondata.extra_data.fields;
				this.results.output           =  jsondata.output;
			} 
			catch (e) {
				this.results.error         =  1;
				this.results.message       =  'Error processing json response: '+e;
				this.results.message_type  =  'error';
				return;
			}
			
			// if error and extra_data is no_session tell the browser to reload the page to present the user with the login screen
			if ( this.results.error == 1 && this.results.extra_data == 'no_session' ) {
				top.location = '/';
			}

			break;


		default:
			this.results.error         =  1;
			this.results.message       =  'An unknown error occurred.';
			this.results.message_type  =  'error';

			Logger.log( 'Clockwork.Ajax: Unknown content-type: ' + content_type, CW_LOG_DEBUG );
			return;
		}
		
	},

	displayMessage:  function ( ) {

		if ( this.results.message == '' ) {
			return;
		}

		var message_target  =  ( this.options.message_target ? this.options.message_target : 'messagebox_target' );
		var mini_message    =  ( this.options.mini_message ? this.options.mini_message : false );
		var message_opts    =  { append: false,
								 message_target: message_target,
								 mini_message: mini_message,
								 message_details: this.results.message_details };

		new Clockwork.Message( this.results.message, this.results.message_type, message_opts );

	},

	setFieldStyles: function( fields, container_css ) {

		fields = fields || [];

		container_css = container_css || '';

		// error text
		for ( var i=0; i<fields.length; i++ ) {
			var field = fields[i];
			if ( field.style == 'error' ) {
				$$( container_css + ' #' + field.id ).each( function( i ) { i.addClassName( 'errortext' ); } );
			} else {
				$$( container_css + ' #' + field.id ).each( function( i ) { i.removeClassName( 'errortext' ); } );
			}

		}

	},


	getTag:  function ( tagName ) {
		var tags  =  this.transport.responseXML.getElementsByTagName( tagName );

		/* Ignore zero-length tags array or text nodes */
		if ( tags.nodeType && tags.nodeType == 1 ) {
			return null; 
		}

		return tags[0];
	},

	getTagContent:  function ( tag ) {

		if ( typeof tag == 'string' ) {
			tag  =  this.getTag( tag );
		}

		if ( tag ) {
			if ( tag.childNodes.length != 0 ) {
				return tag.childNodes[0].nodeValue;
			}
			else {
				return '';
			}
		}

		return null;

	}
	
} );
			
	
/* Start with Updater from prototype */
/* Layer in Clockwork-specific Request additions */
Clockwork.Ajax.Updater  =  Class.create( Clockwork.Ajax.Request, {

	initialize:  function ( $super, container, url, options ) {

		$super( url, options );

		this.containers  =  {
			success:  $( container ),
			failure:  null
		}

	},


	handleResponse:  function ( $super ) {
		this.processResponse( );
		this.updateContent( );
		this.displayMessage( );
		this.setFieldStyles( this.results.fields );
	 },
		

	updateContent:  function ( ) {
		
		if ( ! this.success( ) ) {
			return;
		}
		
		var receiver  =  this.containers.success;

		if ( ! receiver ) {
			return;
		}

		if ( this.results.error == 0 ) {
			var method  =  this.options.replace ? Element.replace : Element.update;
			method( receiver, this.results.output );
		}

	}

} );

Clockwork.Ajax.MultiUpdater  =  Class.create( Clockwork.Ajax.Updater, {

	initialize:  function ( $super, url, options ) {
		$super( null, url, options );
	},


	updateContent:  function ( ) {
		
		if ( ! this.success( ) ) {
			Logger.log( 'MultiUpdater: Not a successful response', CW_LOG_DEBUG );
			return;
		}
		
		if ( this.results.error != 0 ) {
			Logger.log( 'MultiUpdater: Nonzero error value', CW_LOG_DEBUG );
			return;
		}

		var method  =  this.options.replace ? Element.replace : Element.update;

		for ( var key in this.results.output ) {

			var receiver  =  $(key);

			Logger.log( 'MultiUpdater: Examining receiver: ' + key, CW_LOG_DEBUG );

			if ( ! receiver ) {
				Logger.log( 'MultiUpdater: Missing receiver: ' + key, CW_LOG_DEBUG );
				continue;
			}

			method( receiver, this.results.output[key] );
		}
	}
} );

/* Javascript implementation of Logger */

var Logger  =  { };

Logger.log_level  =  Clockwork.config.logger_level ? Clockwork.config.logger_level : CW_LOG_PRODUCTION_LEVEL;

Logger.log  =  function ( message, level ) {

	if ( ! level ) {
		level  =  CW_LOG_DEFAULT_LEVEL;
	}

	if ( Logger.log_level & level ) {
		var now  =  new Date( );
		message  =  '[' + now.toString( ) + '] ' + message;

		try { 
			console.log( message );
		}
		catch ( e ) {
			alert( message );
		}
	}
}

Logger.set_checkpoint  =  function ( message, level ) {
	var new_checkpoint  =  ( new Date( ) ).getTime( );
	if ( message ) {
		Logger.log( message + '[elapsed: ' + ( ( new_checkpoint - Logger.checkpoint ) / 1000.0 ) +
					'; cumulative: ' + ( ( new_checkpoint - Logger.first_checkpoint ) / 1000.0 ) + ']', level );
	}
	Logger.checkpoint  =  new_checkpoint;
}


/* firebug-specific log & info shortcuts, for debugging only */
function log(txt) {try {console.log(txt);} catch (e) {}}
function info(txt) {try {console.info(txt);} catch (e) {}}


Clockwork.Effect  =  { };

Clockwork.Effect.options   =  { duration: 0.75, fade_duration: 1.5, queue:'end' };

Clockwork.Effect.BlindDown  =  function ( element ) {
	Effect.BlindDown( element, this.options );
};

Clockwork.Effect.BlindUp  =  function ( element ) {
	Effect.BlindUp( element, this.options );
};

Clockwork.Effect.BlindToggle  =  function ( element ) {
	Effect.toggle( element, 'blind', this.options );
};

Clockwork.Effect.BlindControl =  function ( element, direction ) {
	if ( direction == 'down' ) {
		Effect.BlindDown ( element, this.options );
	}
	else {
		Effect.BlindUp ( element, this.options );
	}
};

Clockwork.SmartDate = function(args) {

// valid args:  include_time (boolean) - true by default.  If false, only MM-DD-YYYY dates will be shown (no times)
//				allow_blank (boolean) - false by default.  If true, will not attempt to parse blank dates 

	this.make_smart_date = function(args) {

		args.input_el  =  $( args.input_el );

		// Sanity check
		if ( ! args.input_el ) {
			return;
		}

		args.include_time    =  (args.include_time != null)   ? args.include_time   : true;  // include time by default
		args.preserve_blank  =  (args.preserve_blank != null) ? args.preserve_blank : false; // dont preserve blanks by default

		Event.observe(args.input_el, 'change', function(args, evt) {
			if ( args.preserve_blank && args.input_el.value == '' ) {
				return;
			}


			var d = Date.parse(args.input_el.value);

			if ( d != null ) {

				args.input_el.value = Clockwork.SmartDate.format_date(d, args.include_time, args.date_format);
			}

			if ( typeof args.callback != "undefined" ) {
				args.callback( );
			}


		}.bind(this,args));
	}

	if (args)
		this.make_smart_date(args);
};

Clockwork.SmartDate.format_date = function(d, include_time, date_format) {

		var use_format;

		include_time  =  (include_time != null) ? include_time : true; // true by default

		if ( date_format ) {
			use_format  =  date_format;
		}
		else if ( include_time ) {
			use_format  =  Clockwork.config.datetime_format;
		}
		else {
			use_format  =  Clockwork.config.date_format;
		}

		var dateString = null;

		if (!include_time) {

			dateString = d.toString( use_format );

		} else {

			dateString = d.toString( use_format ).replace( /AM/, 'am' ).replace( /PM/, 'pm' ); // php wants lowercase am-pm strings
		}

		return dateString;
	}


// valid args:  ignore_crap(boolean) - true by default.  If false, invalid dates in the input field will prevent the popup from displaying.  It will shake its little head and stubbornly refuse.
//				allow_blank (boolean) - false by default.  If true, will not attempt to parse blank dates 
Clockwork.PopupCalendar = function(args) {
	args.preserve_blank = (args.preserve_blank != null) ? args.preserve_blank : false; // dont preserve blanks by default

	this.add = function(args) {
		if (args.ignore_crap == null) args.ignore_crap = true;

		var input_el  =  $( args.input_el );

		// Sanity check
		if ( ! input_el ) {
			return;
		}

		// on init, parse the contents of the date fields
		var d = null;
		if  (args.date) {
			d = args.date;
		} else {
			// blank value -> today
			if ( input_el.value === '') {
				if ( !args.preserve_blank ) { 
					d = (0).seconds().fromNow();
					if ( input_el.disabled != true ) {
						input_el.value = Clockwork.SmartDate.format_date(d, args.include_time, args.date_format);
					}
				}
			} else { // non-blank date, attempt to parse
				d = Date.parse(input_el.value);
				if (d === null) { // parse failed, set calendar date to current date, but don't replace input
					d = (0).seconds().fromNow();
				} else {
					if ( input_el.disabled != true ) {
						input_el.value = Clockwork.SmartDate.format_date(d, args.include_time, args.date_format);
					}
				}
			}
		}

		var img_el = new Element('img',{'src':'/images/buttons/calendar.png','style':'margin-left:4px;vertical-align:middle;cursor:pointer;'});
		Element.insert(input_el,{after:img_el});

		Event.observe(img_el, 'click', this.show_calendar_popup.bind(this,args));

	}

	this.denial_shake = function(el) {
		new Effect.Shake(el,{ duration: .5,distance:5});
	}

	this.show_calendar_popup = function(args,evt) {
		if ( args.input_el.disabled == true ) {
			return;
		}

		var calendar = new Calendar();
		calendar.isPopup = true;
		var d = Date.parse(args.input_el.value);
		if (d == null) {
			if (args.ignore_crap == true ) {
				d = new Date(); // today
			} else {
				this.denial_shake(evt.element());
				return;
			}
		}

		calendar.setDate(d);

		calendar.setCloseHandler( Calendar.defaultCloseHandler );

		var select_handler  =  function ( args, cal ) {

			args.input_el.value = Clockwork.SmartDate.format_date(cal.d, args.include_time, args.date_format);

			if ( typeof args.callback != "undefined" ) {
				 args.callback(cal.date);
			}

		};

		calendar.setSelectHandler( select_handler.curry( args ) );

		calendar.showAtElement(evt.element());
	}
	 

	if (args)
		this.add(args);
};


/**
 * JS version of PHP escape_single_value( ); wraps Prototype's 
 * escapeHTML( ) extension to String. We also escape double
 * quotes with the &quot; entity until prototype is updated.
 * 
 * Lloyd's comment below:
 *
 * wrapper for prototype's escapeHTML that also escapes double quotes
 * this may be rendered redundant by a prototype upgrade
 * (see: http://github.com/jdalton/prototype/commit/08545904b874632aaba169e95887a34fe1f5bf2e )
 *
 * @author Lloyd Dalton <lloyd@clockwork.net>
 * 
 * @param   string  value  Value to return HTML-escaped.
 * @return  string         HTML-escaped string.
**/

Clockwork.escape_single_value  =  function ( value ) {
	return value.escapeHTML( ).replace( /"/g,'&quot;' );
}


/* Visual effects */
Object.extend( Element, {
	effect_opts: { duration: 0.75, fade_duration: 1.5 },

	slide_toggle: function ( element_id ) {
		Effect.toggle( element_id, 'slide', this.effect_opts );
	},

	blind_toggle: function ( element ) {
		Effect.toggle( element, 'blind', this.effect_opts );
	},


	fade_toggle: function ( element ) {
		var element  =  $( element );
		if ( ! element ) { return; }

		if ( element.style.display == 'none' ) {
			Effect.Fade( element, this.effect_opts );
		}
		else {
			Effect.Appear( element, this.effect_opts );
		}
	},

	blind_remove: function ( element_id, options ) {
		if ( ( element = $( element_id ) ) == null ) { return; }

		/* Uncomment for "inplace blind" */
		/*
		var div           =  document.createElement( 'div' );
		div.style.height  =  element.clientHeight + 'px';
		element  =  element.parentNode.replaceChild( div, element );
		div.appendChild( element );
		*/

		Effect.toggle( element, 'blind', Object.extend( options || { }, this.effect_opts ) );
		setTimeout( function ( ) { element && element.parentNode && Element.remove( element ) }, this.effect_opts.duration * 1000 + 50 );
	},

	fade_remove: function ( element, options ) {
		options  =  Object.extend( this.effect_opts, options || { } ); 
		Effect.Fade( element, options );
		setTimeout( function ( ) { element && element.parentNode && Element.remove( element ) }, ( options.fade_duration || options.duration ) * 1000 + 50 );
	}

} );

/**
 * Determines if an AJAX request is in progress, takes request.transport as the argument
 * Used in Ajax.Responders
*/
Clockwork.request_in_progress = function ( xmlhttp ) {
	
	switch ( xmlhttp.readyState ) {
		case 1:
		case 2: 
		case 3:
			return true;
			break;
		// Case 4 and 0
		default:
			return false;
			break;
	}
	
};

/* Simple tab switching
 * based on http://tetlaw.id.au/view/blog/fabtabulous-simple-tabs-using-prototype/
 *
 * @author Lloyd Dalton <lloyd@clockwork.net>
 */
Clockwork.Tabs  =  Class.create( );

Clockwork.Tabs.prototype  =  {
	initialize : function(element) {
		this.element = $(element);
		var options = Object.extend({}, arguments[1] || {});
		this.menu = $A(this.element.getElementsByTagName('a'));
		this.tabAreas = [];
		this.getTabAreas();
		this.menu.each(this.hide.bind(this));
		this.show(this.getInitialTab());
		this.menu.each(this.setupTab.bind(this));
	},
	setupTab : function(el) {
		Event.observe(el.parentNode,'click',this.activate.bindAsEventListener(this),false)
	},
	activate :  function(ev, li_el) {
		if ( ! li_el ) {
			li_el = Event.findElement(ev, "li");
		}

		var el = li_el.childElements().first();

		if  ( ev != null ) {
			Event.stop(ev);
		}

		this.menu.without(el).each(this.hide.bind(this));
		this.show(el);
	},
	hide : function(el) {
		$($(el).parentNode).removeClassName('active-tab');
		$(this.tabID(el)).removeClassName('active-tab-body');
		$(this.tabID(el)).hide();
	},
	show : function(el) {
		$($(el).parentNode).addClassName('active-tab');
		$(this.tabID(el)).addClassName('active-tab-body');
		$(this.tabID(el)).show();
	},
	tabID : function(el) {
		return el.href.match(/#(\w.+)/)[1];
	},
	getInitialTab : function() {
		if(document.location.href.match(/#(\w.+)/)) {
			var loc = RegExp.$1;
			var el = this.menu.find(function(value) { return value.href.match(/#(\w.+)/)[1] == loc; });
			return el || this.menu.first();
		} else {
			return this.menu.first();
		}
	},
	getTabAreas : function() {
		/*
		this.menu.each(function(el){
			this.tabAreas.push( this.tabID( el ) );
		});
		*/
	}
};



/* Spinner for "saving..." state indicators. 
 *
 * @author Lloyd Dalton <lloyd@clockwork.net>
 */
Clockwork.Spinner  =  Class.create( );

Clockwork.Spinner.prototype  =  {

	initialize : function(el, properties) {

		if ( properties == 'unspin' ) {
			this.unspin ( )
			return;
		}
		var text = properties.text || 'Spinning...';

		this.image = (properties['image']) ? properties['image'] : '/images/indicator.gif'; // spinner image

		if ( properties.append ) {
			this.spin_element = el.insert( new Element( 'span' ), {position : 'after'} ).down();
		}
		else {
			this.spin_element = el;
		}

		this.spin_element_contents = null;
		this.spin_contents = this.spin_element.innerHTML;
		this.spin_element.previous_contents = this.spin_contents;
		this.spin_element.innerHTML = '<img class="spinner" src="'+this.image+'">'+text;
	},

    unspin : function( ) {
		this.spin_element.innerHTML = '';
		this.spin_element.insert( this.spin_contents );
    }
}

Clockwork.Chart  =  {

	data: {},

	default_width: '100%',

	default_height: 175,

	flash_version: '9.0.0',

	ofc_swf_url:  '/shared/swf/open-flash-chart.swf',

	add:
	function ( id, args ) {

		if ( ! window.swfobject ) {
			Logger.log( 'Clockwork.Chart.add: swfobject not available!', CW_LOG_ERROR );
			return false;
		}

		var s  =  window.swfobject;

		args.width   =  args.width  || Clockwork.Chart.default_width;
		args.height  =  args.height || Clockwork.Chart.default_height;

		// Set up initial (empty) data set
		Clockwork.Chart.data[id]  =  {};

		// Set up the getter function
		var getter_fn  =  function ( ) {

			if ( ! Clockwork.Chart.data[id] ) {
				Logger.log( 'Clockwork.Chart: Getter for missing chart ID `' + id + '\' called', CW_LOG_ERROR );
				return '{}';
			}

			return Object.toJSON( Clockwork.Chart.data[id] );
		};

		// Add the getter function to window
		window['clockwork_get_chart_data_' + id]  =  getter_fn;

		// Set up the embed function
		var embed_fn  =  function ( ) {

			// Getter (unfortunately) must live in the window's scope
			var flashvars    =  { 'get-data': 'clockwork_get_chart_data_' + id,
								  'id'      : id };
			var params       =  { 'wmode': 'transparent' };
			var attributes   =  { };
			var install_url  =  null;

			s.embedSWF( Clockwork.Chart.ofc_swf_url, id, args.width, args.height,
						Clockwork.Chart.flash_version, install_url,
						flashvars, params, attributes );
		};

		// Add the embed function to the window's load event
		Event.observe( window, 'load', embed_fn );

		return true;
	},

	refresh:
	function ( id ) {

		var chart  =  $( id );

		if ( ! chart ) {
			Logger.log( 'Clockwork.Chart.refresh: Cannot find chart DIV ID `' + id + '\'', CW_LOG_ERROR );
			return false;
		}

		// Chart not ready yet
		if ( ! chart.load ) {
			Logger.log( 'Clockwork.Chart.refresh: Chart ID `' + id + '\' not yet loaded', CW_LOG_DEBUG );
			return false;
		}

		chart.load( Object.toJSON( Clockwork.Chart.data[id] ) );
	},

	update:
	function ( id, data ) {

		Clockwork.Chart.data[id]  =  data;
		Clockwork.Chart.refresh( id );

		return true;
	}
}

// ofc_ready is an Open Flash Chart hook called when the SWF has finished 
// loading
window.ofc_ready  =  function ( ) {

	for ( var chart_id in Clockwork.Chart.data ) {

		var chart  =  $( chart_id );

		if ( ! chart || ! chart.load ) {
			Logger.log( 'ofc_ready: Could not find activated chart id `' + chart_id + '\'', CW_LOG_ERROR );
			return;
		}

		// Hack to force a resize, fixes glitches with Y-axis display
		chart.style.width  =  '99%';
		chart.style.width  =  '100%';
	}
}

/** 
 * CharacterCount for "X characters remaining" indicators. 
 *
 * @author Lloyd Dalton <lloyd@clockwork.net>
**/
Clockwork.CharacterCount  =  Class.create( );

Clockwork.CharacterCount.prototype  =  {

	initialize : function( properties ) {

		this.max_chars   =  properties.max_chars | 140;
		this.input_el    =  $( properties.input_el );
		this.display_el  =  $( properties.display_el );

		// Sanity check
		if ( ! this.input_el || ! this.display_el ) {
			return;
		}

		Event.observe( this.input_el, 'keyup',  this.update_count.bindAsEventListener( this ) );
		Event.observe( this.input_el, 'change', this.update_count.bindAsEventListener( this ) );

		this.update_count( );
	},

	update_count : function( ) {

		// Sanity check
		if ( ! this.input_el || ! this.display_el ) {
			return;
		}

		this.display_el.update( this.max_chars - this.input_el.value.length );
	}

}



