/**
 * This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
 * The {@link #allowSingle} configuration can be set to false to force the records to always
 * be encoded in an array, even if there is only a single record being sent.
 */
Ext.define('Ext.data.writer.Json', {
    extend: 'Ext.data.writer.Writer',
    alternateClassName: 'Ext.data.JsonWriter',
    alias: 'writer.json',
    
    config: {
        /**
        * @cfg {String} rootProperty The HTTP parameter name by which JSON encoded records
        * will be passed to the server if the {@link #encode} option is `true`.
        */
        rootProperty: undefined,
    
        /**
        * @cfg {Boolean} [encode=false] Configure `true` to send record data (all record fields
        * if {@link #writeAllFields} is `true`) as a JSON encoded HTTP parameter named by the
        * {@link #rootProperty} configuration.
        * 
        * The encode option should only be set to true when a {@link #rootProperty} is defined,
        * because the values will be sent as part of the request parameters as opposed to
        * a raw post. The root will be the name of the parameter sent to the server.
        */
        encode: false,
    
        /**
        * @cfg {Boolean} [allowSingle=true] Configure with `false` to ensure that records are
        * always wrapped in an array, even if there is only one record being sent. When there
        * is more than one record, they will always be encoded into an array.
        */
        allowSingle: true,
    
        /**
        * @cfg {Boolean} [expandData=false] By default, when dot-delimited field
        * {@link #nameProperty mappings} are used (e.g. `name: 'myProperty', mapping:
        * 'my.nested.property'`) the writer will simply output a flat data object containing
        * the mapping string literal as the property name (e.g. `{ 'my.nested.property': 'foo' }`).
        * 
        * Mappings are used to map incoming nested JSON to flat Ext models. In many case, the data
        * output by the writer should preferrably match the original nested data format. Setting
        * this config to `true` will ensure that the output will instead look like
        * `{ my: { nested: { property: 'foo' }}}`. The output is generated by
        * {@link #getExpandedData}, which can optionally be overridden to apply
        * more customized logic.
        */
        expandData: false
    },
    
    //<debug>
    constructor: function(config) {
        if (config && config.hasOwnProperty('root')) {
            config = Ext.apply({}, config);
            config.rootProperty = config.root;
            delete config.root;
            Ext.log.warn('Ext.data.writer.Json: Using the deprecated "root" configuration. ' +
                         'Use "rootProperty" instead.');
        }
        
        this.callParent([config]);
    },
    //</debug>
    
    /**
     * @protected
     * The Reader classes support dot-delimited data mappings for extracting nested raw data
     * into fields, so the writer must support converting the flat {@link Ext.data.Model} structure
     * back into the original nested data format. Using the same mappings when available, the Writer
     * will simply split each delimiter into a nested object in the output, which should exactly
     * match the input format. For example, record data like this:
     * 
     *     my.nested.property: 'foo',
     *     my.nested.another: 'bar',
     *     my.somethingElse: 123
     * 
     * should write out as...
     * 
     *     my: {
     *         nested: {
     *             property: 'foo',
     *             another: 'bar
     *         },
     *         somethingElse: 123
     *     }
     *
     * This behavior is governed by the {@link #expandData} config. By default, this option is
     * `false` for compatibility reasons, and will output a flat structure matching the flat record
     * format. Setting this config to `true` will enable the expanded mapping behavior as shown
     * here. This method could also be overridden to provide an even more customized output data
     * structure.
     */
    getExpandedData: function(data) {
        var dataLength = data.length,
            i = 0,
            item, prop, nameParts, j, tempObj, toObject;
            
        toObject = function(name, value) {
            var o = {};
            
            o[name] = value;
            
            return o;
        };
        
        for (; i < dataLength; i++) {
            item = data[i];
            
            for (prop in item) {
                if (item.hasOwnProperty(prop)) {
                    // e.g. my.nested.property: 'foo'
                    nameParts = prop.split('.');
                    j = nameParts.length - 1;
                    
                    if (> 0) {
                        // Initially this will be the value 'foo'.
                        // Equivalent to rec['my.nested.property']
                        tempObj = item[prop];
                        
                        for (; j > 0; j--) {
                            // Starting with the value above, we loop inside out, assigning the
                            // current object as the value for the parent name. Work all
                            // the way up until only the root name is left to assign.
                            tempObj = toObject(nameParts[j], tempObj);
                        }
                        
                        // At this point we'll have all child properties rolled up into a single
                        // object like `{ nested: { property: 'foo' }}`. Now add the root name
                        // (e.g. 'my') to the record data if needed (do not overwrite existing):
                        item[nameParts[0]] = item[nameParts[0]] || {};
                        
                        // Since there could be duplicate names at any level of the nesting be sure
                        // to merge rather than assign when setting the object as the value:
                        Ext.Object.merge(item[nameParts[0]], tempObj);
                        
                        // Finally delete the original mapped property from the record
                        delete item[prop];
                    }
                }
            }
        }
        
        return data;
    },
    
    writeRecords: function(request, data) {
        var me = this,
            root = me.getRootProperty(),
            json, single, transform;
        
        if (me.getExpandData()) {
            data = me.getExpandedData(data);
        }
        
        if (me.getAllowSingle() && data.length === 1) {
            // convert to single object format
            data = data[0];
            single = true;
        }
        
        transform = this.getTransform();
        
        if (transform) {
            data = transform(data, request);
        }
        
        if (me.getEncode()) {
            if (root) {
                // sending as a param, need to encode
                request.setParam(root, Ext.encode(data));
            }
            else {
                //<debug>
                Ext.raise('Must specify a root when using encode');
                //</debug>
            }
        }
        else if (single || (data && data.length)) {
            // send as jsonData
            json = request.getJsonData() || {};
            
            if (root) {
                json[root] = data;
            }
            else {
                json = data;
            }
            
            request.setJsonData(json);
        }
        
        return request;
    }
});