/**
 * @private
 * Small utility class used internally to represent a Direct method.
 */
Ext.define('Ext.direct.RemotingMethod', {
 
    constructor: function(config) {
        var me = this,
            params = config.params,
            len = config.len,
            metadataCfg = config.metadata,
            metadata = {},
            name, pLen, p, param;
 
        me.name = config.name;
        me.disableBatching = config.batched != null ? !config.batched : false;
        
        if (config.formHandler) {
            me.formHandler = config.formHandler;
        }
        else if (Ext.isNumeric(len)) {
            // given only the number of parameters
            me.len = len;
            me.ordered = true;
        }
        else {
            /*
             * Given an array of either
             * a) String
             * b) Objects with a name property. We may want to encode extra info in here later
             * c) Empty array signifies no mandatory parameters
             */
            me.named = true;
            me.strict = config.strict !== undefined ? config.strict : true;
            me.params = {};
            
            // params may not be defined for a formHandler, or named method
            // with no strict checking
            pLen = params && params.length;
 
            for (= 0; p < pLen; p++) {
                param = params[p];
                name = Ext.isObject(param) ? param.name : param;
                me.params[name] = true;
            }
        }
        
        if (metadataCfg) {
            params = metadataCfg.params;
            len = metadataCfg.len;
            
            if (Ext.isNumeric(len)) {
                //<debug>
                if (len === 0) {
                    Ext.raise('metadata.len cannot be 0 ' +
                                    'for Ext Direct method ' + me.name);
                }
                //</debug>
                
                metadata.ordered = true;
                metadata.len = len;
            }
            else if (Ext.isArray(params)) {
                metadata.named = true;
                metadata.params = {};
                
                for (= 0, pLen = params.length; p < pLen; p++) {
                    param = params[p];
                    metadata.params[param] = true;
                }
                
                metadata.strict = metadataCfg.strict !== undefined ? metadataCfg.strict : true;
            }
            //<debug>
            else {
                Ext.raise('metadata is neither named nor ordered ' +
                                'for Ext Direct method ' + me.name);
            }
            //</debug>
            
            me.metadata = metadata;
        }
    },
    
    /**
     * Prepare Direct function arguments that can be used with getCallData().
     */
    getArgs: function(config) {
        var me = this,
            params = config.params,
            paramOrder = config.paramOrder,
            metadata = config.metadata,
            options = config.options,
            args = [],
            flatten, i, len;
        
        if (me.ordered) {
            if (me.len > 0) {
                // If a paramOrder was specified, add the params into the argument list
                // in that order.
                if (paramOrder) {
                    // Direct proxy uses this configuration for its CRUD operations.
                    // We only do this kind of thing for ordered Methods that accept 1 argument,
                    // if there's more or less we fall back to default processing.
                    flatten = config.paramsAsArray && me.len === 1 &&
                              (paramOrder.length > 1 || Ext.isArray(params));
                    
                    if (flatten) {
                        if (Ext.isArray(params)) {
                            for (= 0, len = params.length; i < len; i++) {
                                args.push(me.convertParams(params[i], paramOrder,
                                                           paramOrder.length, true));
                            }
                        }
                        else {
                            args = me.convertParams(params, paramOrder, paramOrder.length, true);
                        }
                        
                        if (!params.allowSingle || args.length > 1) {
                            args = [args];
                        }
                    }
                    else {
                        // The number of arguments expected by the Method has priority
                        // over the number of parameters in paramOrder.
                        args = me.convertParams(params, paramOrder, me.len, false);
                    }
                }
                else {
                    args.push(params);
                }
            }
        }
        else {
            args.push(params);
        }
        
        args.push(config.callback, config.scope || window);
        
        if (options || metadata) {
            options = Ext.apply({}, options);
            
            if (metadata) {
                // Could be either an object of named arguments,
                // or an array of ordered arguments
                options.metadata = metadata;
            }
            
            args.push(options);
        }
        
        return args;
    },
    
    convertParams: function(params, paramOrder, count, flatten) {
        var ret = [],
            paramName, i, len;
            
        for (= 0, len = count; i < len; i++) {
            paramName = paramOrder[i];
            ret.push(params[paramName]);
        }
        
        if (flatten) {
            return ret.length === 0 ? undefined : ret.length === 1 ? ret[0] : ret;
        }
        else {
            return ret;
        }
    },
 
    /**
     * Takes the arguments for a Direct function and splits the arguments
     * from the scope and the callback.
     *
     * @param {Array} args The arguments passed to the direct call
     *
     * @return {Object} An object with 4 properties: args, callback, scope, and options object.
     */
    getCallData: function(args) {
        var me = this,
            data = null,
            len = me.len,
            params = me.params,
            strict = me.strict,
            form, callback, scope, name, options, metadata;
        
        // Historically, the presence of required arguments was not checked;
        // another idiosyncrasy is that null is sent to the server side
        // instead of empty array when len === 0
        if (me.ordered) {
            callback = args[len];
            scope = args[len + 1];
            options = args[len + 2];
            
            if (len !== 0) {
                data = args.slice(0, len);
            }
        }
        else if (me.formHandler) {
            form = args[0];
            callback = args[1];
            scope = args[2];
            options = args[3];
        }
        else {
            data = Ext.apply({}, args[0]);
            callback = args[1];
            scope = args[2];
            options = args[3];
 
            // filter out any non-existent properties unless !strict
            if (strict) {
                for (name in data) {
                    if (data.hasOwnProperty(name) && !params[name]) {
                        delete data[name];
                    }
                }
            }
        }
        
        if (me.metadata && options && options.metadata) {
            if (me.metadata.ordered) {
                //<debug>
                if (!Ext.isArray(options.metadata)) {
                    Ext.raise('options.metadata is not an Array ' +
                                    'for Ext Direct method ' + me.name);
                }
                else if (options.metadata.length < me.metadata.len) {
                    Ext.raise('Not enough parameters in options.metadata ' +
                                    'for Ext Direct method ' + me.name);
                }
                //</debug>
                
                metadata = options.metadata.slice(0, me.metadata.len);
            }
            else {
                //<debug>
                if (!Ext.isObject(options.metadata)) {
                    Ext.raise('options.metadata is not an Object ' +
                                    'for Ext Direct method ' + me.name);
                }
                //</debug>
 
                metadata = Ext.apply({}, options.metadata);
                
                if (me.metadata.strict) {
                    for (name in metadata) {
                        if (metadata.hasOwnProperty(name) && !me.metadata.params[name]) {
                            delete metadata[name];
                        }
                    }
                }
                
                //<debug>
                for (name in me.metadata.params) {
                    if (!metadata.hasOwnProperty(name)) {
                        Ext.raise('Named parameter ' + name + ' is missing ' +
                                        'in options.metadata for Ext Direct method ' +
                                        me.name);
                    }
                }
                //</debug>
            }
            
            delete options.metadata;
        }
 
        return {
            form: form,
            data: data,
            metadata: metadata,
            callback: callback,
            scope: scope,
            options: options
        };
    }
});