/**
 * @author Ed Spencer
 *
 * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
 * would not usually be used directly.
 * @private
 */
Ext.define('Ext.data.proxy.Server', {
    extend: 'Ext.data.proxy.Proxy',
    alias : 'proxy.server',
    alternateClassName: 'Ext.data.ServerProxy',
    requires  : ['Ext.data.Request'],

    config: {
        /**
         * @cfg {String} url
         * The URL from which to request the data object.
         */
        url: null,

        /**
         * @cfg {String} pageParam
         * The name of the `page` parameter to send in a request. Set this to `false` if you don't
         * want to send a page parameter.
         */
        pageParam: 'page',

        /**
         * @cfg {String} startParam
         * The name of the `start` parameter to send in a request. Set this to `false` if you don't
         * want to send a start parameter.
         */
        startParam: 'start',

        /**
         * @cfg {String} limitParam
         * The name of the `limit` parameter to send in a request. Set this to `false` if you don't
         * want to send a limit parameter.
         */
        limitParam: 'limit',

        /**
         * @cfg {String} groupParam
         * The name of the `group` parameter to send in a request. Set this to `false` if you don't
         * want to send a group parameter.
         */
        groupParam: 'group',

        /**
         * @cfg {String} sortParam
         * The name of the `sort` parameter to send in a request. Set this to `undefined` if you don't
         * want to send a sort parameter.
         */
        sortParam: 'sort',

        /**
         * @cfg {String} filterParam
         * The name of the 'filter' parameter to send in a request. Set this to `undefined` if you don't
         * want to send a filter parameter.
         */
        filterParam: 'filter',

        /**
         * @cfg {String} directionParam
         * The name of the direction parameter to send in a request.
         *
         * __Note:__ This is only used when `simpleSortMode` is set to `true`.
         */
        directionParam: 'dir',

        /**
         * @cfg {Boolean} enablePagingParams This can be set to `false` if you want to prevent the paging params to be
         * sent along with the requests made by this proxy.
         */
        enablePagingParams: true,

        /**
         * @cfg {Boolean} simpleSortMode
         * Enabling `simpleSortMode` in conjunction with `remoteSort` will only send one sort property and a direction when a
         * remote sort is requested. The `directionParam` and `sortParam` will be sent with the property name and either 'ASC'
         * or 'DESC'.
         */
        simpleSortMode: false,

        /**
         * @cfg {Boolean} noCache
         * Disable caching by adding a unique parameter name to the request. Set to `false` to allow caching.
         */
        noCache : true,

        /**
         * @cfg {String} cacheString
         * The name of the cache param added to the url when using `noCache`.
         */
        cacheString: "_dc",

        /**
         * @cfg {Number} timeout
         * The number of milliseconds to wait for a response.
         */
        timeout : 30000,

        /**
         * @cfg {Object} api
         * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
         *
         *     api: {
         *         create  : undefined,
         *         read    : undefined,
         *         update  : undefined,
         *         destroy : undefined
         *     }
         *
         * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
         * {@link #api} property, or if undefined default to the configured
         * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
         *
         * For example:
         *
         *     api: {
         *         create  : '/controller/new',
         *         read    : '/controller/load',
         *         update  : '/controller/update',
         *         destroy : '/controller/destroy_action'
         *     }
         *
         * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
         * configured {@link Ext.data.proxy.Server#url url}.
         */
        api: {
            create  : undefined,
            read    : undefined,
            update  : undefined,
            destroy : undefined
        },

        /**
         * @cfg {Object} extraParams
         * Extra parameters that will be included on every request. Individual requests with params of the same name
         * will override these params when they are in conflict.
         */
        extraParams: {}
    },

    constructor: function(config) {
        config = config || {};
        if (config.nocache !== undefined) {
            config.noCache = config.nocache;
            // <debug>
            Ext.Logger.warn('nocache configuration on Ext.data.proxy.Server has been deprecated. Please use noCache.');
            // </debug>
        }
        this.callParent([config]);
    },

    //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
    create: function() {
        return this.doRequest.apply(this, arguments);
    },

    read: function() {
        return this.doRequest.apply(this, arguments);
    },

    update: function() {
        return this.doRequest.apply(this, arguments);
    },

    destroy: function() {
        return this.doRequest.apply(this, arguments);
    },

    /**
     * Sets a value in the underlying {@link #extraParams}.
     * @param {String} name The key for the new value
     * @param {Object} value The value
     */
    setExtraParam: function(name, value) {
        this.getExtraParams()[name] = value;
    },

    /**
     * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
     * that this Proxy is attached to.
     * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
     * @return {Ext.data.Request} The request object
     */
    buildRequest: function(operation) {
        var me = this,
            params = Ext.applyIf(operation.getParams() || {}, me.getExtraParams() || {}),
            request;

        //copy any sorters, filters etc into the params so they can be sent over the wire
        params = Ext.applyIf(params, me.getParams(operation));

        request = Ext.create('Ext.data.Request', {
            params   : params,
            action   : operation.getAction(),
            records  : operation.getRecords(),
            url      : operation.getUrl(),
            operation: operation,
            proxy    : me
        });

        request.setUrl(me.buildUrl(request));
        operation.setRequest(request);

        return request;
    },

    /**
     * This method handles the processing of the response and is usually overridden by subclasses to
     * do additional processing.
     * @param {Boolean} success Whether or not this request was successful
     * @param {Ext.data.Operation} operation The operation we made this request for
     * @param {Ext.data.Request} request The request that was made
     * @param {Object} response The response that we got
     * @param {Function} callback The callback to be fired onces the response is processed
     * @param {Object} scope The scope in which we call the callback
     * @protected
     */
    processResponse: function(success, operation, request, response, callback, scope) {
        var me = this,
            action = operation.getAction(),
            reader, resultSet;

        if (success === true) {
            reader = me.getReader();

            try {
                resultSet = reader.process(me.getResponseResult(response));
            } catch(e) {
                operation.setException(e.message);

                me.fireEvent('exception', me, response, operation);
                return;
            }

            // This could happen if the model was configured using metaData
            if (!operation.getModel()) {
                operation.setModel(this.getModel());
            }

            if (operation.process(action, resultSet, request, response) === false) {
                me.setException(operation, response);
                me.fireEvent('exception', me, response, operation);
            }
        } else {
            me.setException(operation, response);
            /**
             * @event exception
             * Fires when the server returns an exception
             * @param {Ext.data.proxy.Proxy} this
             * @param {Object} response The response from the AJAX request
             * @param {Ext.data.Operation} operation The operation that triggered request
             */
            me.fireEvent('exception', this, response, operation);
        }

        //this callback is the one that was passed to the 'read' or 'write' function above
        if (typeof callback == 'function') {
            callback.call(scope || me, operation);
        }

        me.afterRequest(request, success);
    },

    /**
     * @private
     */
    getResponseResult: function(response) {
        return response;
    },

    /**
     * Sets up an exception on the operation
     * @private
     * @param {Ext.data.Operation} operation The operation
     * @param {Object} response The response
     */
    setException: function(operation, response) {
        if (Ext.isObject(response)) {
            operation.setException({
                status: response.status,
                statusText: response.statusText
            });
        }
    },

    /**
     * Encode any values being sent to the server. Can be overridden in subclasses.
     * @private
     * @param {Array} value An array of sorters/filters.
     * @return {Object} The encoded value
     */
    applyEncoding: function(value) {
        return Ext.encode(value);
    },

    /**
     * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
     * this simply JSON-encodes the sorter data
     * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
     * @return {String} The encoded sorters
     */
    encodeSorters: function(sorters) {
        var min = [],
            length = sorters.length,
            i = 0;

        for (; i < length; i++) {
            min[i] = {
                property : sorters[i].getProperty(),
                direction: sorters[i].getDirection()
            };
        }
        return this.applyEncoding(min);

    },

    /**
     * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
     * this simply JSON-encodes the filter data
     * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
     * @return {String} The encoded filters
     */
    encodeFilters: function(filters) {
        var min = [],
            length = filters.length,
            i = 0;

        for (; i < length; i++) {
            min[i] = {
                property: filters[i].getProperty(),
                value   : filters[i].getValue()
            };
        }
        return this.applyEncoding(min);
    },

    /**
     * @private
     * Copy any sorters, filters etc into the params so they can be sent over the wire
     */
    getParams: function(operation) {
        var me = this,
            params = {},
            grouper = operation.getGrouper(),
            sorters = operation.getSorters(),
            filters = operation.getFilters(),
            page = operation.getPage(),
            start = operation.getStart(),
            limit = operation.getLimit(),

            simpleSortMode = me.getSimpleSortMode(),

            pageParam = me.getPageParam(),
            startParam = me.getStartParam(),
            limitParam = me.getLimitParam(),
            groupParam = me.getGroupParam(),
            sortParam = me.getSortParam(),
            filterParam = me.getFilterParam(),
            directionParam = me.getDirectionParam();

        if (me.getEnablePagingParams()) {
            if (pageParam && page !== null) {
                params[pageParam] = page;
            }

            if (startParam && start !== null) {
                params[startParam] = start;
            }

            if (limitParam && limit !== null) {
                params[limitParam] = limit;
            }
        }

        if (groupParam && grouper) {
            // Grouper is a subclass of sorter, so we can just use the sorter method
            params[groupParam] = me.encodeSorters([grouper]);
        }

        if (sortParam && sorters && sorters.length > 0) {
            if (simpleSortMode) {
                params[sortParam] = sorters[0].getProperty();
                params[directionParam] = sorters[0].getDirection();
            } else {
                params[sortParam] = me.encodeSorters(sorters);
            }
        }

        if (filterParam && filters && filters.length > 0) {
            params[filterParam] = me.encodeFilters(filters);
        }

        return params;
    },

    /**
     * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
     * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
     * @param {Ext.data.Request} request The request object
     * @return {String} The url
     */
    buildUrl: function(request) {
        var me = this,
            url = me.getUrl(request);

        //<debug>
        if (!url) {
            Ext.Logger.error("You are using a ServerProxy but have not supplied it with a url.");
        }
        //</debug>

        if (me.getNoCache()) {
            url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.getCacheString(), Ext.Date.now()));
        }

        return url;
    },

    /**
     * Get the url for the request taking into account the order of priority,
     * - The request
     * - The api
     * - The url
     * @private
     * @param {Ext.data.Request} request The request
     * @return {String} The url
     */
    getUrl: function(request) {
        return request ? request.getUrl() || this.getApi()[request.getAction()] || this._url : this._url;
    },

    /**
     * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
     * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
     * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
     * each of the methods that delegate to it.
     *
     * @param {Ext.data.Operation} operation The Ext.data.Operation object
     * @param {Function} callback The callback function to call when the Operation has completed
     * @param {Object} scope The scope in which to execute the callback
     * @protected
     * @template
     */
    doRequest: function() {
        //<debug>
        Ext.Logger.error("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
        //</debug>
    },

    /**
     * Optional callback function which can be used to clean up after a request has been completed.
     * @param {Ext.data.Request} request The Request object
     * @param {Boolean} success True if the request was successful
     * @method
     */
    afterRequest: Ext.emptyFn
});


