    /**
     * @author Ed Spencer
     * @class Ext.data.proxy.Server
     * @extends Ext.data.proxy.Proxy
     *
     * <p>ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy},
     * and would not usually be used directly.</p>
     *
     * <p>ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
     * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now an
     * alias of AjaxProxy).</p>
     */
    Ext.define('Ext.data.proxy.Server', {
        extend: 'Ext.data.proxy.Proxy',
        alias : 'proxy.server',
        alternateClassName: 'Ext.data.ServerProxy',
        uses  : ['Ext.data.Request'],

        /**
         * @cfg {String} url The URL from which to request the data object.
         */

        /**
         * @cfg {Object/String/Ext.data.reader.Reader} reader The Ext.data.reader.Reader to use to decode the server's response. This can
         * either be a Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
         */

        /**
         * @cfg {Object/String/Ext.data.writer.Writer} writer The Ext.data.writer.Writer to use to encode any request sent to the server.
         * This can either be a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
         */

        /**
         * @cfg {String} pageParam The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to
         * undefined if you don't want to send a page parameter
         */
        pageParam: 'page',

        /**
         * @cfg {String} startParam The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this
         * to undefined if you don't want to send a start parameter
         */
        startParam: 'start',

        /**
         * @cfg {String} limitParam The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this
         * to undefined if you don't want to send a limit parameter
         */
        limitParam: 'limit',

        /**
         * @cfg {String} groupParam The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this
         * to undefined if you don't want to send a group parameter
         */
        groupParam: 'group',

        /**
         * @cfg {String} sortParam The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this
         * to undefined if you don't want to send a sort parameter
         */
        sortParam: 'sort',

        /**
         * @cfg {String} filterParam The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set
         * this to undefined if you don't want to send a filter parameter
         */
        filterParam: 'filter',

        /**
         * @cfg {String} directionParam The name of the direction parameter to send in a request. <strong>This is only used when simpleSortMode is set to true.</strong>
         * Defaults to 'dir'.
         */
        directionParam: 'dir',

        /**
         * @cfg {Boolean} simpleSortMode Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a remote sort is requested.
         * The directionParam and sortParam will be sent with the property name and either 'ASC' or 'DESC'
         */
        simpleSortMode: false,

        /**
         * @cfg {Boolean} noCache (optional) Defaults to true. Disable caching by adding a unique parameter
         * name to the request.
         */
        noCache : true,

        /**
         * @cfg {String} cacheString The name of the cache param added to the url when using noCache (defaults to "_dc")
         */
        cacheString: "_dc",

        /**
         * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
         */
        timeout : 30000,

        /**
         * @cfg {Object} api
         * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
         * Defaults to:<pre><code>
    api: {
        read    : undefined,
        create  : undefined,
        update  : undefined,
        destroy : undefined
    }
         * </code></pre>
         * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
         * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
         * configured {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.</p><br>
         * <p>For example:</p>
         * <pre><code>
    api: {
        load :    '/controller/load',
        create :  '/controller/new',
        save :    '/controller/update',
        destroy : '/controller/destroy_action'
    }
         * </code></pre>
         * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
         * will be directed to the configured <tt>{@link Ext.data.proxy.Server#url url}</tt>.</p>
         */

        /**
         * @ignore
         */
        constructor: function(config) {
            var me = this;

            config = config || {};
            this.addEvents(
                /**
                 * @event exception
                 * Fires when the server returns an exception
                 * @param {Ext.data.proxy.Proxy} this
                 * @param {Object} response The response from the AJAX request
                 * @param {Ext.data.Operation} operation The operation that triggered request
                 */
                'exception'
            );
            me.callParent([config]);

            /**
             * @cfg {Object} extraParams Extra parameters that will be included on every request. Individual requests with params
             * of the same name will override these params when they are in conflict.
             */
            me.extraParams = config.extraParams || {};

            me.api = config.api || {};

            //backwards compatibility, will be deprecated in 5.0
            me.nocache = me.noCache;
        },

        //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
        create: function() {
            return this.doRequest.apply(this, arguments);
        },

        read: function() {
            return this.doRequest.apply(this, arguments);
        },

        update: function() {
            return this.doRequest.apply(this, arguments);
        },

        destroy: function() {
            return this.doRequest.apply(this, arguments);
        },

        /**
         * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
         * that this Proxy is attached to.
         * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
         * @return {Ext.data.Request} The request object
         */
        buildRequest: function(operation) {
            var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
                request;

            //copy any sorters, filters etc into the params so they can be sent over the wire
            params = Ext.applyIf(params, this.getParams(params, operation));

            if (operation.id && !params.id) {
                params.id = operation.id;
            }

            request = Ext.create('Ext.data.Request', {
                params   : params,
                action   : operation.action,
                records  : operation.records,
                operation: operation,
                url      : operation.url
            });

            request.url = this.buildUrl(request);

            /*
             * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
             * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
             */
            operation.request = request;

            return request;
        },

        /**
         *
         */
        processResponse: function(success, operation, request, response, callback, scope){
            var me = this,
                reader,
                result,
                records,
                length,
                mc,
                record,
                i;

            if (success === true) {
                reader = me.getReader();
                result = reader.read(me.extractResponseData(response));
                records = result.records;
                length = records.length;

                if (result.success !== false) {
                    mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
                    mc.addAll(operation.records);
                    for (i = 0; i < length; i++) {
                        record = mc.get(records[i].getId());

                        if (record) {
                            record.beginEdit();
                            record.set(record.data);
                            record.endEdit(true);
                        }
                    }

                    //see comment in buildRequest for why we include the response object here
                    Ext.apply(operation, {
                        response: response,
                        resultSet: result
                    });

                    operation.setCompleted();
                    operation.setSuccessful();
                } else {
                    operation.setException(result.message);
                    me.fireEvent('exception', this, response, operation);
                }
            } else {
                me.setException(operation, response);
                me.fireEvent('exception', this, response, operation);
            }

            //this callback is the one that was passed to the 'read' or 'write' function above
            if (typeof callback == 'function') {
                callback.call(scope || me, operation);
            }

            me.afterRequest(request, success);
        },

        /**
         * Sets up an exception on the operation
         * @private
         * @param {Ext.data.Operation} operation The operation
         * @param {Object} response The response
         */
        setException: function(operation, response){
            operation.setException({
                status: response.status,
                statusText: response.statusText
            });
        },

        /**
         * Template method to allow subclasses to specify how to get the response for the reader.
         * @private
         * @param {Object} response The server response
         * @return {Mixed} The response data to be used by the reader
         */
        extractResponseData: function(response){
            return response;
        },

        /**
         * Encode any values being sent to the server. Can be overridden in subclasses.
         * @private
         * @param {Array} An array of sorters/filters.
         * @return {Mixed} The encoded value
         */
        applyEncoding: function(value){
            return Ext.encode(value);
        },

        /**
         * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
         * this simply JSON-encodes the sorter data
         * @param {Array} sorters The array of {@link Ext.util.Sorter Sorter} objects
         * @return {String} The encoded sorters
         */
        encodeSorters: function(sorters) {
            var min = [],
                length = sorters.length,
                i = 0;

            for (; i < length; i++) {
                min[i] = {
                    property : sorters[i].property,
                    direction: sorters[i].direction
                };
            }
            return this.applyEncoding(min);

        },

        /**
         * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
         * this simply JSON-encodes the filter data
         * @param {Array} sorters The array of {@link Ext.util.Filter Filter} objects
         * @return {String} The encoded filters
         */
        encodeFilters: function(filters) {
            var min = [],
                length = filters.length,
                i = 0;

            for (; i < length; i++) {
                min[i] = {
                    property: filters[i].property,
                    value   : filters[i].value
                };
            }
            return this.applyEncoding(min);
        },

        /**
         * @private
         * Copy any sorters, filters etc into the params so they can be sent over the wire
         */
        getParams: function(params, operation) {
            params = params || {};

            var me             = this,
                isDef          = Ext.isDefined,
                groupers       = operation.groupers,
                sorters        = operation.sorters,
                filters        = operation.filters,
                page           = operation.page,
                start          = operation.start,
                limit          = operation.limit,

                simpleSortMode = me.simpleSortMode,

                pageParam      = me.pageParam,
                startParam     = me.startParam,
                limitParam     = me.limitParam,
                groupParam     = me.groupParam,
                sortParam      = me.sortParam,
                filterParam    = me.filterParam,
                directionParam       = me.directionParam;

            if (pageParam && isDef(page)) {
                params[pageParam] = page;
            }

            if (startParam && isDef(start)) {
                params[startParam] = start;
            }

            if (limitParam && isDef(limit)) {
                params[limitParam] = limit;
            }

            if (groupParam && groupers && groupers.length > 0) {
                // Grouper is a subclass of sorter, so we can just use the sorter method
                params[groupParam] = me.encodeSorters(groupers);
            }

            if (sortParam && sorters && sorters.length > 0) {
                if (simpleSortMode) {
                    params[sortParam] = sorters[0].property;
                    params[directionParam] = sorters[0].direction;
                } else {
                    params[sortParam] = me.encodeSorters(sorters);
                }

            }

            if (filterParam && filters && filters.length > 0) {
                params[filterParam] = me.encodeFilters(filters);
            }

            return params;
        },

        /**
         * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will
         * add the cache-buster param to the end of the url. Subclasses may need to perform additional modifications
         * to the url.
         * @param {Ext.data.Request} request The request object
         * @return {String} The url
         */
        buildUrl: function(request) {
            var me = this,
                url = me.getUrl(request);

            //<debug>
            if (!url) {
                Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
            }
            //</debug>

            if (me.noCache) {
                url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
            }

            return url;
        },

        /**
         * Get the url for the request taking into account the order of priority,
         * - The request
         * - The api
         * - The url
         * @private
         * @param {Ext.data.Request} request The request
         * @return {String} The url
         */
        getUrl: function(request){
            return request.url || this.api[request.action] || this.url;
        },

        /**
         * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all pass
         * through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link Ext.data.proxy.JsonP}
         * and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as each of the methods that delegate to it.
         * @param {Ext.data.Operation} operation The Ext.data.Operation object
         * @param {Function} callback The callback function to call when the Operation has completed
         * @param {Object} scope The scope in which to execute the callback
         */
        doRequest: function(operation, callback, scope) {
            //<debug>
            Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
            //</debug>
        },

        /**
         * Optional callback function which can be used to clean up after a request has been completed.
         * @param {Ext.data.Request} request The Request object
         * @param {Boolean} success True if the request was successful
         */
        afterRequest: Ext.emptyFn,

        onDestroy: function() {
            Ext.destroy(this.reader, this.writer);
        }
    });