/*
 *  Copyright 2012 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * Defines a dedicated proxy that use the {@link Ametys.data.ServerComm} to communicate with the server.
 * 
 * Examples of configuration in a store:
 * 
 * 		Ext.create('Ext.data.Store', {
 * 			model: 'my.own.Model',
 * 			proxy: {
 * 				type: 'ametys',
 * 				plugin: 'projects',
 * 				url: 'administrator/projects/list.xml',
 * 				reader: {
 * 					type: 'xml',
 * 					record: 'element',
 * 					rootProperty: 'root'
 * 				}
 * 			}
 * 		});
 * 
 * or 
 * 
 *      Ext.create('Ext.data.Store', {
 *          model: 'my.own.Model',
 *          proxy: {
 *              type: 'ametys',
 *              methodName: 'getProjects',
 *              methodArguments: ['siteName', 'lang'],
 *              role: 'my.own.ProjectDAO',
 *              reader: {
 *                  type: 'xml',
 *                  record: 'element',
 *                  rootProperty: 'root'
 *              }
 *          }
 *      });
 *      
 * or 
 *      
 *      Ext.create('Ext.data.Store', {
 *          model: 'my.own.Model',
 *          proxy: {
 *              type: 'ametys',
 *              methodName: 'getProjects',
 *              methodArguments: ['siteName', 'lang'],
 *              serverCaller: this,
 *              reader: {
 *                  type: 'xml',
 *                  record: 'element',
 *                  rootProperty: 'root'
 *              }
 *          }
 *      });
 */
