/**
 * @class Ext.data.writer.Xml
 * This class is used to write {@link Ext.data.Model} data to the server in an XML format.
 * The {@link #documentRoot} property is used to specify the root element in the XML document.
 * The {@link #record} option is used to specify the element name for each record that will
 * make up the XML document.
 */
Ext.define('Ext.data.writer.Xml', {
    extend: 'Ext.data.writer.Writer',
    alternateClassName: 'Ext.data.XmlWriter',
 
    alias: 'writer.xml',
 
    config: {
        /**
         * @cfg {String} documentRoot The name of the root element of the document. Defaults to
         * `'xmlData'`. If there is more than 1 record and the root is not specified, the default
         * document root will still be used to ensure a valid XML document is created.
         *
         * If the {@link #record} mapping includes a root element name, eg: "SystemInfo>Operation",
         * and the selector includes the root element name, then you must configure this as `false`
         */
        documentRoot: 'xmlData',
 
        /**
         * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty
         * and a root is required to form a valid XML document.
         */
        defaultDocumentRoot: 'xmlData',
 
        /**
         * @cfg {String} header A header to use in the XML document (such as setting the encoding
         * or version). Defaults to `''`.
         */
        header: '',
 
        /**
         * @cfg {String} record The name of the node to use for each record. Defaults to
         * the owning {@link Ext.data.proxy.Proxy Proxy}'s {@link Ext.data.reader.Xml Reader}'s
         * {@link Ext.data.reader.Xml#record} setting, or `'record'`.
         */
        record: 'record'
    },
 
    // To break simple XPath selectors like "SystemInfo>SystemName"
    // into ["SystemInfo", "SystemName"]
    selectorRe: /[^>\s]+/g,
 
    writeRecords: function(request, data) {
        var me = this,
            xml = [],
            i = 0,
            len = data.length,
            root = me.getDocumentRoot(),
            recordName = me.getRecord(),
 
            // Convert eg 'Items>Item' into ['Items', 'Item']
            record = recordName.match(this.selectorRe),
            recLen = record.length,
 
            // Need a containing element if there are multiple data records and
            // it's not a compound record selector
            needsRoot = data.length !== 1 && recLen === 1,
            transform;
 
        transform = this.getTransform();
 
        if (transform) {
            data = transform(data, request);
        }
 
        // may not exist
        xml.push(me.getHeader() || '');
 
        if (!root && needsRoot) {
            root = me.getDefaultDocumentRoot();
        }
 
        // May not exist if configured as false, and the record selector is rooted, eg "Items>Item"
        if (root) {
            xml.push('<', root, '>');
        }
 
        // Output record nodes' wrapping, eg "<Items>" from record "Items>Item"->["Items", "Item"]
        for (= 0; i < recLen - 1; i++) {
            xml.push('<', record[i], '>');
        }
 
        recordName = record[i];
 
        for (= 0; i < len; ++i) {
            this.objectToElement(recordName, data[i], xml);
        }
 
        // Close record nodes' wrapping, eg "</Items>" from record "Items>Item"->["Items", "Item"]
        for (= recLen - 2; i > -1; i--) {
            xml.push('</', record[i], '>');
        }
 
        if (root) {
            xml.push('</', root, '>');
        }
 
        request.setXmlData(xml.join(''));
 
        return request;
    },
 
    /**
     * Serializes an object to XML.
     * Properties will be serialized as child elements unless their first character is `'@'`
     *
     * For example:
     *
     *     myWriter.objectToElement('SystemComponent', {
     *         "@SystemNumber": '10118795',
     *         "SystemInfo>SystemName": 'Phase Noise Measurement System',
     *         AssetId: 'DE3208',
     *         AgilentModel: 'E5505A',
     *         SerialNumber: 'US44101357',
     *     }, []).join('');
     *
     * becomes
     *
     *     <SystemComponent SystemNumber="10118795">
     *       <SystemInfo>
     *           <SystemName>Phase Noise Measurement System</SystemName>
     *       </SystemInfo>
     *       <AssetId>DE3208</AssetId>
     *       <AgilentModel>E5505A</AgilentModel>
     *       <SerialNumber>US44101357</SerialNumber>
     *     </SystemComponent>
     *    
     * @param {String} name The element name for the object.
     * @param {Object} o The object to serialize.
     * @param {Array} [output] The array into which to serialize the object.
     * @return {undefined} 
     */
    objectToElement: function(name, o, output) {
        var subOutput = [],
            key, datum, subKeys, subKeyLen, subObject, subObjects, lastObject, lastKey, i;
 
        if (!output) {
            output = [];
        }
 
        // Open the record node, eg "<Item"
        // Stop there because some child properties may be attributes.
        output.push('<', name);
 
        for (key in o) {
            datum = o[key];
 
            // Attribute node
            if (key[0] === '@') {
                output.push(' ', key.substr(1), '="', datum, '"');
            }
            // Child element node
            else {
                // Object properties become child elements
                if (typeof datum === 'object') {
                    this.objectToElement(key, datum, subOutput);
                }
                else {
                    // Is it a selector?
                    subKeys = key.match(this.selectorRe);
 
                    // key was eg "foo > bar".
                    // Ensure looks like contains {foo: {bar: {}}}
                    if ((subKeyLen = subKeys.length) > 1) {
                        subObjects = subObjects || {};
 
                        for (subObject = subObjects, i = 0; i < subKeyLen; i++) {
                            lastObject = subObject;
                            lastKey = subKeys[i];
                            subObject = subObject[lastKey] || (subObject[lastKey] = {});
                        }
 
                        // lastObject is now the bar property in the above example
                        lastObject[lastKey] = datum;
                    }
                    else {
                        subOutput.push('<', key, '>', datum, '</', key, '>');
                    }
                }
            }
        }
 
        output.push('>');
        output.push.apply(output, subOutput);
 
        // Output any embedded nodes that were specified by child element mappings
        // like "SystemInfo>SystemName"
        if (subObjects) {
            for (key in subObjects) {
                datum = subObjects[key];
                this.objectToElement(key, datum, output);
            }
        }
 
        // Close the record node, eg "</Item>"
        output.push('</', name, '>');
 
        return output;
    }
});