/**
 * The JsonP proxy is useful when you need to load data from a domain other than the one your
 * application is running on. If your application is running on http://domainA.com it cannot use
 * {@link Ext.data.proxy.Ajax Ajax} to load its data from http://domainB.com because cross-domain
 * Ajax requests are prohibited by the browser.
 *
 * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM
 * whenever an AJAX request would usually be made. Let's say we want to load data from
 * http://domainB.com/users - the script tag that would be injected might look like this:
 *
 *     <script src="http://domainB.com/users?callback=someCallback"></script>
 *
 * When we inject the tag above, the browser makes a request to that url and includes the response
 * as if it was any other type of JavaScript include. By passing a callback in the url above,
 * we're telling domainB's server that we want to be notified when the result comes in and that it
 * should call our callback function with the data it sends back. So long as the server formats
 * the response to look like this, everything will work:
 *
 *     someCallback({
 *         users: [
 *             {
 *                 id: 1,
 *                 name: "Ed Spencer",
 *                 email: "[email protected]"
 *             }
 *         ]
 *     });
 *
 * As soon as the script finishes loading, the 'someCallback' function that we passed in the url
 * is called with the JSON object that the server returned.
 *
 * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding
 * the callback parameter automatically. It even creates a temporary callback function, waits for it
 * to be called and then puts the data into the Proxy making it look just like you loaded it
 * through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how we might set that up:
 *
 *     Ext.define('User', {
 *         extend: 'Ext.data.Model',
 *         fields: ['id', 'name', 'email']
 *     });
 *
 *     var store = Ext.create('Ext.data.Store', {
 *         model: 'User',
 *         proxy: {
 *             type: 'jsonp',
 *             url: 'http://domainB.com/users'
 *         }
 *     });
 *
 *     store.load();
 *
 * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have
 * injected a script tag like this:
 *
 *     <script src="http://domainB.com/users?callback=callback1"></script>
 *
 * # Customization
 *
 * This script tag can be customized using the {@link #callbackKey} configuration. For example:
 *
 *     var store = Ext.create('Ext.data.Store', {
 *         model: 'User',
 *         proxy: {
 *             type: 'jsonp',
 *             url : 'http://domainB.com/users',
 *             callbackKey: 'theCallbackFunction'
 *         }
 *     });
 *
 *     store.load();
 *
 * Would inject a script tag like this:
 *
 *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
 *
 * # Implementing on the server side
 *
 * The remote server side needs to be configured to return data in this format. Here are suggestions
 * for how you might achieve this using Java, PHP and ASP.net:
 *
 * Java:
 *
 *     boolean jsonP = false;
 *     String cb = request.getParameter("callback");
 *     if (cb != null) {
 *         jsonP = true;
 *         response.setContentType("text/javascript");
 *     } else {
 *         response.setContentType("application/x-json");
 *     }
 *     Writer out = response.getWriter();
 *     if (jsonP) {
 *         out.write(cb + "(");
 *     }
 *     out.print(dataBlock.toJsonString());
 *     if (jsonP) {
 *         out.write(");");
 *     }
 *
 * PHP:
 *
 *     $callback = $_REQUEST['callback'];
 *
 *     // Create the output object.
 *     $output = array('a' => 'Apple', 'b' => 'Banana');
 *
 *     //start output
 *     if ($callback) {
 *         header('Content-Type: text/javascript');
 *         echo $callback . '(' . json_encode($output) . ');';
 *     } else {
 *         header('Content-Type: application/x-json');
 *         echo json_encode($output);
 *     }
 *
 * ASP.net:
 *
 *     String jsonString = "{"success": true}";
 *     String cb = Request.Params.Get("callback");
 *     String responseString = "";
 *     if (!String.IsNullOrEmpty(cb)) {
 *         responseString = cb + "(" + jsonString + ")";
 *     } else {
 *         responseString = jsonString;
 *     }
 *     Response.Write(responseString);
 */