"use strict";

// 导出静态表为excel，这时会把静态表放在第一个tab页，后面的页放枚举列表，方便编辑查看

var fs = require('fs');
var util = require('util');
var base = require('./base');
var xlsx = require('node-xlsx');

// 判断是否有TAB页
function hasTAB(excelobj, tabname) {
    for (let ii = 0; ii < excelobj.length; ++ii) {
        if (excelobj[ii].name == tabname) {
            return true;
        }
    }

    return false;
}

function exportEnum(enumname, root) {
    let curobj = base.getGlobalObj(enumname, root);
    if (curobj != undefined) {
        let tab = {name: enumname, data: []};

        for (let ii = 0; ii < curobj.val.length; ++ii) {
            let curline = curobj.val[ii];
            tab.data.push([curline.name, curline.val.val, curline.comment]);
        }

        return tab;
    }

    return undefined;
}

function findField(arrdata, fieldname) {
    for (let ii = 0; ii < arrdata[1].length; ++ii) {
        if (arrdata[1][ii] == fieldname) {
            return ii;
        }
    }

    return -1;
}

// src1 是老版本，src2 是新版本
function mergeExcel(src1, src2) {
    if (src1 == undefined) {
        return src2;
    }

    if (src1[0].name != src2[0].name) {
        return src2;
    }

    if (src1[0].data.length <= 2) {
        return src2;
    }

    let dest = [];
    dest.push({name: src2[0].name, data: []});

    for (let ii = 0; ii < src1[0].data.length; ++ii) {
        dest[0].data.push([]);
    }

    for (let ii = 0; ii < src2[0].data[1].length; ++ii) {
        let sf = findField(src1[0].data, src2[0].data[1][ii]);
        if (sf != -1) {
            let cn = src1[0].data.length - 2;

            dest[0].data[0].push(src2[0].data[0][ii]);
            dest[0].data[1].push(src2[0].data[1][ii]);

            for (let jj = 0; jj < cn; ++jj) {
                dest[0].data[jj + 2].push(src1[0].data[jj + 2][sf]);
            }
        }
        else {
            let cn = src1[0].data.length - 2;

            dest[0].data[0].push(src2[0].data[0][ii]);
            dest[0].data[1].push(src2[0].data[1][ii]);

            for (let jj = 0; jj < cn; ++jj) {
                dest[0].data[jj + 2].push('');
            }
        }
    }

    for (let ii = 1; ii < src2.length; ++ii) {
        dest.push(src2[ii]);
    }

    return dest;
}

