YUI.add('poller', function(Y) {

/**
 * smart_polling - A utility to poll the server for data changes while being smart the XHR requests.
 * http://925html.com/code/smart-polling/
 * 
 * Copyright (c) 2009 Eric Ferraiuolo - http://eric.ferraiuolo.name
 * MIT License - http://www.opensource.org/licenses/mit-license.php
 */

/**
 * Create a poller to continually ask a server if a resource has been modified.
 * 
 * @module poller
 * @requires io-base, base
 */

var Poller,
	POLLER = 'poller',
	
	isObject = Y.Lang.isObject,
	isString = Y.Lang.isString,
	isNumber = Y.Lang.isNumber,
	isBoolean = Y.Lang.isBoolean,
	
	INTERVAL = 'interval',
	URL = 'url',
	QUERY_PARAMS = 'queryParams',
	HEADERS = 'headers',
	TIMEOUT = 'timeout',
	PAUSE_INACTIVE = 'pauseInactive',
	POLLING = 'polling',
	
	START = 'start',
	STOP = 'stop',
	REQUEST = 'request',
	RESPONSE = 'response',
	MODIFIED = 'modified';

/**
 * Create a polling task to continually check the server at the specified interval for updates of a resource at a URI.
 * The poller will use conditional GET requests and notifiy the client via Events when the resource has changed.
 * 
 * @class Poller
 * @extends Base
 * @param {Object} config Configuration Object
 * @constructor
 */
Poller = function (config) {
	
	Poller.superclass.constructor.apply( this, arguments );
	
};

Y.mix( Poller, {
	
	/**
	 * The identity of the component.
	 * 
	 * @property Poller.NAME
	 * @type String
	 * @static
	 */
	NAME : POLLER,
	
	/**
	 * Static property used to define the default attribute configuration of
	 * the component.
	 *
	 * @property Poller.ATTRS
	 * @type Object
	 * @static
	 */
	ATTRS : {
		
		/**
		 * The time in milliseconds for which the component should send a request to the server.
		 * 
		 * @attribute interval
		 * @type Number
		 * @default 10000
		 */
		interval : {
			value : 10000,
			validator : isNumber
		},
		
		/**
		 * The URL of the resource which the component will check for modifications.
		 * 
		 * @attribute url
		 * @type String
		 * @default null
		 */
		url : {
			value : null,
			validator : isString
		},
		
		/**
		 * A set of name=value pairs to be appened to the URL requests will be sent to.
		 * 
		 * @attribute queryParams
		 * @type String
		 * @default null
		 */
		queryParams : {
			value : null,
			validator : isString
		},
		
		/**
		 * An Object containing the key : value pairs for any HTTP headers to be send with requests.
		 * 
		 * @attribute headers
		 * @type Object
		 * @default null
		 */
		headers : {
			value : null,
			validator : isObject
		},
		
		/**
		 * The time in milliseconds which the XHR request should abort if no response is received.
		 * 
		 * @attribute timeout
		 * @type Number
		 * @default 5000
		 */
		timeout : {
			value : 5000,
			set : function (v) {
				var interval = this.get(INTERVAL);
				return (v <= interval) ? v : interval;
			},
			validator : isNumber
		},
		
		/**
		 * Choice for the polling to be paused if the browser window loses focus, becoming an inactive window.
		 * 
		 * @attribute pauseInactive
		 * @type Boolean
		 * @default false
		 */
		pauseInactive : {
			value : false,
			validator : isBoolean
		},
		
		/**
		 * A read-only attribute the client can check to see if the component is activly polling the server.
		 * 
		 * @attribute polling
		 * @type Boolean
		 * @default false
		 * @final
		 */
		polling : {
			value : false,
			readOnly : true
		}
		
	}
	
});

Y.extend( Poller, Y.Base, {
	
	/**
	 * Reference to the timer object that's polling the server.
	 * 
	 * @property _poller
	 * @type Y.later
	 * @protected
	 */
	_poller : null,
	
	/**
	 * Last-Modified date of the resource that the server returned, used to determine if the resource has changed.
	 * 
	 * @property _modifiedDate
	 * @type String
	 * @protected
	 */
	_modifiedDate : null,
	
	/**
	 * Etag of the resource returned by the server, used to determine if the resource has changed.
	 * 
	 * @property _etag
	 * @type String
	 * @protected
	 */
	_etag : null,
	
	/**
	 * Reference to the timer object which is waiting to resume from a pause request.
	 * 
	 * @property _paused
	 * @type Y.later
	 * @protected
	 */
	_paused : false,
	
	/**
	 * Event handle referencing the binding to the window's focus event.
	 * 
	 * @property _focusHandle
	 * @type Event.Handle
	 * @protected
	 */
	_focusHandle : null,
	
	/**
	 * Event handle referencing the binding to the window's blur event.
	 * 
	 * @property _blurHandle
	 * @type Event.Handle
	 * @protected
	 */
	_blurHandle : null,
	
	/**
	 * Construction of the component linking up and publishing event during initialization.
	 * 
	 * @method initializer
	 * @param {Object} config Configuration Ojbect
	 * @protected
	 */
	initializer : function (config) {
		this.after( 'intervalChange', this._afterIntervalChange );
		this.after( 'urlChange', this._afterUrlChange );
		this.after( 'headersChange', this._afterHeadersChange );
		this.after( 'pauseInactiveChange', this._afterPauseInactiveChange );
		
		/**
		 * Signals that polling has started, poller:start.
		 * 
		 * @event start
		 * @param {Event.Facade} e Event Facade
		 */
		this.publish(START);
		
		/**
		 * Signals that polling has stopped, poller:stop.
		 * 
		 * @event stop
		 * @param {Event.Facade} e Event Facade
		 */
		this.publish(STOP);
		
		/**
		 * Signals that the component has sent a XHR request to the Server.
		 * The request object is passed to subscribers of the event, poller:request.
		 * 
		 * @event request
		 * @param {Event.Facade} e Event Facade
		 * @param {Object} tx Y.io Request Object
		 */
		this.publish(REQUEST);
		
		/**
		 * Signals that the component has received a response (io:complete) for the server, poller:response.
		 * 
		 * @event response
		 * @param {Event.Facade} e Event Facade
		 * @param {Number} txId Y.io Transaction ID
		 * @param {Object} r Y.io Response Object
		 * @param {MIXED} args Arguments passed to response handler
		 */
		this.publish(RESPONSE);
		
		/**
		 * Signals that the resource the component is pulling the server for has been modified.
		 * This is the interesting event for the client ot subscribe to.
		 * The subscriber could, for example, update the UI in response to this event, poller:modified.
		 * 
		 * @event modified
		 * @param {Event.Facade} e Event Facade
		 * @param {Number} txId Y.io Transaction ID
		 * @param {Object} r Y.io Response Object
		 * @param {MIXED} args Arguments passed to response handler
		 */
		this.publish(MODIFIED);
		
		if ( this.get(PAUSE_INACTIVE) ) {
			this._enablePauseInactive();
		}
	},
	
	/**
	 * Deconstruction of the component. Stops polling and removes event internal listeners.
	 * 
	 * @method destructor
	 * @protected
	 */
	destructor : function () {
		this.stop();
		this._disablePauseInactive();
	},
	
	/**
	 * Starts the polling task, the poller:start event is fired as a result of calling this method.
	 * A request will be sent to the server right at the time of calling this method;
	 * continued by sending subsequent requests at the set interval.
	 * If the pause method has been called, calling start will clear the pause.
	 * 
	 * @method start
	 * @chainable
	 */
	start : function () {
		this._clearPause();
		this._startPolling();
		this._set( POLLING, true );
		return this;
	},
	
	/**
	 * Stops the polling task, the poller:stop event is fired.
	 * If the paused method has been called, calling start will clear the pause.
	 * 
	 * @method stop
	 * @chainable
	 */
	stop : function () {
		this._clearPause();
		this._stopPolling();
		this._set( POLLING, false );
		return this;
	},
	
	/**
	 * Pauses the polling task for a duration.
	 * This method first calls stop, which will fire the poller:stop event;
	 * which will also clear out a waiting pause to resume.
	 * 
	 * @method pause
	 * @param {Number} duration milliseconds until resuming
	 * @chainable
	 */
	pause : function (duration) {
		this.stop();
		
		if ( isNumber(duration) ) {
			this._paused = Y.later( duration, this, this.start );
		}
		
		return this;
	},
	
	/**
	 * Protected method that actually starts the polling task.
	 * Calling this method will send a request to the server, fire the poller:start event, and create the interval task.
	 * 
	 * @method _startPolling
	 * @protected
	 */
	_startPolling : function () {
		if ( ! this._poller ) {
			this.fire(START);
			this.sendRequest();
			this._poller = Y.later( this.get(INTERVAL), this, this.sendRequest, null, true );
		}
	},
	
	/**
	 * Protected method that actually stops the polling task.
	 * Calling this method will stop the poller and fire the poller:stop event.
	 * 
	 * @method _stopPolling
	 * @protected
	 */
	_stopPolling : function () {
		if ( this._poller ) {
			this._poller.cancel();
			this._poller = null;
			this.fire(STOP);
		}
	},
	
	/**
	 * Utility method used to clear a pause timer that has been set.
	 * 
	 * @method _clearPause
	 * @protected
	 */
	_clearPause : function () {
		if ( this._paused ) {
			this._paused.cancel();
			this._paused = false;
		}
	},
	
	/**
	 * Sends the XHR request to the server at the given URL (resource).
	 * This is method is call at the set interval while polling.
	 * Calling this method will fire the poller:request event.
	 * 
	 * @method sendRequest
	 * @chainable
	 */
	sendRequest : function () {
		var config, headers, tx;
		
		headers = this.get(HEADERS);
		if ( this._etag ) {
			headers = Y.merge( headers, { 'If-None-Match':this._etag });
		} else if ( this._modifiedDate ) {
			headers = Y.merge( headers, { 'If-Modified-Since':this._modifiedDate });
		}
		
		config = {
			
			method : 'GET',
			data : this.get(QUERY_PARAMS),
			headers : headers,
			on : {
				complete : this._handleResponse,
				success : this._handleModified
			},
			context : this,
			timeout : this.get(TIMEOUT)
			
		};
		
		tx = Y.io( this.get(URL), config );
		this.fire( REQUEST, tx );
		return this;
	},
	
	/**
	 * Handles the response from a completed XHR request.
	 * Fires the poller:response event.
	 * 
	 * @method _handleResponse
	 * @param {Number} txId Y.io Transaction ID
	 * @param {Object} r Y.io Response Object
	 * @param {MIXED} args Arguments passed to response handler
	 * @protected
	 */
	_handleResponse : function ( txId, r, args ) {
		this.fire( RESPONSE, txId, r, args );
	},
	
	/**
	 * Handles the response from a successful XHR request (2xx response status).
	 * The resource has changed on the server if this method has been called.
	 * Fires the poller:modified event.
	 * 
	 * @method _handleModified
	 * @param {Number} txId Y.io Transaction ID
	 * @param {Object} r Y.io Response Object
	 * @param {MIXED} args Arguments passed to response handler
	 * @protected
	 */
	_handleModified : function ( txId, r, args ) {
		this._etag = r.getResponseHeader('Etag');
		this._modifiedDate = r.getResponseHeader('Last-Modified');
		this.fire( MODIFIED, txId, r, args );
	},
	
	/**
	 * Attaches the event handles to the focus and blur events on the window.
	 * Provides a way to automatically pause polling when the browser window is inactive,
	 * and starts the polling process right when the windows becomes active again.
	 * 
	 * @method _enablePauseInactive
	 * @protected
	 */
	_enablePauseInactive : function () { // TODO: Make more robust across browsers
		this._focusHandle = Y.on( 'focus', this._handleFocus, window, this );
		this._blurHandle = Y.on( 'blur', this._handleBlur, window, this );
	},
	
	/**
	 * Detacheds the event handles from the window's focus and blur events.
	 * Preventing the polling to pause when the window is inactive, polling will continue until stop or pause is called.
	 * 
	 * @method _disablePauseInactive
	 * @protected
	 */
	_disablePauseInactive : function () {
		this._focusHandle.detach();
		this._blurHandle.detach();
	},
	
	/**
	 * Handles the window coming into focus.
	 * Checks that the component is activly polling (stop hasn't been called), and resumes the polling task.
	 * 
	 * @method _handleFocus
	 * @param {Event} e Window focus event
	 * @protected
	 */
	_handleFocus : function (e) {
		if ( this.get(POLLING) && Y.Node.getDOMNode(e.target) === document ) {
			this._startPolling();
		}
	},
	
	/**
	 * Handles the window losing focus, blur.
	 * Checks that the component is activly polling (stop hasn't been called), and pauses the polling task.
	 * 
	 * @method _handleBlur
	 * @param {Object} e
	 * @protected
	 */
	_handleBlur : function (e) {
		if ( this.get(POLLING) && Y.Node.getDOMNode(e.target) === document ) {
			this._stopPolling();
		}
	},
	
	/**
	 * If the polling task is active, it will be stopped then started to use the new interval.
	 * 
	 * @method _afterIntervalChange
	 * @param {Event} e intervalChange custom event
	 * @protected
	 */
	_afterIntervalChange : function (e) {
		if ( this.get(POLLING) ) {
			this._stopPolling();
			this._startPolling();
		}
	},
	
	/**
	 * Invalidate the cached etag and modifiedDate used to determine if the resource has been changed.
	 * If the polling task is active then send a request to the server right away.
	 * 
	 * @method _afterUrlChange
	 * @param {Event} e urlChange custom event
	 * @protected
	 */
	_afterUrlChange : function (e) {
		this._etag = null;
		this._modifiedDate = null;
		if ( this.get(POLLING) ) {
			this.sendRequest();
		}
	},
	
	/**
	 * Invalidate the cached etag and modifiedDate used to determine if the resource has been changed.
	 * Changing the HTTP headers can effect the response returned by the server.
	 * Changing this attributed is treated like the changing the url attribute.
	 * 
	 * @method _afterHeadersChange
	 * @param {Event} e headersChange custom event
	 * @protected
	 */
	_afterHeadersChange : function (e) {
		this._etag = null;
		this._modifiedDate = null;
		if ( this.get(POLLING) ) {
			this.sendRequest();
		}
	},
	
	/**
	 * Enable/disable the pausing/resuming of the polling task when the window has become inactive/active.
	 * 
	 * @method _afterPauseInactiveChange
	 * @param {Event} e pauseInactiveChange custom event
	 * @protected
	 */
	_afterPauseInactiveChange : function (e) {
		if ( e.newVal === true ) {
			this._enablePauseInactive();
		} else {
			this._disablePauseInactive();
		}
	}
	
});

Y.Poller = Poller;


}, '@VERSION@' ,{requires:['io-base','base']});