Ext.define('Ametys.data.ServerCommProxy', {
	alias: 'proxy.ametys',
	extend: 'Ext.data.proxy.Server',
	
	noCache: false,
	
	/**
	 * @cfg {String} plugin The name of the plugin receiving the request. See Ametys.data.ServerComm#send.
	 */
	plugin: null,
	/**
	 * @cfg {String} workspace The name of the workspace receiving the request. See Ametys.data.ServerComm#send.
	 */
	workspace: null,
    
    /**
     * @cfg {String} serverId If the server-side {@link #cfg-role} is a multiple extension point, this refers to an extension. See Ametys.data.ServerComm#callMethod. Can be null if {@link #cfg-url} or {@link #cfg-serverCaller} is used.
     */
    serverId: null,
    /**
     * @cfg {String} role The id of Java component. See Ametys.data.ServerComm#callMethod. Can be null if {@link #cfg-url} or {@link #cfg-serverCaller} is used.
     */
    role: null,
    /**
     * @cfg {Ametys.data.ServerCaller} serverCaller The JS component corresponding to a serverside ClientSideElement to call {@link #cfg-methodName} callable method. Can be null if {@link #cfg-url} or {@link #cfg-role} is used.
     */
    serverCaller: null,
    
    /**
     * @cfg {String} methodName The "callable" method to call for this proxy. See Ametys.data.ServerComm#callMethod. Can be null {@link #cfg-url} is used.
     */
    methodName: null,
    /**
     * @cfg {String[]} methodArguments Names of the parameters expected by the "callable" method, in expected order. If null, the all request's parameters as a Object will be pass to the "callable" method.
     */
    methodArguments: null,
    
	/**
	 * @cfg {Boolean} cancelOutdatedRequest True to cancel the old and outdated requests of this proxy
	 */
    /**
     * @private
     * @property {String} _cancelCode The cancel code for the requests of this proxy.
     */
	_cancelCode: null,
	
	/**
	 * @cfg {String} errorMessage The error message to display if an error occures. There is a default value.
	 */
	errorMessage: "{{i18n PLUGINS_CORE_UI_SERVERCOMMPROXY_ERROR_MSG}}",
	
	/**
	 * @cfg {Object} reader2ResponseTypes Values are the responseType of Ametys.data.ServerComm#send and keys possible reader type.
	 * The default value do associate xml<->xml and json<->text
	 */
	reader2ResponseTypes: {
		xml: 'xml',
		json: 'text'
	},
	
	statics: {
		/**
		 * Add a new mapping between a reader type and a response type. This is
		 * necessary to define a new reader to be used by this proxy.
		 * @param {String} readerType The type of the reader
		 * @param {String} responseType The type of the response
		 */
		addReader2ResponseType: function(readerType, responseType)
		{
			this.prototype.reader2ResponseTypes[readerType] = responseType;
		}
	},
    
    constructor: function(config)
    {
        if (config.cancelOutdatedRequest)
        {
            this._cancelCode = Ext.id(null, this.self.getName() + '$');
        }
        
        this.callParent(arguments);
    },
    
    /**
     * Gets the cancel code
     * @return {String} The cancel code for this proxy
     */
    getCancelCode: function()
    {
        return this._cancelCode;
    },
	
	doRequest: function(operation, callback, scope)
	{
        if (this.methodName && (this.role || this.serverCaller))
        {
            operation.setUrl('client-call');
            this.plugin = 'core-ui';
        }
        
        var request = this.buildRequest(operation);
            
        var params = {};
        if (this.methodName && this.role)
        {
            params = {
                role: this.role,
                id: this.serverId,
                methodName: this.methodName,
                parameters: this._getMethodParameters(request)
            }
        }
        else if (this.methodName && this.serverCaller)
        {
            if (!this.serverCaller.isServerCaller)
            {
                Ametys.log.ErrorDialog.display({
                    title: "{{i18n PLUGINS_CORE_UI_SERVERCOMM_ERROR_TITLE}}",
                    text: this.errorMessage,
                    details: this.serverCaller.$className + " is not a instance of Ametys.data.ServerCaller",
                    category: "Ametys.data.ServerCommProxy"
                });
                return;
            }
            
            params = {
                role: this.serverCaller.getServerRole(),
                id: this.serverCaller.getServerId(),
                methodName: this.methodName,
                parameters: this._getMethodParameters(request)
            }
        }
        else
        {
            params = request.getParams();
        }
        
        var writer  = this.getWriter();
    
        if (operation.allowWrite()) {
            request = writer.write(request);
        }
    
        Ametys.data.ServerComm.send({
            priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
            plugin: this.plugin,
            workspace: this.workspace,
            url: request.getUrl(),
            parameters: params,
            responseType: this.getResponseType(),
            callback: {
                handler: this.createRequestCallback,
                scope: this,
                arguments: {
                    request: request,
                    operation: operation,
                    callback: callback,
                    scope: scope
                }
            },
            cancelCode: this._cancelCode
        });
	},
    
    /**
     * @private
     * Get the method's parameters as a Array.
     * If {@link #cfg-methodArguments} is not null, the parameters will be extracted from request's parameters and flatten into the returned array, in same order. 
     * Otherwise, the returned parameters will be a one-size array with the request's parameters
     * @param {Ext.data.Request} request The request
     * @return {Object[]} The method's parameters into a Array 
     */
    _getMethodParameters: function(request)
    {
        var params = request.getParams();
        
        if (this.methodArguments && Ext.isArray(this.methodArguments))
        {
            var paramsAsArray = [];
            Ext.Array.each(this.methodArguments, function (name) {
                paramsAsArray.push(params[name]);
            });
            return paramsAsArray;
        }
        else
        {
	        return [params];
        }
        
    },
	
	/**
	 * @private
	 * Get the response type for Ametys.data.ServerComm#send depending on the current reader type.
	 * @return {String} A response type accepted by Ametys.data.ServerComm#send. Default value is 'xml'.
	 */
	getResponseType: function()
	{
		return this.reader2ResponseTypes[this.getReader().type || 'xml'];
	},
	
	/**
	 * @private
	 * Creates an intermediary callback for the Ametys.data.ServerComm#send to call the #doRequest callback with the rights arguments
	 * @param {Object} response The response
	 * @param {Object[]} arguments The arguments transmited to the Ametys.data.ServerComm#send call in the #doRequest method.
	 * @return {Function} A callback function at the Ametys.data.ServerComm#send format to call the #doRequest callback
	 */
	createRequestCallback: function(response, arguments)
	{
		var failure = Ametys.data.ServerComm.handleBadResponse(this.errorMessage, 
																response, 
																Ext.getClassName(this) + '.createRequestCallback');
		
		this.processResponse(!failure, arguments.operation, arguments.request, response, arguments.callback, arguments.scope);
	},
	
	extractResponseData: function(response)
	{
		var responseType = this.getResponseType();
		
		// Return first child which is the expected root node
		if (responseType == 'xml')
		{
			return Ext.dom.Query.selectNode('*', response)
		}
		else
		{
			return Ext.JSON.decode(Ext.dom.Query.selectValue('', response));
		}
	}
});