function exportStatic(path, obj, callback, root) {
    let arrold = undefined;
    if (fs.existsSync(path)) {
        arrold = xlsx.parse(path);
    }

    var arr = [];
    arr.push({name: obj.name, data: []});

    var comment = [];
    var head = [];

    base.forEachStruct(obj.name, obj, root, function (structname, memberobj, root) {
        let curcomment = memberobj.comment;

        let curobj = base.getGlobalObj(memberobj.type, root);
        if (base.isEnum(memberobj.type, root)) {
            curcomment = util.format('%s(%s)', memberobj.comment, memberobj.type);

            if (!hasTAB(arr, memberobj.type)) {
                let curtab = exportEnum(memberobj.type, root);
                arr.push(curtab);
            }
        }

        comment.push(curcomment);
        head.push(memberobj.name.name);
    });

    arr[0].data.push(comment);
    arr[0].data.push(head);

    let destarr = mergeExcel(arrold, arr);

    var exceldata = xlsx.build(destarr);
    fs.writeFileSync(path, exceldata, 'binary');
}

function exportExcel(path, obj, callback) {
    if (!fs.existsSync(path)) {
        fs.mkdirSync(path);
    }

    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            if (obj[i].type == 'static') {
                var fn = base.getMemberName(obj[i].name);

                exportStatic(path + '/' + fn + '.xlsx', obj[i], callback, obj);
            }
        }

        return ;
    }
}

