/*
 * OAuthRequest
 * https://github.com/bytespider/jsOAuth
 *
 * Copyright (c) 2012 Rob Griffiths
 * Licensed under the MIT license.
 */

var Url = require("./Url");
var Querystring = require("./Querystring");
var Cryptography = require("./Cryptography");
var btoa = require("./Base64").btoa;

/**
 * rfc3986 compatable encode of a string
 *
 * @param {String} string
 */

function encode(string) {
    function hex(code) {
        var hex_code = code.toString(16).toUpperCase();
        if (hex_code.length < 2) {
            hex_code = 0 + hex_code;
        }
        return '%' + hex_code;
    }

    string = string + '';
    var reserved_chars = /[ :\/?#\[\]@!$&'()*+,;=<>"{}|\\`\^%\r\n\u0080-\uffff]/;
    var str_len = string.length;
    var i;
    var string_arr = string.split('');
    var c;

    for (i = 0; i < str_len; i += 1) {
        if (c = string_arr[i].match(reserved_chars)) {
            c = c[0].charCodeAt(0);

            if (c < 128) {
                string_arr[i] = hex(c);
            } else if (c < 2048) {
                string_arr[i] = hex(192 + (c >> 6)) + hex(128 + (c & 63));
            } else if (c < 65536) {
                string_arr[i] = hex(224 + (c >> 12)) + hex(128 + ((c >> 6) & 63)) + hex(128 + (c & 63));
            } else if (c < 2097152) {
                string_arr[i] = hex(240 + (c >> 18)) + hex(128 + ((c >> 12) & 63)) + hex(128 + ((c >> 6) & 63)) + hex(128 + (c & 63));
            }
        }
    }

    return string_arr.join('');
}

/**
 * rfc3986 compatable decode of a string
 *
 * @param {String} string
 */

function decode(string) {
    return string.replace(/%[a-fA-F0-9]{2}/ig, function (match) {
        return String.fromCharCode(parseInt(match.replace('%', ''), 16));
    });
}

/**
 * Generate a timestamp for the request
 */

function getTimestamp() {
    var date = new Date();
    return parseInt(date.getTime() / 1000, 10);
}

/**
 * Generate a nonce for the request
 *
 * @param key_length {number} Optional nonce length
 */

function getNonce(key_length) {
    function rand() {
        return Math.floor(Math.random() * chars.length);
    }

    key_length = key_length || 64;

    var key_bytes = key_length / 8;
    var value = '';
    var key_iter = key_bytes / 4;
    var key_remainder = key_bytes % 4;
    var i;
    var chars = ['20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A', '2B', '2C', '2D', '2E', '2F', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3A', '3B', '3C', '3D', '3E', '3F', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4A', '4B', '4C', '4D', '4E', '4F', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5A', '5B', '5C', '5D', '5E', '5F', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6A', '6B', '6C', '6D', '6E', '6F', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7A', '7B', '7C', '7D', '7E'];

    for (i = 0; i < key_iter; i += 1) {
        value += chars[rand()] + chars[rand()] + chars[rand()] + chars[rand()];
    }

    // handle remaing bytes
    for (i = 0; i < key_remainder; i += 1) {
        value += chars[rand()];
    }

    return value;
}

/**
 * Get a string of the parameters for the OAuth Authorization header
 *
 * @param params {object} A key value paired object of data
 *                           example: {'q':'foobar'}
 *                           for GET this will append a query string
 */

function toHeaderString(params, realm) {
    var arr = [];
    var i;

    for (i in params) {
        if (typeof params[i] !== 'object' && params[i] !== '' && params[i] !== undefined) {
            arr.push(encode(i) + '="' + encode(params[i]) + '"');
        }
    }

    arr.sort();
    if (realm) {
        arr.unshift('realm="' + encode(realm) + '"');
    }

    return arr.join(', ');
}

/**
 * Generate a signature base string for the request
 *
 * @param method {string} ['GET', 'POST', 'PUT', ...]
 * @param url {string} A valid http(s) url
 * @param header_params A key value paired object of additional headers
 * @param query_params {object} A key value paired object of data
 *                               example: {'q':'foobar'}
 *                               for GET this will append a query string
 */

function toSignatureBaseString(method, url, header_params, query_params) {
    var arr = [];
    var i;

    for (i in header_params) {
        if (header_params[i] !== undefined && header_params[i] !== '') {
            arr.push([encode(i), encode(header_params[i] + '')]);
        }
    }

    for (i in query_params) {
        if (query_params[i] !== undefined && query_params[i] !== '') {
            arr.push([encode(i), encode(query_params[i] + '')]);
        }
    }

    arr = arr.sort(function lexicalSort(a, b) {
        if (a[0] < b[0]) {
            return -1;
        } else if (a[0] > b[0]) {
            return 1;
        } else {
            if (a[1] < b[1]) {
                return -1;
            } else if (a[1] > b[1]) {
                return 1;
            } else {
                return 0;
            }
        }
    }).map(function (el) {
        return el.join("=");
    });

    return [method, encode(url), encode(arr.join('&'))].join('&');
}

function OAuthRequest() {
    if (this.transport.toLowerCase() == 'default') {
        this.transport = Object.keys(OAuthRequest.transports)[0];
    }

    if (!(this.transport in OAuthRequest.transports)) {
        throw 'Transport ' + this.transport + ' not registered';
    }

    if (!OAuthRequest.transports[this.transport].test()) {
        throw 'Transport ' + this.transport + ' is not supported in this environment';
    }

    this.request = OAuthRequest.transports[this.transport].factory();
    this.request.headers = {};
}

OAuthRequest.UNSENT = 0;
OAuthRequest.OPENED = 1;
OAuthRequest.HEADERS_RECEIVED = 2;
OAuthRequest.LOADING = 3;
OAuthRequest.DONE = 4;

OAuthRequest.prototype = {
    constructor: OAuthRequest,
    set onreadystatechange(handler) {
        this.request.onreadystatechange = handler;
    },
    get onreadystatechange() {
        return this.request.onreadystatechange;
    },
    set onerror(handler) {
        this.request.onerror = handler;
    },
    get onerror() {
        return this.request.onerror;
    },
    get readyState() {
        return this.request.readyState;
    },

    // request
    open: function (method, url, async, user, password) {
        var xhr = this.request;

        xhr.method = method.toUpperCase();
        xhr.url = Url.parse(url, true);
        xhr.async = async;
        xhr.user = password;

        xhr.open(xhr.method, xhr.url, xhr.async, xhr.user, xhr.password);
    },

    setRequestHeader: function (header, value) {
        this.request.headers[header] = value;
        this.request.setRequestHeader(header, value);
    },

    send: function (data) {
        var xhr = this.request;
        var header_params = {
            'oauth_callback': this.callbackUrl,
            'oauth_consumer_key': this.applicationKey,
            'oauth_token': this.accessTokenKey,
            'oauth_signature_method': this.signatureMethod,
            'oauth_timestamp': getTimestamp(),
            'oauth_nonce': getNonce(),
            'oauth_verifier': this.verifier,
            'oauth_version': '1.0'
        };
        var signature_data = {};

        var content_type = xhr.headers['Content-Type'] || 'application/x-www-form-urlencoded';
        if (content_type === 'application/x-www-form-urlencoded') {
            var query = xhr.url.query;
            for (var i in query) {
                if (query.hasOwnProperty(i)) {
                    signature_data[i] = query[i];
                }
            }

            if (data) {
                data = Querystring.parse(data);
                for (i in data) {
                    if (query.hasOwnProperty(i)) {
                        signature_data[i] = data[i];
                    }
                }
            }
        }

        var url = xhr.url.protocol + '//' + xhr.url.host + xhr.url.path;
        var signature_string = toSignatureBaseString(xhr.method, url, header_params, signature_data);
        var signature = OAuthRequest.signatureMethod[this.signatureMethod](this.applicationSecret, this.accessTokenSecret, signature_string);
        header_params['oauth_signature'] = signature;

        xhr.setRequestHeader('Authorization', 'OAuth ' + toHeaderString(header_params, this.realm));

        xhr.send(data);
    },

    abort: function () {
        this.request.abort();
    },

    // response
    get status() {
        return this.request.status;
    },

    get statusText() {
        return this.request.statusText;
    },

    getResponseHeader: function (header) {
        this.request.getResponseHeader(header);
    },

    getAllResponseHeaders: function () {
        return this.request.getAllResponseHeaders();
    },

    get responseText() {
        return this.request.responseText;
    },

    get responseXML() {
        return this.request.responseXML;
    },

    UNSENT:                 OAuthRequest.UNSENT,
    OPENED:                 OAuthRequest.OPENED,
    HEADERS_RECEIVED:       OAuthRequest.HEADERS_RECEIVED,
    LOADING:                OAuthRequest.LOADING,
    DONE:                   OAuthRequest.DONE,

    // Events
    /**
     * Adds an event listener
     */
    addEventListener: function (type, handler, useCapture) {
        this.request.addEventListener(type, handler, useCapture);
    },

    /**
     * Removes an event listener
     */
    removeEventListener: function (type, handler, useCapture) {
        this.request.removeEventListener(type, handler, useCapture);
    },

    /**
     * Dispatches an event
     */
    dispatchEvent: function (event) {
        this.request.dispatchEvent(event);
    },

    // OAuthRequest specifics
    applicationKey: '',
    applicationSecret: '',
    accessTokenKey: '',
    accessTokenSecret: '',
    realm: '',
    callbackUrl: '',
    signatureMethod: 'HMAC-SHA1',
    transport: 'Default'
};

OAuthRequest.transports = {
    'CommonJS': require("./Transport/CommonJS")
};

OAuthRequest.signatureMethod = {
    /**
     * Sign the request
     *
     * @param consumer_secret {string} the consumer secret
     * @param token_secret {string}  the token secret
     * @param signature_base {string}  the signature base string
     */
    'HMAC-SHA1': function (application_secret, token_secret, signature_base) {
        var passphrase;
        var signature;

        application_secret = encode(application_secret);
        token_secret = encode(token_secret || '');

        passphrase = application_secret + '&' + token_secret;
        signature = Cryptography.hmac(Cryptography.SHA1, passphrase, signature_base);

        return btoa(signature);
    }
};

module.exports = OAuthRequest;


var util = require('util');
var logger = require('pomelo-logger').getLogger('pomelo-config-reader', __filename);
var moment = require('moment');
var format = require('string-format');
format.extend(String.prototype);
/**
 * Tbl model `new Tbl()`
 *
 * @param {Array}
 *
 */
var Tbl = function(fileName, tmpL, idx, indexColumn) {
    var self = this;
    var fNameList = tmpL.splice(0, 1)[0];
    var fTypeList = tmpL.splice(0, 1)[0];
    if (indexColumn != undefined) {
        idx = fNameList[indexColumn];
    }
    var fieldsName = {};
    fNameList.forEach(function(value, index) {
        fieldsName[value] = index;
    });
    self.data = {};
    tmpL.forEach(function(item) {
        var obj = {};
        for (var k in fieldsName) {
            //obj[k] = item[fieldsName[k]];
            var retVal = parseValue(item[fieldsName[k]], fTypeList[fieldsName[k]]);
            if (retVal == undefined) {
                logger.error('Parse config file "{}" \'s filed "{}" error,the value is:"{}",the type is:"{}"'.format(fileName, k, item[fieldsName[k]], fTypeList[fieldsName[k]]))
            }
            obj[k] = retVal
        }
        if (self.data.hasOwnProperty(obj[idx])) {
            logger.error('Index column can not have duplicate value,please check the config file :%s', fileName + '.csv');
        } else {
            if (obj[idx]) {
                self.data[obj[idx]] = obj;
            } else {
                logger.error('No `%s` exists in tbl=%s', idx, util.inspect(fNameList, {
                    showHidden: false,
                    depth: 1
                }));
            }
        }
    });
};

var parseValue = function(value, type) {
    var strType = type.trim();
    if (value === "") return undefined
    switch (strType) {
        case "Int":
            var retVal = parseInt(value);
            if (isNaN(retVal)) {
                logger.error('parse "{}" to "{}" error,more detail below'.format(value, type));
                return undefined;
            }
            return retVal;
        case "Float":
            var retVal = parseFloat(value);
            if (isNaN(retVal)) {
                logger.error('parse "{}" to "{}" error,more detail below'.format(value, type));
                return undefined;
            }
            return retVal;
            break;
        case "String":
            return value;
        case "Boolean":
            return Boolean(value); // (false,"",0,NaN,null,undefined) is false ,else thing is true
        case "TS":
            if (!moment(value, "YYYY/MM/DD HH:mm", true).isValid()) {
                logger.error('parse "{}" to "{}" error,more detail below'.format(value, type));
                return undefined;
            }
            return Date.parse(value);
        case "Array4Int":
            var intList = value.split('|');
            if (intList.length == 1) {
                logger.warn('warn!the length of int[] is 1');
            }
            var retVal = [];
            intList.map(function(num) {
                var tmp = parseInt(num);
                if (isNaN(tmp)) {
                    logger.error('error!the int[] contain NaN(Not a Number) value');
                }
                retVal.push(tmp);
            })
            return retVal;
        case "Array4String":
            var strList = value.split('|');
            if (strList.length == 1) {
                logger.warn('warn!the length of string[] is 1');
            }
            var retVal = [];
            strList.map(function(str) {
                retVal.push(str);
            });
            return retVal;
        default:
            logger.error('unknown type detected "{}",more detail below'.format(type));
            return undefined;
    }
};
/**
 * find item by id
 *
 * @param id
 * @return {Obj}
 * @api public
 */
Tbl.prototype.findById = function(id) {
    return this.data[id];
};

/**
 * find item by function
 * @param func  provided testing function,accept a parameter 'element'
 * @return {Obj} first satisfies element
 * @api public
 */
Tbl.prototype.findByFunc = function(func) {
    for (var key in this.data) {
        if (func.call(null, this.data[key])) {
            return this.data[key];
        }
    }
    return undefined;
};
/**
 * find all item
 *
 * @return {array}
 * @api public
 */
Tbl.prototype.findAll = function() {
    return this.data;
};

///////////////////////////////////////////////////////
/**
 * Expose 'Tbl' constructor.
 */
module.exports = Tbl;


/**
 * Utility class for generating different styles of message boxes. The framework provides a global singleton
 * {@link Ext.Msg} for common usage which you should use in most cases.
 *
 * If you want to use {@link Ext.MessageBox} directly, just think of it as a modal {@link Ext.Container}.
 *
 * Note that the MessageBox is asynchronous. Unlike a regular JavaScript `alert` (which will halt browser execution),
 * showing a MessageBox will not cause the code to stop. For this reason, if you have code that should only run _after_
 * some user feedback from the MessageBox, you must use a callback function (see the `fn` configuration option parameter
 * for the {@link #method-show show} method for more details).
 *
 *     @example preview
 *     Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
 *
 * Checkout {@link Ext.Msg} for more examples.
 *
 */
Ext.define('Ext.MessageBox', {
    extend  : 'Ext.Sheet',
    requires: [
        'Ext.Toolbar',
        'Ext.field.Text',
        'Ext.field.TextArea',
        'Ext.util.InputBlocker'
    ],

    config: {
        /**
         * @cfg
         * @inheritdoc
         */
        ui: 'dark',

        /**
         * @cfg
         * @inheritdoc
         */
        baseCls: Ext.baseCSSPrefix + 'msgbox',

        /**
         * @cfg {String} iconCls
         * CSS class for the icon. The icon should be 40px x 40px.
         * @accessor
         */
        iconCls: null,

        /**
         * @cfg
         * @inheritdoc
         */
        showAnimation: {
            type: 'popIn',
            duration: 250,
            easing: 'ease-out'
        },

        /**
         * @cfg
         * @inheritdoc
         */
        hideAnimation: {
            type: 'popOut',
            duration: 250,
            easing: 'ease-out'
        },

        /**
         * Override the default `zIndex` so it is normally always above floating components.
         */
        zIndex: 999,

        /**
         * @cfg {Number} defaultTextHeight
         * The default height in pixels of the message box's multiline textarea if displayed.
         * @accessor
         */
        defaultTextHeight: 75,

        /**
         * @cfg {String} title
         * The title of this {@link Ext.MessageBox}.
         * @accessor
         */
        title: null,

        /**
         * @cfg {Array/Object} buttons
         * An array of buttons, or an object of a button to be displayed in the toolbar of this {@link Ext.MessageBox}.
         */
        buttons: null,

        /**
         * @cfg {String} message
         * The message to be displayed in the {@link Ext.MessageBox}.
         * @accessor
         */
        message: null,

        /**
         * @cfg {String} msg
         * The message to be displayed in the {@link Ext.MessageBox}.
         * @removed 2.0.0 Please use {@link #message} instead.
         */

        /**
         * @cfg {Object} prompt
         * The configuration to be passed if you want an {@link Ext.field.Text} or {@link Ext.field.TextArea} field
         * in your {@link Ext.MessageBox}.
         *
         * Pass an object with the property `multiLine` with a value of `true`, if you want the prompt to use a TextArea.
         *
         * Alternatively, you can just pass in an object which has an xtype/xclass of another component.
         *
         *     prompt: {
         *         xtype: 'textareafield',
         *         value: 'test'
         *     }
         *
         * @accessor
         */
        prompt: null,

        /**
         * @private
         */
        modal: true,

        /**
         * @cfg
         * @inheritdoc
         */
        layout: {
            type: 'vbox',
            pack: 'center'
        }
    },

    platformConfig: [{
        theme: ['Windows'],
        ui: 'light',
        showAnimation: {
            type: 'fadeIn'
        },
        hideAnimation: {
            type: 'fadeOut'
        }
    }, {
        theme: ['Blackberry', 'Blackberry103'],
        ui: 'plain'
    }, {
        theme: ['MoutainView']
    }],

    statics: {
        OK    : {iconCls: 'tickCls',     itemId: 'ok',  ui: 'action'},
        YES   : {iconCls: 'tickCls',    itemId: 'yes', ui: 'action'},
        NO    : {iconCls: 'crossCls',    itemId: 'no'},
        CANCEL: {iconCls: 'loopBackCls', itemId: 'cancel'},

        INFO    : Ext.baseCSSPrefix + 'msgbox-info',
        WARNING : Ext.baseCSSPrefix + 'msgbox-warning',
        QUESTION: Ext.baseCSSPrefix + 'msgbox-question',
        ERROR   : Ext.baseCSSPrefix + 'msgbox-error',

        OKCANCEL: [
            {iconCls: 'crossCls', itemId: 'cancel'},
            {iconCls: 'tickCls',     itemId: 'ok',  ui : 'action'}
        ],
        YESNOCANCEL: [
            {iconCls: 'loopBackCls', itemId: 'cancel'},
            {iconCls: 'crossCls', itemId: 'no'},
            {iconCls: 'tickCls', itemId: 'yes', ui: 'action'}
        ],
        YESNO: [
            {iconCls: 'crossCls', itemId: 'no'},
            {iconCls: 'tickCls', itemId: 'yes', ui: 'action'}
        ]
    },

    constructor: function(config) {
        config = config || {};

        if (config.hasOwnProperty('promptConfig')) {
            //<debug warn>
            Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
            //</debug>

            Ext.applyIf(config, {
                prompt: config.promptConfig
            });

            delete config.promptConfig;
        }

        if (config.hasOwnProperty('multiline') || config.hasOwnProperty('multiLine')) {
            config.prompt = config.prompt || {};
            Ext.applyIf(config.prompt, {
                multiLine: config.multiline || config.multiLine
            });

            delete config.multiline;
            delete config.multiLine;
        }

        this.defaultAllowedConfig = {};
        var allowedConfigs = ['ui', 'showAnimation', 'hideAnimation', 'title', 'message', 'prompt', 'iconCls', 'buttons', 'defaultTextHeight'],
            ln = allowedConfigs.length,
            i, allowedConfig;

        for (i = 0; i < ln; i++) {
            allowedConfig = allowedConfigs[i];
            this.defaultAllowedConfig[allowedConfig] = this.defaultConfig[allowedConfig];
        }

        this.callParent([config]);
    },

    /**
     * Creates a new {@link Ext.Toolbar} instance using {@link Ext#factory}.
     * @private
     */
    applyTitle: function(config) {
        if (typeof config == "string") {
            config = {
                title: config
            };
        }

        var minHeight = '1.3em';
        if (Ext.theme.is.Cupertino) {
            minHeight = '1.5em'
        } else if (Ext.filterPlatform('blackberry') || Ext.filterPlatform('ie10')) {
            minHeight = '2.6em';
        }

        Ext.applyIf(config, {
            docked: 'top',
            minHeight: minHeight,
            ui: Ext.filterPlatform('blackberry') ? 'light' : 'dark',
            cls   : this.getBaseCls() + '-title'
        });

        if (Ext.theme.is.Tizen) {
            Ext.applyIf(config, {
                centered: false
            });
        }

        return Ext.factory(config, Ext.Toolbar, this.getTitle());
    },

    /**
     * Adds the new {@link Ext.Toolbar} instance into this container.
     * @private
     */
    updateTitle: function(newTitle) {
        if (newTitle) {
            this.add(newTitle);
        }
    },

    /**
     * Adds the new {@link Ext.Toolbar} instance into this container.
     * @private
     */
    updateButtons: function(newButtons) {
        var me = this;

        // If there are no new buttons or it is an empty array, set newButtons
        // to false
        newButtons = (!newButtons || newButtons.length === 0) ? false : newButtons;

        if (newButtons) {
            if (me.buttonsToolbar) {
                me.buttonsToolbar.show();
                me.buttonsToolbar.removeAll();
                me.buttonsToolbar.setItems(newButtons);
            } else {
                var layout = {
                    type: 'hbox',
                    pack: 'center'
                };

                var isFlexed = Ext.theme.is.CupertinoClassic  || Ext.theme.is.MountainView  || Ext.theme.is.Blackberry || Ext.theme.is.Blackberry103;

                me.buttonsToolbar = Ext.create('Ext.Toolbar', {
                    docked: 'bottom',
                    defaultType: 'button',
                    defaults: {
                        flex: (isFlexed) ? 1 : undefined,
                        ui: (Ext.theme.is.Blackberry || Ext.theme.is.Blackberry103) ? 'action' : undefined
                    },
                    layout: layout,
                    ui: me.getUi(),
                    cls: me.getBaseCls() + '-buttons',
                    items: newButtons
                });

                me.add(me.buttonsToolbar);
            }
        } else if (me.buttonsToolbar) {
            me.buttonsToolbar.hide();
        }
    },

    /**
     * @private
     */
    applyMessage: function(config) {
        config = {
            html : config,
            cls  : this.getBaseCls() + '-text'
        };

        return Ext.factory(config, Ext.Component, this._message);
    },

    /**
     * @private
     */
    updateMessage: function(newMessage) {
        if (newMessage) {
            this.add(newMessage);
        }
    },

    getMessage: function() {
        if (this._message) {
            return this._message.getHtml();
        }

        return null;
    },

    /**
     * @private
     */
    applyIconCls: function(config) {
        config = {
            xtype : 'component',
            docked: 'left',
            width : 40,
            height: 40,
            baseCls: Ext.baseCSSPrefix + 'icon',
            hidden: (config) ? false : true,
            cls: config
        };

        return Ext.factory(config, Ext.Component, this._iconCls);
    },

    /**
     * @private
     */
    updateIconCls: function(newIconCls, oldIconCls) {
        //ensure the title and button elements are added first
        this.getTitle();
        this.getButtons();

        if (newIconCls) {
            this.add(newIconCls);
        }
        else {
            this.remove(oldIconCls);
        }
    },

    getIconCls: function() {
        var icon = this._iconCls,
            iconCls;

        if (icon) {
            iconCls = icon.getCls();
            return (iconCls) ? iconCls[0] : null;
        }

        return null;
    },

    /**
     * @private
     */
    applyPrompt: function(prompt) {
        if (prompt) {
            var config = {
                label: false
            };

            if (Ext.isObject(prompt)) {
                Ext.apply(config, prompt);
            }

            if (config.multiLine) {
                config.height = Ext.isNumber(config.multiLine) ? parseFloat(config.multiLine) : this.getDefaultTextHeight();
                return Ext.factory(config, Ext.field.TextArea, this.getPrompt());
            } else {
                return Ext.factory(config, Ext.field.Text, this.getPrompt());
            }
        }

        return prompt;
    },

    /**
     * @private
     */
    updatePrompt: function(newPrompt, oldPrompt) {
        if (newPrompt) {
            this.add(newPrompt);
        }

        if (oldPrompt) {
            this.remove(oldPrompt);
        }
    },

    // @private
    // pass `fn` config to show method instead
    onClick: function(button) {
        if (button) {
            var config = button.config.userConfig || {},
                initialConfig = button.getInitialConfig(),
                prompt = this.getPrompt();

            if (typeof config.fn == 'function') {
                button.disable();
                this.on({
                    hiddenchange: function() {
                        config.fn.call(
                            config.scope || null,
                            initialConfig.itemId || initialConfig.text,
                            prompt ? prompt.getValue() : null,
                            config
                        );
                        button.enable();
                    },
                    single: true,
                    scope: this
                });
            }

            if (config.input) {
                config.input.dom.blur();
            }
        }

        this.hide();
    },

    /**
     * Displays the {@link Ext.MessageBox} with a specified configuration. All
     * display functions (e.g. {@link #method-prompt}, {@link #alert}, {@link #confirm})
     * on MessageBox call this function internally, although those calls
     * are basic shortcuts and do not support all of the config options allowed here.
     *
     * Example usage:
     *
     *     @example
     *     Ext.Msg.show({
     *        title: 'Address',
     *        message: 'Please enter your address:',
     *        width: 300,
     *        buttons: Ext.MessageBox.OKCANCEL,
     *        multiLine: true,
     *        prompt : { maxlength : 180, autocapitalize : true },
     *        fn: function(buttonId) {
     *            alert('You pressed the "' + buttonId + '" button.');
     *        }
     *     });
     *
     * @param {Object} config An object with the following config options:
     *
     * @param {Object/Array} [config.buttons=false]
     * A button config object or Array of the same(e.g., `Ext.MessageBox.OKCANCEL` or `{text:'Foo', itemId:'cancel'}`),
     * or false to not show any buttons.
     *
     * @param {String} config.cls
     * A custom CSS class to apply to the message box's container element.
     *
     * @param {Function} config.fn
     * A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
     *
     * @param {String} config.fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
     * @param {String} config.fn.value Value of the input field if either `prompt` or `multiline` option is `true`.
     * @param {Object} config.fn.opt The config object passed to show.
     *
     * @param {Number} [config.width=auto]
     * A fixed width for the MessageBox.
     *
     * @param {Number} [config.height=auto]
     * A fixed height for the MessageBox.
     *
     * @param {Object} config.scope
     * The scope of the callback function
     *
     * @param {String} config.icon
     * A CSS class that provides a background image to be used as the body icon for the dialog
     * (e.g. Ext.MessageBox.WARNING or 'custom-class').
     *
     * @param {Boolean} [config.modal=true]
     * `false` to allow user interaction with the page while the message box is displayed.
     *
     * @param {String} [config.message= ]
     * A string that will replace the existing message box body text.
     * Defaults to the XHTML-compliant non-breaking space character ` `.
     *
     * @param {Number} [config.defaultTextHeight=75]
     * The default height in pixels of the message box's multiline textarea if displayed.
     *
     * @param {Boolean} [config.prompt=false]
     * `true` to prompt the user to enter single-line text. Please view the {@link Ext.MessageBox#method-prompt} documentation in {@link Ext.MessageBox}.
     * for more information.
     *
     * @param {Boolean} [config.multiline=false]
     * `true` to prompt the user to enter multi-line text.
     *
     * @param {String} config.title
     * The title text.
     *
     * @param {String} config.value
     * The string value to set into the active textbox element if displayed.
     *
     * @return {Ext.MessageBox} this
     */
    show: function(initialConfig) {
        Ext.util.InputBlocker.blockInputs();
        //if it has not been added to a container, add it to the Viewport.
        if (!this.getParent() && Ext.Viewport) {
            Ext.Viewport.add(this);
        }

        if (!initialConfig) {
            return this.callParent();
        }

        var config = Ext.Object.merge({}, {
            value: ''
        }, initialConfig);

        var buttons        = initialConfig.buttons || Ext.MessageBox.OK || [],
            buttonBarItems = [],
            userConfig     = initialConfig;

        Ext.each(buttons, function(buttonConfig) {
            if (!buttonConfig) {
                return;
            }

            buttonBarItems.push(Ext.apply({
                userConfig: userConfig,
                scope     : this,
                handler   : 'onClick'
            }, buttonConfig));
        }, this);

        config.buttons = buttonBarItems;

        if (config.promptConfig) {
            //<debug warn>
            Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
            //</debug>
        }
        config.prompt = (config.promptConfig || config.prompt) || null;

        if (config.multiLine) {
            config.prompt = config.prompt || {};
            config.prompt.multiLine = config.multiLine;
            delete config.multiLine;
        }

        config = Ext.merge({}, this.defaultAllowedConfig, config);

        this.setConfig(config);

        var prompt = this.getPrompt();
        if (prompt) {
            prompt.setValue(initialConfig.value || '');
        }

        this.callParent();

        return this;
    },

    /**
     * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt). If
     * a callback function is passed it will be called after the user clicks the button, and the `itemId` of the button
     * that was clicked will be passed as the only parameter to the callback.
     *
     * @param {String} title The title bar text.
     * @param {String} message The message box body text.
     * @param {Function} [fn] A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
     * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
     * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
     * @param {Object} fn.opt The config object passed to show.
     * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
     * Defaults to: the browser window
     *
     * @return {Ext.MessageBox} this
     */
    alert: function(title, message, fn, scope) {
        return this.show({
            title: title || null,
            message: message || null,
            buttons: Ext.MessageBox.OK,
            promptConfig: false,
            fn: function() {
                if (fn) {
                    fn.apply(scope, arguments);
                }
            },
            scope: scope
        });
    },

    /**
     * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm). If a callback
     * function is passed it will be called after the user clicks either button, and the id of the button that was
     * clicked will be passed as the only parameter to the callback (could also be the top-right close button).
     *
     * @param {String} title The title bar text.
     * @param {String} message The message box body text.
     * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
     * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
     * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
     * @param {Object} fn.opt The config object passed to show.
     * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
     *
     * Defaults to: the browser window
     *
     * @return {Ext.MessageBox} this
     */
    confirm: function(title, message, fn, scope) {
        return this.show({
            title       : title || null,
            message     : message || null,
            buttons     : Ext.MessageBox.YESNO,
            promptConfig: false,
            scope       : scope,
            fn: function() {
                if (fn) {
                    fn.apply(scope, arguments);
                }
            }
        });
    },

    /**
     * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to
     * JavaScript's prompt). The prompt can be a single-line or multi-line textbox. If a callback function is passed it
     * will be called after the user clicks either button, and the id of the button that was clicked (could also be the
     * top-right close button) and the text that was entered will be passed as the two parameters to the callback.
     *
     * Example usage:
     *
     *     @example
     *     Ext.Msg.prompt(
     *         'Welcome!',
     *         'What\'s your name going to be today?',
     *         function (buttonId, value) {
     *             console.log(value);
     *         },
     *         null,
     *         false,
     *         null,
     *         {
     *             autoCapitalize: true,
     *             placeHolder: 'First-name please...'
     *         }
     *     );
     *
     * @param {String} title The title bar text.
     * @param {String} message The message box body text.
     * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
     * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
     * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
     * @param {Object} fn.opt The config object passed to show.
     * @param {Object} scope The scope (`this` reference) in which the callback is executed.
     *
     * Defaults to: the browser window.
     *
     * @param {Boolean/Number} [multiLine=false] `true` to create a multiline textbox using the `defaultTextHeight` property,
     * or the height in pixels to create the textbox.
     *
     * @param {String} [value] Default value of the text input element.
     *
     * @param {Object} [prompt=true]
     * The configuration for the prompt. See the {@link Ext.MessageBox#cfg-prompt prompt} documentation in {@link Ext.MessageBox}
     * for more information.
     *
     * @return {Ext.MessageBox} this
     */
    prompt: function(title, message, fn, scope, multiLine, value, prompt) {
        return this.show({
            title    : title || null,
            message  : message || null,
            buttons  : Ext.MessageBox.OKCANCEL,
            scope    : scope,
            prompt   : prompt || true,
            multiLine: multiLine,
            value    : value,
            fn: function() {
                if (fn) {
                    fn.apply(scope, arguments);
                }
            }
        });
    }
}, function(MessageBox) {
    // <deprecated product=touch since=2.0>
    this.override({
        /**
         * @cfg {String} icon
         * Sets CSS class for icon.
         * @removed 2.0 Use #iconCls instead.
         */

        /**
         * Sets #icon.
         * @deprecated 2.0 Please use #setIconCls instead.
         * @param {String} iconCls A CSS class name or empty string to clear the icon.
         * @return {Ext.MessageBox} this
         */
        setIcon: function(iconCls, doLayout){
            //<debug warn>
            Ext.Logger.deprecate("Ext.MessageBox#setIcon is deprecated, use setIconCls instead", 2);
            //</debug>
            this.setIconCls(iconCls);

            return this;
        },

        /**
         * @inheritdoc Ext.MessageBox#setMessage
         * @deprecated 2.0.0 Please use #setMessage instead.
         */
        updateText: function(text){
            //<debug warn>
            Ext.Logger.deprecate("Ext.MessageBox#updateText is deprecated, use setMessage instead", 2);
            //</debug>
            this.setMessage(text);

            return this;
        }
    });
    // </deprecated>

    Ext.onSetup(function() {
        /**
         * @class Ext.Msg
         * @extends Ext.MessageBox
         * @singleton
         *
         * A global shared singleton instance of the {@link Ext.MessageBox} class.
         *
         * Allows for simple creation of various different alerts and notifications.
         *
         * To change any configurations on this singleton instance, you must change the
         * `defaultAllowedConfig` object.  For example to remove all animations on `Msg`:
         *
         *     Ext.Msg.defaultAllowedConfig.showAnimation = false;
         *     Ext.Msg.defaultAllowedConfig.hideAnimation = false;
         *
         * ## Examples
         *
         * ### Alert
         * Use the {@link #alert} method to show a basic alert:
         *
         *     @example preview
         *     Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
         *
         * ### Prompt
         * Use the {@link #method-prompt} method to show an alert which has a textfield:
         *
         *     @example preview
         *     Ext.Msg.prompt('Name', 'Please enter your name:', function(text) {
         *         // process text value and close...
         *     });
         *
         * ### Confirm
         * Use the {@link #confirm} method to show a confirmation alert (shows yes and no buttons).
         *
         *     @example preview
         *     Ext.Msg.confirm("Confirmation", "Are you sure you want to do that?", Ext.emptyFn);
         */
        Ext.Msg = new MessageBox;
    });
});


/**
 * Wraps an HTML5 number field. Example usage:
 *
 *     @example miniphone
 *     var spinner = Ext.create('Ext.field.Spinner', {
 *         label     : 'Spinner Field',
 *         minValue  : 0,
 *         maxValue  : 100,
 *         stepValue : 2,
 *         cycle     : true
 *     });
 *     Ext.Viewport.add({ xtype: 'container', items: [spinner] });
 *
 * For more information regarding forms and fields, please review [Using Forms in Sencha Touch Guide](../../../components/forms.html)
 */
Ext.define('Ext.field.Spinner', {
    extend: 'Ext.field.Number',
    xtype: 'spinnerfield',
    alternateClassName: 'Ext.form.Spinner',
    requires: ['Ext.util.TapRepeater'],

    /**
     * @event spin
     * Fires when the value is changed via either spinner buttons.
     * @param {Ext.field.Spinner} this
     * @param {Number} value
     * @param {String} direction 'up' or 'down'.
     */

    /**
     * @event spindown
     * Fires when the value is changed via the spinner down button.
     * @param {Ext.field.Spinner} this
     * @param {Number} value
     */

    /**
     * @event spinup
     * Fires when the value is changed via the spinner up button.
     * @param {Ext.field.Spinner} this
     * @param {Number} value
     */

    /**
     * @event change
     * Fires just before the field blurs if the field value has changed.
     * @param {Ext.field.Text} this This field.
     * @param {Number} newValue The new value.
     * @param {Number} oldValue The original value.
     */

    /**
     * @event updatedata
     * @hide
     */

    /**
     * @event action
     * @hide
     */

    config: {
        /**
         * @cfg
         * @inheritdoc
         */
        cls: Ext.baseCSSPrefix + 'spinner',

        /**
         * @cfg {Number} [minValue=-infinity] The minimum allowed value.
         * @accessor
         */
        minValue: Number.NEGATIVE_INFINITY,

        /**
         * @cfg {Number} [maxValue=infinity] The maximum allowed value.
         * @accessor
         */
        maxValue: Number.MAX_VALUE,

        /**
         * @cfg {Number} stepValue Value that is added or subtracted from the current value when a spinner is used.
         * @accessor
         */
        stepValue: 0.1,

        /**
         * @cfg {Boolean} accelerateOnTapHold True if autorepeating should start slowly and accelerate.
         * @accessor
         */
        accelerateOnTapHold: true,

        /**
         * @cfg {Boolean} cycle When set to `true`, it will loop the values of a minimum or maximum is reached.
         * If the maximum value is reached, the value will be set to the minimum.
         * @accessor
         */
        cycle: false,

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

        /**
         * @cfg {Number} defaultValue The default value for this field when no value has been set.
         * It is also used when the value is set to `NaN`.
         */
        defaultValue: 0,

        /**
         * @cfg {Number} tabIndex
         * @hide
         */
        tabIndex: -1,

        /**
         * @cfg {Boolean} groupButtons
         * `true` if you want to group the buttons to the right of the fields. `false` if you want the buttons
         * to be at either side of the field.
         */
        groupButtons: true,

        /**
         * @cfg component
         * @inheritdoc
         */
        component: {
            disabled: true
        },

        /**
         * @cfg {Boolean}
         * `false` if allow to enter an arbitrary value via touch keyboard. `true` only using tap buttons
         */
        onlyTap: false
    },

    platformConfig: [{
        platform: 'android',
        component: {
            disabled: false,
            readOnly: true
        }
    }],

    constructor: function() {
        var me = this;

        me.callParent(arguments);

        if (!me.getValue()) {
            me.suspendEvents();
            me.setValue(me.getDefaultValue());
            me.resumeEvents();
        }
    },

    syncEmptyCls: Ext.emptyFn,

    /**
     * Updates the {@link #component} configuration
     */
    updateComponent: function(newComponent) {
        this.callParent(arguments);

        var cls = this.getCls();

        if (newComponent) {
            this.spinDownButton = Ext.Element.create({
                cls : cls + '-button ' + cls + '-button-down',
                html: '-'
            });

            this.spinUpButton = Ext.Element.create({
                cls : cls + '-button ' + cls + '-button-up',
                html: '+'
            });

            this.downRepeater = this.createRepeater(this.spinDownButton, this.onSpinDown);
            this.upRepeater = this.createRepeater(this.spinUpButton,     this.onSpinUp);
        }
    },

    updateGroupButtons: function(newGroupButtons, oldGroupButtons) {
        var me = this,
            innerElement = me.innerElement,
            cls = me.getBaseCls() + '-grouped-buttons';

        me.getComponent();

        if (newGroupButtons != oldGroupButtons) {
            if (newGroupButtons) {
                this.addCls(cls);
                innerElement.appendChild(me.spinDownButton);
                innerElement.appendChild(me.spinUpButton);
            } else {
                this.removeCls(cls);
                innerElement.insertFirst(me.spinDownButton);
                innerElement.appendChild(me.spinUpButton);
            }
        }
    },

    applyValue: function(value) {
        value = parseFloat(value);
        if (isNaN(value) || value === null) {
            value = this.getDefaultValue();
        }

        //round the value to 1 decimal
        value = Math.round(value * 10) / 10;

        return this.callParent([value]);
    },

    applyOnlyTap: function(value) {
        return Boolean(value);
    },

    updateOnlyTap: function(value) {
        var component = this.getComponent();

        component.setDisabled(value);
        if (Ext.os.is.Android) {
            component.setReadOnly(value);
        }

    },

    // @private
    createRepeater: function(el, fn) {
        var me = this,
            repeater = Ext.create('Ext.util.TapRepeater', {
                el: el,
                accelerate: me.getAccelerateOnTapHold()
            });

        repeater.on({
            tap: fn,
            touchstart: 'onTouchStart',
            touchend: 'onTouchEnd',
            scope: me
        });

        return repeater;
    },

    // @private
    onSpinDown: function() {
        if (!this.getDisabled() && !this.getReadOnly()) {
            this.spin(true);
        }
    },

    // @private
    onSpinUp: function() {
        if (!this.getDisabled() && !this.getReadOnly()) {
            this.spin(false);
        }
    },

    // @private
    onTouchStart: function(repeater) {
        if (!this.getDisabled() && !this.getReadOnly()) {
            repeater.getEl().addCls(Ext.baseCSSPrefix + 'button-pressed');
        }
    },

    // @private
    onTouchEnd: function(repeater) {
        repeater.getEl().removeCls(Ext.baseCSSPrefix + 'button-pressed');
    },

    // @private
    spin: function(down) {
        var me = this,
            originalValue = me.getValue(),
            stepValue = me.getStepValue(),
            direction = down ? 'down' : 'up',
            minValue = me.getMinValue(),
            maxValue = me.getMaxValue(),
            cycle = me.getCycle(),
            value;

        if (down) {
            value = originalValue - stepValue;
        }
        else {
            value = originalValue + stepValue;
        }

        //if cycle is true, then we need to check fi the value hasn't changed and we cycle the value
        if (originalValue >= minValue && value < minValue) {
            if (cycle) {
                value = maxValue;
            } else {
                value = minValue;
            }
        }

        if (originalValue <= maxValue && value > maxValue) {
            if (cycle) {
                value = minValue;
            } else {
                value = maxValue;
            }
        }

        if (minValue <= maxValue) {
            if (value < minValue) {
                value = minValue;
            }
            if (value > maxValue) {
                value = maxValue;
            }
        }

        me.setValue(value);
        value = me.getValue();

        if (value != originalValue) {
            me.fireEvent('spin', me, value, direction);
            me.fireEvent('spin' + direction, me, value);
        }
    },

    /**
     * @private
     */
    doSetDisabled: function(disabled) {
        Ext.Component.prototype.doSetDisabled.apply(this, arguments);
    },

    /**
     * @private
     */
    setDisabled: function() {
        Ext.Component.prototype.setDisabled.apply(this, arguments);
    },

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

//    setValue: function(value){
//        this.callSuper(arguments);

        // @TODO: Isn't this already done by the framework by default?
//        if(Ext.getThemeName() == 'WP'){
//            this.getComponent().element.dom.setAttribute('value',value);
//        }
//    },

    // @private
    destroy: function() {
        var me = this;
        Ext.destroy(me.downRepeater, me.upRepeater, me.spinDownButton, me.spinUpButton);
        me.callParent(arguments);
    }
}, function() {
    //<deprecated product=touch since=2.0>
    this.override({
        constructor: function(config) {
            if (config) {
                /**
                 * @cfg {String} incrementValue
                 * The increment value of this spinner field.
                 * @deprecated 2.0.0 Please use {@link #increment} instead
                 */
                if (config.hasOwnProperty('incrementValue')) {
                    //<debug warn>
                    Ext.Logger.deprecate("'incrementValue' config is deprecated, please use 'stepValue' config instead", this);
                    //</debug>
                    config.stepValue = config.incrementValue;
                    delete config.incrementValue;
                }

                /**
                 * @cfg {String} increment
                 * The increment value of this spinner field.
                 * @deprecated 2.0.0 Please use {@link #stepValue} instead
                 */
                if (config.hasOwnProperty('increment')) {
                    //<debug warn>
                    Ext.Logger.deprecate("'increment' config is deprecated, please use 'stepValue' config instead", this);
                    //</debug>
                    config.stepValue = config.increment;
                    delete config.increment;
                }
            }

            this.callParent([config]);
        }
    });
    //</deprecated>
});