/**
 * Ext.direct.Provider is an abstract class meant to be extended.
 *
 * For example Ext JS implements the following subclasses:
 *
 *     Provider
 *     |
 *     +---JsonProvider
 *         |
 *         +---PollingProvider
 *         |
 *         +---RemotingProvider
 *
 * @abstract
 */
Ext.define('Ext.direct.Provider', {
   alias: 'direct.provider',

    mixins: [
        'Ext.mixin.Observable'
    ],
    
    requires: [
        'Ext.direct.Manager'
    ],
    
    isProvider: true,
    $configPrefixed: false,
    $configStrict: false,

   /**
     * @cfg {String} id
     * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
     * You should assign an id if you need to be able to access the provider later and you do
     * not have an object reference available, for example:
     *
     *      Ext.direct.Manager.addProvider({
     *          type: 'polling',
     *          url:  'php/poll.php',
     *          id:   'poll-provider'
     *      });
     *      var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
     *     p.disconnect();
     *
     */
    
    /**
     * @cfg {String[]} relayedEvents
     * List of Provider events that should be relayed by {@link Ext.direct.Manager}.
     * 'data' event is always relayed.
     */
    
    config: {
        /**
         * @cfg {Object} [headers]
         * An object containing default headers for every Ajax request made by this Provider.
         */
        headers: undefined
    },
    
    /**
     * @event connect
     * Fires when the Provider connects to the server-side
     *
     * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
     */

    /**
     * @event disconnect
     * Fires when the Provider disconnects from the server-side
     *
     * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
     */

    /**
     * @event data
     * Fires when the Provider receives data from the server-side. This event is fired
     * for valid responses as well as for exceptions.
     *
     * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider} instance.
     * @param {Ext.direct.Event} e The {@link Ext.direct.Event} that occurred.
     */

    /**
     * @event exception
     * Fires when the Provider receives an exception from the server-side. This event is *not*
     * fired for valid responses.
     *
     * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider} instance.
     * @param {Ext.direct.Event} e The {@link Ext.direct.Event Exception event} that occured.
     */
    
    subscribers: 0,

    constructor: function(config) {
        var me = this;
        
        me.mixins.observable.constructor.call(me, config);

        me.requests = {};

        Ext.applyIf(me, {
            id: Ext.id(null, 'provider-')
        });
    },
    
    destroy: function() {
        var me = this;
        
        me.disconnect(true);
        me.callParent();
    },
    
    /**
     * Returns whether or not the server-side is currently connected.
     */
    isConnected: function() {
        return this.subscribers > 0;
    },

    /**
     * Connect the provider and start its service.
     * Provider will fire `connect` event upon successful connection.
     */
    connect: function() {
        var me = this;
        
        if (me.subscribers === 0) {
            me.doConnect();
            me.fireEventArgs('connect', [me]);
        }
        
        me.subscribers++;
    },
    
    /**
     * @method
     *
     * Do connection setup. This is a template method.
     * @template
     * @protected
     */
    doConnect: Ext.emptyFn,

    /**
     * Disconnect the provider and stop its service.
     * Provider will fire `disconnect` event upon successful disconnect.
     */
    disconnect: function(/* */ force) {
        var me = this;
        
        if (me.subscribers > 0 || force) {
            if (force) {
                me.subscribers = 0;
            }
            else {
                me.subscribers--;
            }
            
            if (me.subscribers === 0) {
                me.doDisconnect();
                me.fireEventArgs('disconnect', [me]);
            }
        }
    },
    
    /**
     * @method
     *
     * Do connection teardown. This is a template method.
     * @template
     * @protected
     */
    doDisconnect: function() {
        var requests = this.requests,
            request, id;
        
        for (id in requests) {
            request = requests[id];
            request.abort();
        }
        
        this.requests = {};
    },
    
    /**
     * Send the Ajax request
     *
     * @param {Object} Ajax request parameters
     *
     * @private
     */
    sendAjaxRequest: function(params) {
        var request = Ext.Ajax.request(params);
        
        if (request && request.id) {
            this.requests[request.id] = request;
        }
            
        return request;
    },
    
    /**
     * Ajax request callback
     *
     * @private
     */
    onData: function(options, success, response) {
        if (response && response.request) {
            delete this.requests[response.request.id];
        }
    },
    
    inheritableStatics: {
        /**
         * @method
         *
         * Check if the passed configuration object contains enough
         * information to construct a Provider.
         *
         * @param {Object} config
         *
         * @return {Boolean} `true` if config is sufficient, `false` otherwise.
         * @static
         * @inheritable
         */
        checkConfig: Ext.returnFalse
    },
    
    onClassExtended: function(cls, data, hooks) {
        if (data.type) {
            Ext.direct.Manager.addProviderClass(data.type, cls);
        }
    }
});