exports.exportExcel = exportExcel;


/**
 * This is a specialized field which shows a {@link Ext.picker.Date} when tapped. If it has a predefined value,
 * or a value is selected in the {@link Ext.picker.Date}, it will be displayed like a normal {@link Ext.field.Text}
 * (but not selectable/changable).
 *
 *     Ext.create('Ext.field.DatePicker', {
 *         label: 'Birthday',
 *         value: new Date()
 *     });
 *
 * {@link Ext.field.DatePicker} fields are very simple to implement, and have no required configurations.
 *
 * For more information regarding forms and fields, please review [Using Forms in Sencha Touch Guide](../../../components/forms.html)
 *
 * ## Examples
 *
 * It can be very useful to set a default {@link #value} configuration on {@link Ext.field.DatePicker} fields. In
 * this example, we set the {@link #value} to be the current date. You can also use the {@link #setValue} method to
 * update the value at any time.
 *
 *     @example miniphone preview
 *     Ext.create('Ext.form.Panel', {
 *         fullscreen: true,
 *         items: [
 *             {
 *                 xtype: 'fieldset',
 *                 items: [
 *                     {
 *                         xtype: 'datepickerfield',
 *                         label: 'Birthday',
 *                         name: 'birthday',
 *                         value: new Date()
 *                     }
 *                 ]
 *             },
 *             {
 *                 xtype: 'toolbar',
 *                 docked: 'bottom',
 *                 items: [
 *                     { xtype: 'spacer' },
 *                     {
 *                         text: 'setValue',
 *                         handler: function() {
 *                             var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
 *
 *                             var randomNumber = function(from, to) {
 *                                 return Math.floor(Math.random() * (to - from + 1) + from);
 *                             };
 *
 *                             datePickerField.setValue({
 *                                 month: randomNumber(0, 11),
 *                                 day  : randomNumber(0, 28),
 *                                 year : randomNumber(1980, 2011)
 *                             });
 *                         }
 *                     },
 *                     { xtype: 'spacer' }
 *                 ]
 *             }
 *         ]
 *     });
 *
 * When you need to retrieve the date from the {@link Ext.field.DatePicker}, you can either use the {@link #getValue} or
 * {@link #getFormattedValue} methods:
 *
 *     @example preview
 *     Ext.create('Ext.form.Panel', {
 *         fullscreen: true,
 *         items: [
 *             {
 *                 xtype: 'fieldset',
 *                 items: [
 *                     {
 *                         xtype: 'datepickerfield',
 *                         label: 'Birthday',
 *                         name: 'birthday',
 *                         value: new Date()
 *                     }
 *                 ]
 *             },
 *             {
 *                 xtype: 'toolbar',
 *                 docked: 'bottom',
 *                 items: [
 *                     {
 *                         text: 'getValue',
 *                         handler: function() {
 *                             var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
 *                             Ext.Msg.alert(null, datePickerField.getValue());
 *                         }
 *                     },
 *                     { xtype: 'spacer' },
 *                     {
 *                         text: 'getFormattedValue',
 *                         handler: function() {
 *                             var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
 *                             Ext.Msg.alert(null, datePickerField.getFormattedValue());
 *                         }
 *                     }
 *                 ]
 *             }
 *         ]
 *     });
 *
 *
 */