Ext.define('Ext.data.proxy.JsonP', {
    extend: 'Ext.data.proxy.Server',
    alternateClassName: 'Ext.data.ScriptTagProxy',
    alias: ['proxy.jsonp', 'proxy.scripttag'],
    requires: ['Ext.data.JsonP'],
 
    config: {
        /**
         * @cfg {String} callbackKey
         * See {@link Ext.data.JsonP#callbackKey}.
         */
        callbackKey: 'callback',
 
        /**
        * @cfg {String} [recordParam]
        * The HTTP parameter name to use when passing records to the server and the
        * {@link #writer Json writer} is not configured to
        * {@link Ext.data.writer.Json#encode encode} records into a parameter.
        * 
        * The {@link #encodeRecords} method is used to encode the records to create this parameter's
        * value.
        */
        recordParam: 'records',
 
        /**
        * @cfg {Boolean} autoAppendParams
        * True to automatically append the request's params to the generated url. Defaults to true
        */
        autoAppendParams: true
    },
 
    /**
     * @private
     * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax
     * request, instead we write out a `<script>` tag based on the configuration of the internal
     * Ext.data.Request object
     * @param {Ext.data.operation.Operation} operation The
     * {@link Ext.data.operation.Operation Operation} object to execute
     * @param {Function} operation.callback A callback function to execute when the Operation has
     * been completed
     * @param {Object} operation.scope The scope to execute the callback in
     */
    doRequest: function(operation) {
        // generate the unique IDs for this request
        var me = this,
            request = me.buildRequest(operation),
            params = request.getParams();
 
        // apply JsonP proxy-specific attributes to the Request
        request.setConfig({
            callbackKey: me.callbackKey,
            timeout: me.timeout,
            scope: me,
            disableCaching: false, // handled by the proxy
            callback: me.createRequestCallback(request, operation)
        });
 
        // If we are responsible for appending the params to the URL, clear them now so that
        // The Ext.data.JsonP singleton does not append them.
        if (me.getAutoAppendParams()) {
            request.setParams({});
        }
 
        request.setRawRequest(Ext.data.JsonP.request(request.getCurrentConfig()));
 
        // Set the params back once we have made the request though
        request.setParams(params);
        me.lastRequest = request;
 
        return request;
    },
 
    /**
     * @private
     * Creates and returns the function that is called when the request has completed. The returned
     * function should accept a Response object, which contains the response to be read by the
     * configured Reader. The third argument is the callback that should be called after the
     * request has been completed and the Reader has decoded the response. This callback will
     * typically be the callback passed by a store, e.g. in
     * proxy.read(operation, theCallback, scope) theCallback refers to the callback argument
     * received by this function.
     * See {@link #doRequest} for details.
     * @param {Ext.data.Request} request The Request object
     * @param {Ext.data.operation.Operation} operation The Operation being executed
     * @param {Function} operation.callback The callback function to be called when the request
     * completes. This is usually the callback passed to doRequest
     * @param {Object} operation.scope The scope in which to execute the callback function
     * @return {Function} The callback function
     */
    createRequestCallback: function(request, operation) {
        var me = this;
 
        return function(success, response, errorType) {
            if (request === me.lastRequest) {
                me.lastRequest = null;
            }
 
            me.processResponse(success, operation, request, response);
        };
    },
 
    setException: function(operation, response) {
        operation.setException(operation.getRequest().getRawRequest().errorType);
    },
 
    /**
     * Generates a url based on a given Ext.data.Request object. Adds the params and callback
     * function name to the url
     * @param {Ext.data.Request} request The request object
     * @return {String} The url
     */
    buildUrl: function(request) {
        var me = this,
            url = me.callParent(arguments),
            records = request.getRecords(),
            writer = me.getWriter(),
            params,
            filters,
            filter, i, v;
 
        // In the JsonP proxy, params may only go into the URL.
        // So params created by the Writer get applied to the request's params here
        if (writer && request.getOperation().allowWrite()) {
            request = writer.write(request);
        }
 
        // Encode filters into the URL via params
        params = request.getParams();
        filters = params.filters;
        delete params.filters;
 
        if (filters && filters.length) {
            for (= 0; i < filters.length; i++) {
                filter = filters[i];
 
                v = filter.getValue();
 
                if (v) {
                    params[filter.getProperty()] = v;
                }
            }
        }
 
        // If there's no writer, or the writer is not configured to encode the records into
        // a parameter, then we have to do it here.
        if (Ext.isArray(records) && records.length > 0 && (!writer || !writer.getEncode())) {
            params[me.getRecordParam()] = me.encodeRecords(records);
        }
 
        // If we are responsible for appending the params to the URL, do it now.
        // The params are cleared in doRequest so that the Ext.data.JsonP singleton does not
        // add them.
        if (me.getAutoAppendParams()) {
            url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
        }
 
        return url;
    },
 
    /**
     * Aborts a server request. If no request is passed, the most recent request
     * will be aborted.
     * @param {Ext.data.Request} [request] The request to abort.
     */
    abort: function(request) {
        request = request || this.lastRequest;
 
        if (request) {
            Ext.data.JsonP.abort(request.getRawRequest());
        }
    },
 
    /**
     * Encodes an array of records into a value suitable to be added to the request `params`
     * as the {@link #recordParam} parameter. This is broken out into its own function so that
     * it can be easily overridden.
     * 
     * The default implementation 
     * @param {Ext.data.Model[]} records The records array
     * @return {Array} An array of record data objects
     */
    encodeRecords: function(records) {
        var encoded = [],
            i = 0,
            len = records.length;
 
        for (; i < len; i++) {
            encoded.push(Ext.encode(records[i].getData()));
        }
 
        return encoded;
    }
});