Ext.define('Ext.field.DatePicker', {
    extend: 'Ext.field.Select',
    alternateClassName: 'Ext.form.DatePicker',
    xtype: 'datepickerfield',
    requires: [
        'Ext.picker.Date',
        'Ext.DateExtras'
    ],

    /**
     * @event change
     * Fires when a date is selected
     * @param {Ext.field.DatePicker} this
     * @param {Date} newDate The new date
     * @param {Date} oldDate The old date
     */

    config: {
        ui: 'select',

        /**
         * @cfg {Object/Ext.picker.Date} picker
         * An object that is used when creating the internal {@link Ext.picker.Date} component or a direct instance of {@link Ext.picker.Date}.
         * @accessor
         */
        picker: true,

        /**
         * @cfg {Boolean}
         * @hide
         * @accessor
         */
        clearIcon: false,

        /**
         * @cfg {Object/Date} value
         * Default value for the field and the internal {@link Ext.picker.Date} component. Accepts an object of 'year',
         * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
         *
         * Example: {year: 1989, day: 1, month: 5} = 1st May 1989 or new Date()
         * @accessor
         */

        /**
         * @cfg {Boolean} destroyPickerOnHide
         * Whether or not to destroy the picker widget on hide. This save memory if it's not used frequently,
         * but increase delay time on the next show due to re-instantiation.
         * @accessor
         */
        destroyPickerOnHide: false,

        /**
         * @cfg {String} [dateFormat=Ext.util.Format.defaultDateFormat] The format to be used when displaying the date in this field.
         * Accepts any valid date format. You can view formats over in the {@link Ext.Date} documentation.
         */
        dateFormat: null,

        /**
         * @cfg {Object}
         * @hide
         */
        component: {
            useMask: true
        }
    },

    initialize: function() {
        var me = this,
            component = me.getComponent();

        me.callParent();

        component.on({
            scope: me,
            masktap: 'onMaskTap'
        });

        component.doMaskTap = Ext.emptyFn;

        if (Ext.browser.is.AndroidStock2) {
            component.input.dom.disabled = true;
        }
    },

    syncEmptyCls: Ext.emptyFn,

    applyValue: function(value) {
        if (!Ext.isDate(value) && !Ext.isObject(value)) {
            return null;
        }

        if (Ext.isObject(value)) {
            return new Date(value.year, value.month - 1, value.day);
        }

        return value;
    },

    updateValue: function(newValue, oldValue) {
        var me     = this,
            picker = me._picker;

        if (picker && picker.isPicker) {
            picker.setValue(newValue);
        }

        // Ext.Date.format expects a Date
        if (newValue !== null) {
            me.getComponent().setValue(Ext.Date.format(newValue, me.getDateFormat() || Ext.util.Format.defaultDateFormat));
        } else {
            me.getComponent().setValue('');
        }

        if (newValue !== oldValue) {
            me.fireEvent('change', me, newValue, oldValue);
        }
    },

    /**
     * Updates the date format in the field.
     * @private
     */
    updateDateFormat: function(newDateFormat, oldDateFormat) {
        var value = this.getValue();
        if (newDateFormat != oldDateFormat && Ext.isDate(value)) {
            this.getComponent().setValue(Ext.Date.format(value, newDateFormat || Ext.util.Format.defaultDateFormat));
        }
    },

    /**
     * Returns the {@link Date} value of this field.
     * If you wanted a formatted date use the {@link #getFormattedValue} method.
     * @return {Date} The date selected
     */
    getValue: function() {
        if (this._picker && this._picker instanceof Ext.picker.Date) {
            return this._picker.getValue();
        }

        return this._value;
    },

    /**
     * Returns the value of the field formatted using the specified format. If it is not specified, it will default to
     * {@link #dateFormat} and then {@link Ext.util.Format#defaultDateFormat}.
     * @param {String} format The format to be returned.
     * @return {String} The formatted date.
     */
    getFormattedValue: function(format) {
        var value = this.getValue();
        return (Ext.isDate(value)) ? Ext.Date.format(value, format || this.getDateFormat() || Ext.util.Format.defaultDateFormat) : value;
    },

    applyPicker: function(picker, pickerInstance) {
        if (pickerInstance && pickerInstance.isPicker) {
            picker = pickerInstance.setConfig(picker);
        }

        return picker;
    },

    getPicker: function() {
        var picker = this._picker,
            value = this.getValue();

        if (picker && !picker.isPicker) {
            picker = Ext.factory(picker, Ext.picker.Date);
            if (value != null) {
                picker.setValue(value);
            }
        }

        picker.on({
            scope: this,
            change: 'onPickerChange',
            hide  : 'onPickerHide'
        });

        this._picker = picker;

        return picker;
    },

    /**
     * @private
     * Listener to the tap event of the mask element. Shows the internal DatePicker component when the button has been tapped.
     */
    onMaskTap: function() {
        if (this.getDisabled()) {
            return false;
        }

        this.onFocus();

        return false;
    },

    /**
     * Called when the picker changes its value.
     * @param {Ext.picker.Date} picker The date picker.
     * @param {Object} value The new value from the date picker.
     * @private
     */
    onPickerChange: function(picker, value) {
        var me = this,
            oldValue = me.getValue();

        me.setValue(value);
        me.fireEvent('select', me, value);
        me.onChange(me, value, oldValue);
    },

    /**
     * Override this or change event will be fired twice. change event is fired in updateValue
     * for this field. TOUCH-2861
     */
    onChange: Ext.emptyFn,

    /**
     * Destroys the picker when it is hidden, if
     * {@link Ext.field.DatePicker#destroyPickerOnHide destroyPickerOnHide} is set to `true`.
     * @private
     */
    onPickerHide: function() {
        var me     = this,
            picker = me.getPicker();

        if (me.getDestroyPickerOnHide() && picker) {
            picker.destroy();
            me._picker = me.getInitialConfig().picker || true;
        }
    },

    reset: function() {
        this.setValue(this.originalValue);
    },

    onFocus: function(e) {
        var component = this.getComponent();
        this.fireEvent('focus', this, e);

        if (Ext.os.is.Android4) {
            component.input.dom.focus();
        }
        component.input.dom.blur();

        if (this.getReadOnly()) {
            return false;
        }

        this.isFocused = true;

        this.getPicker().show();
    },

    // @private
    destroy: function() {
        var picker = this._picker;

        if (picker && picker.isPicker) {
            picker.destroy();
        }

        this.callParent(arguments);
    }
    //<deprecated product=touch since=2.0>
}, function() {
    this.override({
        getValue: function(format) {
            if (format) {
                //<debug warn>
                Ext.Logger.deprecate("format argument of the getValue method is deprecated, please use getFormattedValue instead", this);
                //</debug>
                return this.getFormattedValue(format);
            }
            return this.callOverridden();
        }
    });

    /**
     * @method getDatePicker
     * @inheritdoc Ext.field.DatePicker#getPicker
     * @deprecated 2.0.0 Please use #getPicker instead
     */
    Ext.deprecateMethod(this, 'getDatePicker', 'getPicker');
    //</deprecated>
});


var Route = require('./route');
var parseURL = require('./url').parse;

/**
 * Prints out debugging to console if `ghosttrain.get('debug')` is true
 *
 * @param {GhostTrain} gt
 * @param {String} out
 */

function debug (gt) {
  var args = arrayify(arguments);
  args.splice(0, 1); // Pop first `gt` argument
  if (gt.get('debug'))
    console.log.apply(console, ['GhostTrain debug: '].concat(args));
}
exports.debug = debug;

/**
 * `findRoute` takes a verb and a `url` and returns a matching
 * GhostTrain.Route, or `null` if no matches. `url` can be a string
 * that gets parsed, or an already parsed (./lib/url#parse) URL object.
 *
 * @param {GhostTrain} ghosttrain
 * @param {String} verb
 * @param {String|Object} url
 * @return {Route|null}
 */

function findRoute (ghosttrain, verb, url) {
  // Extract path from the object or string
  var path = url.pathname ? url.pathname : parseURL(url);

  var routes = ghosttrain.routes[verb.toLowerCase()];
  if (!routes) return null;
  for (var i = 0; i < routes.length; i++)
    if (routes[i].match(path))
      return routes[i];
  return null;
};
exports.findRoute = findRoute;

/**
 * Clones an object properties onto a new object
 *
 * @param {Object} obj
 * @return {Object}
 */

function clone (obj) {
  var newObj = {};

  // Return a new obj if no `obj` passed in
  if (!obj || typeof obj !== 'object' || Array.isArray(obj))
    return newObj;

  for (var prop in obj)
    newObj[prop] = obj[prop];
  return newObj;
}
exports.clone = clone;

/**
 * Turns a url string or a parsed url object (./lib/url#parse)
 * into a string of the URL with host/port/protocol info stripped
 *
 * requestURL('http://localhost:9999/search?q=myquery'); // '/search?q=myquery'
 *
 * @param {String|Object} url
 * @return {String}
 */

function requestURL (url) {
  var parsedURL = url.pathname ? url : parseURL(url, true);

  return parsedURL.pathname + (parsedURL.search || '');
}
exports.requestURL = requestURL;

/**
 * Returns a function that acts as `app.VERB(path, route)`; helper function
 * used in `./lib/ghosttrain.js`.
 *
 * @param {String} verb
 * @return {Function{
 */

function addRoute (verb) {
  return function (path, fn) {
    // Support all HTTP verbs
    if (!this.routes[verb])
      this.routes[verb] = [];

    this.routes[verb].push(new Route(verb, path, fn, {
      sensitive: this.settings['case sensitive routing'],
      strict: this.settings['strict routing']
    }));
  };
}
exports.addRoute = addRoute;

/**
 * A function that takes a `name` that returns a function
 * that throws an error regarding an unsupported function of `name`,
 * to be called when invoking an unsupported function.
 *
 * @param {String} name
 * @return {Function}
 */

function unsupported (name) {
  return function () {
    throw new Error('`' + name + '` is currently unsupported');
  };
}
exports.unsupported = unsupported;

/**
 * Arrayify `arguments`
 *
 * @param {Argument} args
 * @return {Array}
 */

function arrayify (args) {
 return Array.prototype.slice.call(args, 0);
}
exports.arrayify = arrayify;

/**
 * From Express:
 * Normalize the given path string,
 * returning a regular expression.
 *
 * An empty array should be passed,
 * which will contain the placeholder
 * key names. For example "/user/:id" will
 * then contain ["id"].
 *
 * @param  {String|RegExp|Array} path
 * @param  {Array} keys
 * @param  {Boolean} sensitive
 * @param  {Boolean} strict
 * @return {RegExp}
 */

function pathRegexp (path, keys, sensitive, strict) {
  if (Object.prototype.toString.call(path) == '[object RegExp]') return path;
  if (Array.isArray(path)) path = '(' + path.join('|') + ')';

  path = path
    .concat(strict ? '' : '/?')
    .replace(/\/\(/g, '(?:/')
    .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
      keys.push({ name: key, optional: !! optional });
      slash = slash || '';
      return ''
        + (optional ? '' : slash)
        + '(?:'
        + (optional ? slash : '')
        + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
        + (optional || '')
        + (star ? '(/*)?' : '');
    })
    .replace(/([\/.])/g, '\\$1')
    .replace(/\*/g, '(.*)');

  return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
exports.pathRegexp = pathRegexp;

/**
 * Status codes from Node core
 * https://github.com/joyent/node/blob/master/lib/_http_server.js
 */

var STATUS_CODES = exports.STATUS_CODES = {
  100 : 'Continue',
  101 : 'Switching Protocols',
  102 : 'Processing',                 // RFC 2518, obsoleted by RFC 4918
  200 : 'OK',
  201 : 'Created',
  202 : 'Accepted',
  203 : 'Non-Authoritative Information',
  204 : 'No Content',
  205 : 'Reset Content',
  206 : 'Partial Content',
  207 : 'Multi-Status',               // RFC 4918
  300 : 'Multiple Choices',
  301 : 'Moved Permanently',
  302 : 'Moved Temporarily',
  303 : 'See Other',
  304 : 'Not Modified',
  305 : 'Use Proxy',
  307 : 'Temporary Redirect',
  400 : 'Bad Request',
  401 : 'Unauthorized',
  402 : 'Payment Required',
  403 : 'Forbidden',
  404 : 'Not Found',
  405 : 'Method Not Allowed',
  406 : 'Not Acceptable',
  407 : 'Proxy Authentication Required',
  408 : 'Request Time-out',
  409 : 'Conflict',
  410 : 'Gone',
  411 : 'Length Required',
  412 : 'Precondition Failed',
  413 : 'Request Entity Too Large',
  414 : 'Request-URI Too Large',
  415 : 'Unsupported Media Type',
  416 : 'Requested Range Not Satisfiable',
  417 : 'Expectation Failed',
  418 : 'I\'m a teapot',              // RFC 2324
  422 : 'Unprocessable Entity',       // RFC 4918
  423 : 'Locked',                     // RFC 4918
  424 : 'Failed Dependency',          // RFC 4918
  425 : 'Unordered Collection',       // RFC 4918
  426 : 'Upgrade Required',           // RFC 2817
  428 : 'Precondition Required',      // RFC 6585
  429 : 'Too Many Requests',          // RFC 6585
  431 : 'Request Header Fields Too Large',// RFC 6585
  500 : 'Internal Server Error',
  501 : 'Not Implemented',
  502 : 'Bad Gateway',
  503 : 'Service Unavailable',
  504 : 'Gateway Time-out',
  505 : 'HTTP Version Not Supported',
  506 : 'Variant Also Negotiates',    // RFC 2295
  507 : 'Insufficient Storage',       // RFC 4918
  509 : 'Bandwidth Limit Exceeded',
  510 : 'Not Extended',               // RFC 2774
  511 : 'Network Authentication Required' // RFC 6585
};


/**
 * node.js CLI module runner
 *
 * Licensed under MIT.
 * Copyright (c) 2013 David Smith <https://github.com/codeactual/>
 */

'use strict';

/**
 * Reference to ImpulseBin.
 */
exports.ImpulseBin = ImpulseBin;

/**
 * Create a new ImpulseBin.
 *
 * @return {object}
 */
exports.create = function create() { return new ImpulseBin(); };

/**
 * Extend ImpulseBin.prototype.
 *
 * @param {object} ext
 * @return {object} Merge result.
 */
exports.extend = function extendProto(ext) { return extend(ImpulseBin.prototype, ext); };

const util = require('util');
const sprintf = util.format;

const configurable = require('configurable');
const extend = require('extend');

/**
 * ImpulseBin constructor.
 *
 *     const bin = require('impulse-bin').create();
 *
 * Example configuration:
 *
 *     bin
 *       .set('adapter', 'optimist')
 *       .set('quietOption', 'silent');
 *
 * Configuration:
 *
 * - `{string} [adapter='commander]` Valid name of adapter in `./lib/adapter/`
 * - `{string} [quietOption='quiet']` Silence all loggers on `--quiet`
 * - `{string} [requiredOptionTmpl='--%s is required']` `exitOnMissingOption()` message template
 * - `{string} [verboseOption='verbose']` Enable verbose logger on `--verbose`
 * - `{string} [verboseLogName='[verbose]']` Prepended to each message
 * - `{string} [stdoutLogName='[stdout]']` Prepended to each message
 * - `{string} [stderrLogName='[stderr]']` Prepended to each message
 *
 * Properties:
 *
 * - `{object} adapter` Ex. `require('./lib/adapter/commander')`
 * - `{object} console` `LongCon` instance
 * - `{object} options` CLI options extracted by adapter
 * - `{object} provider` commander.js, optimist, etc.
 *
 * @see LongCon https://github.com/codeactual/long-con/blob/master/docs/LongCon.md
 */
function ImpulseBin() {
  this.settings = {
    adapter: 'commander',
    quietOption: 'quiet',
    requiredOptionTmpl: '--%s is required',
    verboseOption: 'verbose',
    verboseLogName: '[verbose]',
    stdoutLogName: '[stdout]',
    stderrLogName: '[stderr]'
  };

  this.console = require('long-con').create();

  // Assigned in run():
  this.adapter = null;
  this.options = null;
  this.provider = null;
}

configurable(ImpulseBin.prototype);

/**
 * Wrap adapter require() for stubbing.
 *
 * @return {object}
 * @api private
 */
ImpulseBin.prototype.loadAdapter = function loadAdapter() {
  return require('../adapter/' + this.get('adapter'));
};

/**
 * Run the handler function with a prepared context.
 *
 * Examples:
 *
 *     bin.run(commander, require('./path/to/handler/module'));
 *     bin.run(optimist, require('./path/to/handler/module'));
 *     yield bin.runGenerator(commander, require('./path/to/handler/module'));
 *
 * `handler` module must export:
 *
 * - `{function} init` Set option/arg expectations
 *   - Receives one argument: `provider` object
 * - `{function} run` Respond to parsed options/args
 *   - Receives no arguments
 *
 * @param {object} provider Ex. commander.js or optimist module
 * @param {object} handler User-defined module with init/run/etc. functions
 * @param {mixed} [args]* Remaining args are passed to handler module's `run`.
 */
ImpulseBin.prototype.run = function run(provider, handler) {
  this.applySettings(provider, handler);
  handler.run.apply(this.prepareContext(provider), [].slice.call(arguments, 2));
};

/**
 * GeneratorFunction compatible version of ImpulseBin.prototype.run.
 */
ImpulseBin.prototype.runGenerator = function *runGenerator(provider, handler) {
  this.applySettings(provider, handler);
  yield handler.run.apply(this.prepareContext(provider), [].slice.call(arguments, 2));
};

/**
 * Create internal console/adapter/etc. properties based on user settings.
 *
 * @param {object} provider Ex. commander.js or optimist module
 * @param {object} handler User-defined module with init/run/etc. functions
 * @api private
 */
ImpulseBin.prototype.applySettings = function runGenerator(provider, handler) {
  this.provider = provider;
  this.adapter = this.loadAdapter();

  handler.init.call(null, this.provider);

  this.options = this.adapter.options(provider);

  // Used internally and available by handler for optional use.
  this.stderr = this.console.create(this.get('stderrLogName'), console.error, 'red'); // eslint-disable-line no-console
  this.stdout = this.console.create(this.get('stdoutLogName'), console.log); // eslint-disable-line no-console
  this.verbose = this.createVerbose(console.log); // eslint-disable-line no-console

  this.console.set('quiet', this.options[this.get('quietOption')]);
};

/**
 * Build a context object for the handler function.
 *
 * @param {object} provider Ex. commander.js or optimist module
 * @return {object}
 * @api private
 */
ImpulseBin.prototype.prepareContext = function runGenerator(provider) {
  return {
    provider: provider,

    args: this.adapter.args(provider),
    options: this.options,

    console: this.console,
    clc: require('cli-color'),

    createVerbose: this.createVerbose.bind(this),
    stderr: this.stderr,
    stdout: this.stdout,
    verbose: this.verbose.bind(this),

    exit: this.exit.bind(this),
    exitOnMissingOption: this.exitOnMissingOption.bind(this)
  };
};

/**
 * Create a logger that respects `--verbose`, or the flag with a name
 * matching the `verboseOption` config value.
 *
 * Examples:
 *
 *     const log = bin.createVerbose();
 *     fn('key: %s', key);
 *     fn.push('method entered: %s', name);
 *     fn.pop('method exited: %s', name);
 *
 * @param {mixed} args* `LongCon#create` arguments
 * @return {function} `long-con` logger function
 * @see LongCon https://github.com/codeactual/long-con/blob/master/docs/LongCon.md
 */
ImpulseBin.prototype.createVerbose = function createVerbose() {
  if (!this.options[this.get('verboseOption')]) { return impulseBinNoOp; }

  const args = [this.get('verboseLogName')].concat([].slice.call(arguments));
  return this.console.create.apply(this.console, args);
};

/**
 * Exit process.
 *
 * Examples:
 *
 *     bin.exit('bailing'); // status code = 1
 *     bin.exit('no more work', 0); // status code = 0
 *     bin.exit('no config file', 2); // status code = 2
 *
 * @param {string} msg Message sent to stderr
 * @param {number} [code=1] Status code
 */
ImpulseBin.prototype.exit = function exit(msg, code) {
  this.stderr(msg);
  process.exit(typeof code === 'undefined' ? 1 : code);
};

/**
 * Exit if the given CLI options are undefined.
 *
 * Examples:
 *
 *     bin.exitOnMissingOption('config', 2); // status code = 2
 *     bin.exitOnMissingOption(['config', 'file'], 3); // status code = 3
 *
 * @param {string|array} key
 * @param {number} exitCode
 */
ImpulseBin.prototype.exitOnMissingOption = function exitOnMissingOption(key, exitCode) {
  const _this = this;
  [].concat(key).forEach(function forEachkey(key) {
    if (typeof _this.options[key] === 'undefined') {
      if (_this.adapter.help) {
        _this.adapter.help(_this.provider);
      }
      _this.exit(sprintf(_this.get('requiredOptionTmpl'), key), exitCode);
    }
  });
};

function impulseBinNoOp() {}